2009-07-05 19:54:49 +00:00
|
|
|
/*
|
|
|
|
* Copyright (C) 2008-2009 Marc Blank
|
|
|
|
* Licensed to The Android Open Source Project.
|
|
|
|
*
|
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
|
* You may obtain a copy of the License at
|
|
|
|
*
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
*
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
* See the License for the specific language governing permissions and
|
|
|
|
* limitations under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
package com.android.exchange;
|
|
|
|
|
2009-08-05 15:41:16 +00:00
|
|
|
import com.android.email.R;
|
|
|
|
import com.android.email.activity.AccountFolderList;
|
2009-07-05 19:54:49 +00:00
|
|
|
import com.android.email.mail.AuthenticationFailedException;
|
|
|
|
import com.android.email.mail.MessagingException;
|
2009-07-23 22:31:28 +00:00
|
|
|
import com.android.email.provider.EmailContent.Account;
|
|
|
|
import com.android.email.provider.EmailContent.AccountColumns;
|
|
|
|
import com.android.email.provider.EmailContent.Attachment;
|
|
|
|
import com.android.email.provider.EmailContent.AttachmentColumns;
|
|
|
|
import com.android.email.provider.EmailContent.HostAuth;
|
|
|
|
import com.android.email.provider.EmailContent.Mailbox;
|
|
|
|
import com.android.email.provider.EmailContent.MailboxColumns;
|
|
|
|
import com.android.email.provider.EmailContent.Message;
|
2009-07-31 01:17:52 +00:00
|
|
|
import com.android.exchange.adapter.AbstractSyncAdapter;
|
|
|
|
import com.android.exchange.adapter.ContactsSyncAdapter;
|
|
|
|
import com.android.exchange.adapter.EmailSyncAdapter;
|
|
|
|
import com.android.exchange.adapter.FolderSyncParser;
|
|
|
|
import com.android.exchange.adapter.PingParser;
|
|
|
|
import com.android.exchange.adapter.Serializer;
|
|
|
|
import com.android.exchange.adapter.Tags;
|
|
|
|
import com.android.exchange.adapter.Parser.EasParserException;
|
2009-07-05 19:54:49 +00:00
|
|
|
import com.android.exchange.utility.Base64;
|
|
|
|
|
2009-08-08 20:26:03 +00:00
|
|
|
import org.apache.http.Header;
|
2009-07-17 23:29:35 +00:00
|
|
|
import org.apache.http.HttpEntity;
|
|
|
|
import org.apache.http.HttpResponse;
|
2009-08-08 20:26:03 +00:00
|
|
|
import org.apache.http.client.HttpClient;
|
|
|
|
import org.apache.http.client.methods.HttpOptions;
|
2009-07-17 23:29:35 +00:00
|
|
|
import org.apache.http.client.methods.HttpPost;
|
2009-08-08 20:26:03 +00:00
|
|
|
import org.apache.http.client.methods.HttpRequestBase;
|
|
|
|
import org.apache.http.entity.ByteArrayEntity;
|
2009-07-17 23:29:35 +00:00
|
|
|
import org.apache.http.impl.client.DefaultHttpClient;
|
2009-08-08 20:26:03 +00:00
|
|
|
import org.apache.http.params.BasicHttpParams;
|
|
|
|
import org.apache.http.params.HttpConnectionParams;
|
|
|
|
import org.apache.http.params.HttpParams;
|
2009-07-17 23:29:35 +00:00
|
|
|
|
2009-08-05 15:41:16 +00:00
|
|
|
import android.app.Notification;
|
|
|
|
import android.app.NotificationManager;
|
|
|
|
import android.app.PendingIntent;
|
2009-07-05 19:54:49 +00:00
|
|
|
import android.content.ContentResolver;
|
2009-08-05 15:41:16 +00:00
|
|
|
import android.content.ContentUris;
|
2009-07-05 19:54:49 +00:00
|
|
|
import android.content.ContentValues;
|
|
|
|
import android.content.Context;
|
2009-08-05 15:41:16 +00:00
|
|
|
import android.content.Intent;
|
2009-07-05 19:54:49 +00:00
|
|
|
import android.database.Cursor;
|
2009-08-11 20:41:13 +00:00
|
|
|
import android.net.ConnectivityManager;
|
2009-07-15 22:08:53 +00:00
|
|
|
import android.os.RemoteException;
|
2009-08-09 04:58:54 +00:00
|
|
|
import android.util.Log;
|
2009-07-05 19:54:49 +00:00
|
|
|
|
2009-07-17 23:29:35 +00:00
|
|
|
import java.io.ByteArrayInputStream;
|
|
|
|
import java.io.File;
|
|
|
|
import java.io.FileOutputStream;
|
|
|
|
import java.io.IOException;
|
|
|
|
import java.io.InputStream;
|
|
|
|
import java.net.HttpURLConnection;
|
|
|
|
import java.net.URI;
|
|
|
|
import java.net.URLEncoder;
|
|
|
|
import java.util.ArrayList;
|
2009-08-05 15:41:16 +00:00
|
|
|
import java.util.HashMap;
|
2009-07-17 23:29:35 +00:00
|
|
|
|
2009-08-05 15:41:16 +00:00
|
|
|
public class EasSyncService extends AbstractSyncService {
|
2009-07-05 19:54:49 +00:00
|
|
|
|
2009-08-07 18:43:32 +00:00
|
|
|
private static final String EMAIL_WINDOW_SIZE = "5";
|
2009-08-08 20:26:03 +00:00
|
|
|
public static final String PIM_WINDOW_SIZE = "20";
|
2009-07-05 19:54:49 +00:00
|
|
|
private static final String WHERE_ACCOUNT_KEY_AND_SERVER_ID =
|
|
|
|
MailboxColumns.ACCOUNT_KEY + "=? and " + MailboxColumns.SERVER_ID + "=?";
|
2009-08-08 05:23:10 +00:00
|
|
|
private static final String WHERE_ACCOUNT_AND_SYNC_INTERVAL_PING =
|
|
|
|
MailboxColumns.ACCOUNT_KEY + "=? and " + MailboxColumns.SYNC_INTERVAL +
|
2009-08-12 05:25:39 +00:00
|
|
|
'=' + Mailbox.CHECK_INTERVAL_PING;
|
2009-07-16 18:42:10 +00:00
|
|
|
private static final String AND_FREQUENCY_PING_PUSH_AND_NOT_ACCOUNT_MAILBOX = " AND " +
|
2009-08-12 05:25:39 +00:00
|
|
|
MailboxColumns.SYNC_INTERVAL + " IN (" + Mailbox.CHECK_INTERVAL_PING +
|
|
|
|
',' + Mailbox.CHECK_INTERVAL_PUSH + ") AND " + MailboxColumns.TYPE + "!=\"" +
|
2009-07-31 01:17:52 +00:00
|
|
|
Mailbox.TYPE_EAS_ACCOUNT_MAILBOX + '\"';
|
2009-08-08 05:23:10 +00:00
|
|
|
private static final String WHERE_PUSH_HOLD_NOT_ACCOUNT_MAILBOX =
|
|
|
|
MailboxColumns.ACCOUNT_KEY + "=? and " + MailboxColumns.SYNC_INTERVAL +
|
2009-08-12 05:25:39 +00:00
|
|
|
'=' + Mailbox.CHECK_INTERVAL_PUSH_HOLD;
|
2009-07-30 20:08:06 +00:00
|
|
|
|
2009-08-08 20:26:03 +00:00
|
|
|
static private final int CHUNK_SIZE = 16*1024;
|
|
|
|
|
|
|
|
static private final String PING_COMMAND = "Ping";
|
2009-08-10 18:25:09 +00:00
|
|
|
static private final int COMMAND_TIMEOUT = 20*SECONDS;
|
|
|
|
static private final int PING_COMMAND_TIMEOUT = 20*MINUTES;
|
|
|
|
|
2009-08-11 20:41:13 +00:00
|
|
|
// For mobile, we use a 5 minute timeout (less a few seconds)
|
2009-08-11 22:19:50 +00:00
|
|
|
static private final int PING_HEARTBEAT_MOBILE = 295;
|
2009-08-11 20:41:13 +00:00
|
|
|
// For wifi, we use a 15 minute timeout
|
2009-08-11 22:19:50 +00:00
|
|
|
static private final int PING_HEARTBEAT_WIFI = 900;
|
2009-08-11 20:41:13 +00:00
|
|
|
|
2009-08-10 18:25:09 +00:00
|
|
|
// Fallbacks (in minutes) for ping loop failures
|
2009-08-11 20:41:13 +00:00
|
|
|
static private final int MAX_PING_FAILURES = 2;
|
2009-08-10 18:25:09 +00:00
|
|
|
static private final int PING_FALLBACK_INBOX = 5;
|
2009-08-11 16:52:27 +00:00
|
|
|
static private final int PING_FALLBACK_PIM = 30;
|
2009-07-15 22:08:53 +00:00
|
|
|
|
2009-07-05 19:54:49 +00:00
|
|
|
// Reasonable default
|
|
|
|
String mProtocolVersion = "2.5";
|
2009-07-17 23:29:35 +00:00
|
|
|
public Double mProtocolVersionDouble;
|
2009-08-08 05:23:10 +00:00
|
|
|
private String mDeviceId = null;
|
|
|
|
private String mDeviceType = "Android";
|
2009-07-31 01:17:52 +00:00
|
|
|
AbstractSyncAdapter mTarget;
|
2009-07-05 19:54:49 +00:00
|
|
|
String mAuthString = null;
|
|
|
|
String mCmdString = null;
|
|
|
|
public String mHostAddress;
|
|
|
|
public String mUserName;
|
|
|
|
public String mPassword;
|
|
|
|
String mDomain = null;
|
|
|
|
boolean mSentCommands;
|
|
|
|
boolean mIsIdle = false;
|
|
|
|
boolean mSsl = true;
|
|
|
|
public Context mContext;
|
|
|
|
public ContentResolver mContentResolver;
|
|
|
|
String[] mBindArguments = new String[2];
|
|
|
|
InputStream mPendingPartInputStream = null;
|
2009-08-10 18:25:09 +00:00
|
|
|
HttpPost mPendingPost = null;
|
2009-08-11 20:41:13 +00:00
|
|
|
int mNetworkType;
|
2009-08-11 22:19:50 +00:00
|
|
|
int mPingHeartbeat;
|
2009-07-05 19:54:49 +00:00
|
|
|
|
|
|
|
public EasSyncService(Context _context, Mailbox _mailbox) {
|
|
|
|
super(_context, _mailbox);
|
|
|
|
mContext = _context;
|
|
|
|
mContentResolver = _context.getContentResolver();
|
|
|
|
HostAuth ha = HostAuth.restoreHostAuthWithId(_context, mAccount.mHostAuthKeyRecv);
|
|
|
|
mSsl = (ha.mFlags & HostAuth.FLAG_SSL) != 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
private EasSyncService(String prefix) {
|
|
|
|
super(prefix);
|
|
|
|
}
|
|
|
|
|
|
|
|
public EasSyncService() {
|
|
|
|
this("EAS Validation");
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void ping() {
|
|
|
|
userLog("We've been pinged!");
|
2009-08-07 18:43:32 +00:00
|
|
|
Object synchronizer = getSynchronizer();
|
|
|
|
synchronized (synchronizer) {
|
|
|
|
synchronizer.notify();
|
2009-07-05 19:54:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void stop() {
|
|
|
|
mStop = true;
|
2009-08-10 18:25:09 +00:00
|
|
|
synchronized(getSynchronizer()) {
|
|
|
|
if (mPendingPost != null) {
|
|
|
|
mPendingPost.abort();
|
|
|
|
}
|
|
|
|
}
|
2009-08-09 04:21:54 +00:00
|
|
|
}
|
2009-07-05 19:54:49 +00:00
|
|
|
|
2009-07-30 20:08:06 +00:00
|
|
|
@Override
|
2009-07-05 19:54:49 +00:00
|
|
|
public int getSyncStatus() {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2009-08-02 22:33:22 +00:00
|
|
|
private boolean isAuthError(int code) {
|
2009-08-09 04:58:54 +00:00
|
|
|
return (code == HttpURLConnection.HTTP_UNAUTHORIZED
|
|
|
|
|| code == HttpURLConnection.HTTP_FORBIDDEN
|
2009-08-02 22:33:22 +00:00
|
|
|
|| code == HttpURLConnection.HTTP_INTERNAL_ERROR);
|
|
|
|
}
|
|
|
|
|
2009-07-30 20:08:06 +00:00
|
|
|
@Override
|
2009-07-05 19:54:49 +00:00
|
|
|
public void validateAccount(String hostAddress, String userName, String password, int port,
|
|
|
|
boolean ssl, Context context) throws MessagingException {
|
|
|
|
try {
|
2009-07-31 01:17:52 +00:00
|
|
|
userLog("Testing EAS: " + hostAddress + ", " + userName + ", ssl = " + ssl);
|
2009-07-05 19:54:49 +00:00
|
|
|
EasSyncService svc = new EasSyncService("%TestAccount%");
|
2009-08-08 05:23:10 +00:00
|
|
|
svc.mContext = context;
|
2009-07-05 19:54:49 +00:00
|
|
|
svc.mHostAddress = hostAddress;
|
|
|
|
svc.mUserName = userName;
|
|
|
|
svc.mPassword = password;
|
|
|
|
svc.mSsl = ssl;
|
2009-08-08 05:23:10 +00:00
|
|
|
svc.mDeviceId = SyncManager.getDeviceId();
|
2009-08-08 20:26:03 +00:00
|
|
|
HttpResponse resp = svc.sendHttpClientOptions();
|
|
|
|
int code = resp.getStatusLine().getStatusCode();
|
2009-08-08 05:23:10 +00:00
|
|
|
userLog("Validation (OPTIONS) response: " + code);
|
2009-07-05 19:54:49 +00:00
|
|
|
if (code == HttpURLConnection.HTTP_OK) {
|
|
|
|
// No exception means successful validation
|
|
|
|
userLog("Validation successful");
|
|
|
|
return;
|
|
|
|
}
|
2009-08-02 22:33:22 +00:00
|
|
|
if (isAuthError(code)) {
|
2009-07-05 19:54:49 +00:00
|
|
|
userLog("Authentication failed");
|
|
|
|
throw new AuthenticationFailedException("Validation failed");
|
|
|
|
} else {
|
|
|
|
// TODO Need to catch other kinds of errors (e.g. policy) For now, report the code.
|
|
|
|
userLog("Validation failed, reporting I/O error: " + code);
|
|
|
|
throw new MessagingException(MessagingException.IOERROR);
|
|
|
|
}
|
|
|
|
} catch (IOException e) {
|
|
|
|
userLog("IOException caught, reporting I/O error: " + e.getMessage());
|
|
|
|
throw new MessagingException(MessagingException.IOERROR);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2009-07-29 01:28:24 +00:00
|
|
|
private void doStatusCallback(long messageId, long attachmentId, int status) {
|
2009-07-15 22:08:53 +00:00
|
|
|
try {
|
2009-07-30 20:08:06 +00:00
|
|
|
SyncManager.callback().loadAttachmentStatus(messageId, attachmentId, status, 0);
|
|
|
|
} catch (RemoteException e) {
|
2009-07-15 22:08:53 +00:00
|
|
|
// No danger if the client is no longer around
|
|
|
|
}
|
|
|
|
}
|
2009-07-05 19:54:49 +00:00
|
|
|
|
2009-07-29 01:28:24 +00:00
|
|
|
private void doProgressCallback(long messageId, long attachmentId, int progress) {
|
2009-07-15 22:08:53 +00:00
|
|
|
try {
|
2009-07-30 20:08:06 +00:00
|
|
|
SyncManager.callback().loadAttachmentStatus(messageId, attachmentId,
|
|
|
|
EmailServiceStatus.IN_PROGRESS, progress);
|
|
|
|
} catch (RemoteException e) {
|
2009-07-15 22:08:53 +00:00
|
|
|
// No danger if the client is no longer around
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-07-24 16:11:17 +00:00
|
|
|
public File createUniqueFileInternal(String dir, String filename) {
|
|
|
|
File directory;
|
|
|
|
if (dir == null) {
|
|
|
|
directory = mContext.getFilesDir();
|
|
|
|
} else {
|
|
|
|
directory = new File(dir);
|
|
|
|
}
|
|
|
|
if (!directory.exists()) {
|
|
|
|
directory.mkdirs();
|
|
|
|
}
|
|
|
|
File file = new File(directory, filename);
|
|
|
|
if (!file.exists()) {
|
|
|
|
return file;
|
|
|
|
}
|
|
|
|
// Get the extension of the file, if any.
|
|
|
|
int index = filename.lastIndexOf('.');
|
|
|
|
String name = filename;
|
|
|
|
String extension = "";
|
|
|
|
if (index != -1) {
|
|
|
|
name = filename.substring(0, index);
|
|
|
|
extension = filename.substring(index);
|
|
|
|
}
|
|
|
|
for (int i = 2; i < Integer.MAX_VALUE; i++) {
|
|
|
|
file = new File(directory, name + '-' + i + extension);
|
|
|
|
if (!file.exists()) {
|
|
|
|
return file;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2009-07-15 22:08:53 +00:00
|
|
|
/**
|
|
|
|
* Loads an attachment, based on the PartRequest passed in. The PartRequest is basically our
|
|
|
|
* wrapper for Attachment
|
|
|
|
* @param req the part (attachment) to be retrieved
|
|
|
|
* @throws IOException
|
|
|
|
*/
|
2009-07-24 16:11:17 +00:00
|
|
|
protected void getAttachment(PartRequest req) throws IOException {
|
2009-07-15 22:08:53 +00:00
|
|
|
Attachment att = req.att;
|
2009-07-20 03:10:35 +00:00
|
|
|
Message msg = Message.restoreMessageWithId(mContext, att.mMessageKey);
|
2009-07-29 01:28:24 +00:00
|
|
|
doProgressCallback(msg.mId, att.mId, 0);
|
2009-07-05 19:54:49 +00:00
|
|
|
DefaultHttpClient client = new DefaultHttpClient();
|
2009-07-15 22:08:53 +00:00
|
|
|
String us = makeUriString("GetAttachment", "&AttachmentName=" + att.mLocation);
|
2009-07-05 19:54:49 +00:00
|
|
|
HttpPost method = new HttpPost(URI.create(us));
|
|
|
|
method.setHeader("Authorization", mAuthString);
|
|
|
|
|
|
|
|
HttpResponse res = client.execute(method);
|
|
|
|
int status = res.getStatusLine().getStatusCode();
|
|
|
|
if (status == HttpURLConnection.HTTP_OK) {
|
|
|
|
HttpEntity e = res.getEntity();
|
|
|
|
int len = (int)e.getContentLength();
|
|
|
|
String type = e.getContentType().getValue();
|
|
|
|
InputStream is = res.getEntity().getContent();
|
2009-07-28 02:52:21 +00:00
|
|
|
File f = (req.destination != null)
|
|
|
|
? new File(req.destination)
|
|
|
|
: createUniqueFileInternal(req.destination, att.mFileName);
|
2009-07-05 19:54:49 +00:00
|
|
|
if (f != null) {
|
2009-07-28 02:52:21 +00:00
|
|
|
// Ensure that the target directory exists
|
|
|
|
File destDir = f.getParentFile();
|
|
|
|
if (!destDir.exists()) {
|
|
|
|
destDir.mkdirs();
|
|
|
|
}
|
2009-07-05 19:54:49 +00:00
|
|
|
FileOutputStream os = new FileOutputStream(f);
|
|
|
|
if (len > 0) {
|
|
|
|
try {
|
|
|
|
mPendingPartRequest = req;
|
|
|
|
mPendingPartInputStream = is;
|
|
|
|
byte[] bytes = new byte[CHUNK_SIZE];
|
|
|
|
int length = len;
|
|
|
|
while (len > 0) {
|
|
|
|
int n = (len > CHUNK_SIZE ? CHUNK_SIZE : len);
|
|
|
|
int read = is.read(bytes, 0, n);
|
|
|
|
os.write(bytes, 0, read);
|
|
|
|
len -= read;
|
2009-07-15 22:08:53 +00:00
|
|
|
int pct = ((length - len) * 100 / length);
|
2009-07-29 01:28:24 +00:00
|
|
|
doProgressCallback(msg.mId, att.mId, pct);
|
2009-07-05 19:54:49 +00:00
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
mPendingPartRequest = null;
|
|
|
|
mPendingPartInputStream = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
os.flush();
|
|
|
|
os.close();
|
|
|
|
|
2009-07-15 22:08:53 +00:00
|
|
|
// EmailProvider will throw an exception if we try to update an unsaved attachment
|
|
|
|
if (att.isSaved()) {
|
2009-07-28 02:52:21 +00:00
|
|
|
String contentUriString = (req.contentUriString != null)
|
|
|
|
? req.contentUriString
|
|
|
|
: "file://" + f.getAbsolutePath();
|
2009-07-15 22:08:53 +00:00
|
|
|
ContentValues cv = new ContentValues();
|
2009-07-28 02:52:21 +00:00
|
|
|
cv.put(AttachmentColumns.CONTENT_URI, contentUriString);
|
2009-07-15 22:08:53 +00:00
|
|
|
cv.put(AttachmentColumns.MIME_TYPE, type);
|
|
|
|
att.update(mContext, cv);
|
2009-07-29 01:28:24 +00:00
|
|
|
doStatusCallback(msg.mId, att.mId, EmailServiceStatus.SUCCESS);
|
2009-07-15 22:08:53 +00:00
|
|
|
}
|
2009-07-05 19:54:49 +00:00
|
|
|
}
|
2009-07-15 22:08:53 +00:00
|
|
|
} else {
|
2009-07-29 01:28:24 +00:00
|
|
|
doStatusCallback(msg.mId, att.mId, EmailServiceStatus.MESSAGE_NOT_FOUND);
|
2009-07-05 19:54:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-07-27 17:24:58 +00:00
|
|
|
@SuppressWarnings("deprecation")
|
2009-08-08 05:23:10 +00:00
|
|
|
private String makeUriString(String cmd, String extra) throws IOException {
|
2009-07-05 19:54:49 +00:00
|
|
|
// Cache the authentication string and the command string
|
|
|
|
String safeUserName = URLEncoder.encode(mUserName);
|
|
|
|
if (mAuthString == null) {
|
|
|
|
String cs = mUserName + ':' + mPassword;
|
|
|
|
mAuthString = "Basic " + Base64.encodeBytes(cs.getBytes());
|
|
|
|
mCmdString = "&User=" + safeUserName + "&DeviceId=" + mDeviceId + "&DeviceType="
|
|
|
|
+ mDeviceType;
|
|
|
|
}
|
|
|
|
String us = (mSsl ? "https" : "http") + "://" + mHostAddress +
|
|
|
|
"/Microsoft-Server-ActiveSync";
|
|
|
|
if (cmd != null) {
|
|
|
|
us += "?Cmd=" + cmd + mCmdString;
|
|
|
|
}
|
|
|
|
if (extra != null) {
|
|
|
|
us += extra;
|
|
|
|
}
|
|
|
|
return us;
|
|
|
|
}
|
|
|
|
|
2009-08-08 20:26:03 +00:00
|
|
|
private void setHeaders(HttpRequestBase method) {
|
|
|
|
method.setHeader("Authorization", mAuthString);
|
|
|
|
method.setHeader("MS-ASProtocolVersion", mProtocolVersion);
|
|
|
|
method.setHeader("Connection", "keep-alive");
|
|
|
|
method.setHeader("User-Agent", mDeviceType + '/' + Eas.VERSION);
|
|
|
|
}
|
2009-07-05 19:54:49 +00:00
|
|
|
|
2009-08-08 20:26:03 +00:00
|
|
|
private HttpClient getHttpClient(int timeout) {
|
|
|
|
HttpParams params = new BasicHttpParams();
|
2009-08-10 18:25:09 +00:00
|
|
|
HttpConnectionParams.setConnectionTimeout(params, 10*SECONDS);
|
2009-08-08 20:26:03 +00:00
|
|
|
HttpConnectionParams.setSoTimeout(params, timeout);
|
|
|
|
return new DefaultHttpClient(params);
|
|
|
|
}
|
2009-07-05 19:54:49 +00:00
|
|
|
|
2009-08-08 20:26:03 +00:00
|
|
|
protected HttpResponse sendHttpClientPost(String cmd, byte[] bytes) throws IOException {
|
2009-08-12 17:35:40 +00:00
|
|
|
return sendHttpClientPost(cmd, new ByteArrayEntity(bytes));
|
|
|
|
}
|
|
|
|
|
|
|
|
protected HttpResponse sendHttpClientPost(String cmd, HttpEntity entity) throws IOException {
|
2009-08-08 20:26:03 +00:00
|
|
|
HttpClient client =
|
|
|
|
getHttpClient(cmd.equals(PING_COMMAND) ? PING_COMMAND_TIMEOUT : COMMAND_TIMEOUT);
|
|
|
|
String us = makeUriString(cmd, null);
|
|
|
|
HttpPost method = new HttpPost(URI.create(us));
|
|
|
|
if (cmd.startsWith("SendMail&")) {
|
|
|
|
method.setHeader("Content-Type", "message/rfc822");
|
|
|
|
} else {
|
|
|
|
method.setHeader("Content-Type", "application/vnd.ms-sync.wbxml");
|
2009-07-05 19:54:49 +00:00
|
|
|
}
|
2009-08-08 20:26:03 +00:00
|
|
|
setHeaders(method);
|
2009-08-12 17:35:40 +00:00
|
|
|
method.setEntity(entity);
|
2009-08-10 18:25:09 +00:00
|
|
|
synchronized(getSynchronizer()) {
|
|
|
|
mPendingPost = method;
|
|
|
|
}
|
|
|
|
try {
|
|
|
|
return client.execute(method);
|
|
|
|
} finally {
|
|
|
|
synchronized(getSynchronizer()) {
|
|
|
|
mPendingPost = null;
|
|
|
|
}
|
|
|
|
}
|
2009-08-08 20:26:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
protected HttpResponse sendHttpClientOptions() throws IOException {
|
|
|
|
HttpClient client = getHttpClient(COMMAND_TIMEOUT);
|
|
|
|
String us = makeUriString("OPTIONS", null);
|
|
|
|
HttpOptions method = new HttpOptions(URI.create(us));
|
|
|
|
setHeaders(method);
|
|
|
|
return client.execute(method);
|
2009-07-05 19:54:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
String getTargetCollectionClassFromCursor(Cursor c) {
|
|
|
|
int type = c.getInt(Mailbox.CONTENT_TYPE_COLUMN);
|
|
|
|
if (type == Mailbox.TYPE_CONTACTS) {
|
|
|
|
return "Contacts";
|
|
|
|
} else if (type == Mailbox.TYPE_CALENDAR) {
|
|
|
|
return "Calendar";
|
|
|
|
} else {
|
|
|
|
return "Email";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Performs FolderSync
|
|
|
|
*
|
|
|
|
* @throws IOException
|
|
|
|
* @throws EasParserException
|
|
|
|
*/
|
2009-07-31 04:17:19 +00:00
|
|
|
public void runAccountMailbox() throws IOException, EasParserException {
|
2009-07-30 20:08:06 +00:00
|
|
|
// Initialize exit status to success
|
2009-08-11 20:41:13 +00:00
|
|
|
mNetworkType = waitForConnectivity();
|
2009-08-11 22:19:50 +00:00
|
|
|
mPingHeartbeat = PING_HEARTBEAT_MOBILE;
|
|
|
|
if (mNetworkType == ConnectivityManager.TYPE_WIFI) {
|
|
|
|
mPingHeartbeat = PING_HEARTBEAT_WIFI;
|
|
|
|
}
|
2009-07-30 20:08:06 +00:00
|
|
|
mExitStatus = EmailServiceStatus.SUCCESS;
|
2009-07-05 19:54:49 +00:00
|
|
|
try {
|
2009-07-30 20:08:06 +00:00
|
|
|
try {
|
|
|
|
SyncManager.callback()
|
|
|
|
.syncMailboxListStatus(mAccount.mId, EmailServiceStatus.IN_PROGRESS, 0);
|
|
|
|
} catch (RemoteException e1) {
|
|
|
|
// Don't care if this fails
|
|
|
|
}
|
|
|
|
|
2009-07-05 19:54:49 +00:00
|
|
|
if (mAccount.mSyncKey == null) {
|
|
|
|
mAccount.mSyncKey = "0";
|
2009-08-07 18:43:32 +00:00
|
|
|
userLog("Account syncKey INIT to 0");
|
2009-07-22 22:13:30 +00:00
|
|
|
ContentValues cv = new ContentValues();
|
|
|
|
cv.put(AccountColumns.SYNC_KEY, mAccount.mSyncKey);
|
|
|
|
mAccount.update(mContext, cv);
|
2009-07-05 19:54:49 +00:00
|
|
|
}
|
|
|
|
|
2009-08-08 05:23:10 +00:00
|
|
|
boolean firstSync = mAccount.mSyncKey.equals("0");
|
|
|
|
if (firstSync) {
|
2009-08-07 18:43:32 +00:00
|
|
|
userLog("Initial FolderSync");
|
|
|
|
}
|
|
|
|
|
2009-07-05 19:54:49 +00:00
|
|
|
// When we first start up, change all ping mailboxes to push.
|
|
|
|
ContentValues cv = new ContentValues();
|
2009-08-12 05:25:39 +00:00
|
|
|
cv.put(Mailbox.SYNC_INTERVAL, Mailbox.CHECK_INTERVAL_PUSH);
|
2009-07-05 19:54:49 +00:00
|
|
|
if (mContentResolver.update(Mailbox.CONTENT_URI, cv,
|
2009-08-08 05:23:10 +00:00
|
|
|
WHERE_ACCOUNT_AND_SYNC_INTERVAL_PING,
|
|
|
|
new String[] {Long.toString(mAccount.mId)}) > 0) {
|
|
|
|
SyncManager.kick("change ping boxes to push");
|
2009-07-05 19:54:49 +00:00
|
|
|
}
|
|
|
|
|
2009-08-03 17:47:49 +00:00
|
|
|
// Determine our protocol version, if we haven't already
|
|
|
|
if (mAccount.mProtocolVersion == null) {
|
2009-08-07 18:43:32 +00:00
|
|
|
userLog("Determine EAS protocol version");
|
2009-08-08 20:26:03 +00:00
|
|
|
HttpResponse resp = sendHttpClientOptions();
|
|
|
|
int code = resp.getStatusLine().getStatusCode();
|
|
|
|
userLog("OPTIONS response: " + code);
|
|
|
|
if (code == HttpURLConnection.HTTP_OK) {
|
|
|
|
Header header = resp.getFirstHeader("ms-asprotocolversions");
|
|
|
|
String versions = header.getValue();
|
|
|
|
if (versions != null) {
|
|
|
|
if (versions.contains("12.0")) {
|
|
|
|
mProtocolVersion = "12.0";
|
2009-07-05 19:54:49 +00:00
|
|
|
}
|
2009-08-08 20:26:03 +00:00
|
|
|
mProtocolVersionDouble = Double.parseDouble(mProtocolVersion);
|
|
|
|
mAccount.mProtocolVersion = mProtocolVersion;
|
|
|
|
userLog(versions);
|
|
|
|
userLog("Using version " + mProtocolVersion);
|
2009-07-05 19:54:49 +00:00
|
|
|
} else {
|
2009-08-08 20:26:03 +00:00
|
|
|
errorLog("No protocol versions in OPTIONS response");
|
2009-07-05 19:54:49 +00:00
|
|
|
throw new IOException();
|
|
|
|
}
|
2009-08-08 20:26:03 +00:00
|
|
|
} else {
|
|
|
|
errorLog("OPTIONS command failed; throwing IOException");
|
|
|
|
throw new IOException();
|
2009-08-03 17:47:49 +00:00
|
|
|
}
|
|
|
|
}
|
2009-08-07 18:43:32 +00:00
|
|
|
|
2009-08-09 04:58:54 +00:00
|
|
|
// Change all pushable boxes to push when we start the account mailbox
|
|
|
|
if (mAccount.mSyncInterval == Account.CHECK_INTERVAL_PUSH) {
|
|
|
|
cv = new ContentValues();
|
2009-08-12 05:25:39 +00:00
|
|
|
cv.put(Mailbox.SYNC_INTERVAL, Mailbox.CHECK_INTERVAL_PUSH);
|
2009-08-09 04:58:54 +00:00
|
|
|
if (mContentResolver.update(Mailbox.CONTENT_URI, cv,
|
|
|
|
SyncManager.WHERE_IN_ACCOUNT_AND_PUSHABLE,
|
|
|
|
new String[] {Long.toString(mAccount.mId)}) > 0) {
|
|
|
|
userLog("Push account; set pushable boxes to push...");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
while (!mStop) {
|
2009-08-08 20:26:03 +00:00
|
|
|
userLog("Sending Account syncKey: " + mAccount.mSyncKey);
|
|
|
|
Serializer s = new Serializer();
|
|
|
|
s.start(Tags.FOLDER_FOLDER_SYNC).start(Tags.FOLDER_SYNC_KEY)
|
2009-08-09 04:58:54 +00:00
|
|
|
.text(mAccount.mSyncKey).end().end().done();
|
2009-08-08 20:26:03 +00:00
|
|
|
HttpResponse resp = sendHttpClientPost("FolderSync", s.toByteArray());
|
|
|
|
if (mStop) break;
|
|
|
|
int code = resp.getStatusLine().getStatusCode();
|
|
|
|
if (code == HttpURLConnection.HTTP_OK) {
|
|
|
|
HttpEntity entity = resp.getEntity();
|
|
|
|
int len = (int)entity.getContentLength();
|
|
|
|
if (len > 0) {
|
|
|
|
InputStream is = entity.getContent();
|
|
|
|
// Returns true if we need to sync again
|
2009-08-09 01:59:33 +00:00
|
|
|
userLog("FolderSync, deviceId = " + mDeviceId);
|
2009-08-08 20:26:03 +00:00
|
|
|
if (new FolderSyncParser(is, this).parse()) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (code == HttpURLConnection.HTTP_UNAUTHORIZED ||
|
2009-08-03 17:47:49 +00:00
|
|
|
code == HttpURLConnection.HTTP_FORBIDDEN) {
|
|
|
|
mExitStatus = AbstractSyncService.EXIT_LOGIN_FAILURE;
|
|
|
|
} else {
|
|
|
|
userLog("FolderSync response error: " + code);
|
|
|
|
}
|
2009-07-05 19:54:49 +00:00
|
|
|
|
2009-08-08 05:23:10 +00:00
|
|
|
// Change all push/hold boxes to push
|
|
|
|
cv = new ContentValues();
|
|
|
|
cv.put(Mailbox.SYNC_INTERVAL, Account.CHECK_INTERVAL_PUSH);
|
|
|
|
if (mContentResolver.update(Mailbox.CONTENT_URI, cv,
|
|
|
|
WHERE_PUSH_HOLD_NOT_ACCOUNT_MAILBOX,
|
|
|
|
new String[] {Long.toString(mAccount.mId)}) > 0) {
|
|
|
|
userLog("Set push/hold boxes to push...");
|
|
|
|
}
|
|
|
|
|
2009-08-03 17:47:49 +00:00
|
|
|
try {
|
|
|
|
SyncManager.callback()
|
2009-08-08 05:23:10 +00:00
|
|
|
.syncMailboxListStatus(mAccount.mId, mExitStatus, 0);
|
2009-08-03 17:47:49 +00:00
|
|
|
} catch (RemoteException e1) {
|
|
|
|
// Don't care if this fails
|
|
|
|
}
|
2009-07-30 20:08:06 +00:00
|
|
|
|
2009-08-03 17:47:49 +00:00
|
|
|
// Wait for push notifications.
|
|
|
|
String threadName = Thread.currentThread().getName();
|
|
|
|
try {
|
|
|
|
runPingLoop();
|
|
|
|
} catch (StaleFolderListException e) {
|
|
|
|
// We break out if we get told about a stale folder list
|
|
|
|
userLog("Ping interrupted; folder list requires sync...");
|
|
|
|
} finally {
|
|
|
|
Thread.currentThread().setName(threadName);
|
|
|
|
}
|
2009-07-05 19:54:49 +00:00
|
|
|
}
|
2009-08-03 17:47:49 +00:00
|
|
|
} catch (IOException e) {
|
2009-07-30 20:08:06 +00:00
|
|
|
// We catch this here to send the folder sync status callback
|
|
|
|
// A folder sync failed callback will get sent from run()
|
|
|
|
try {
|
2009-08-02 22:29:41 +00:00
|
|
|
if (!mStop) {
|
|
|
|
SyncManager.callback()
|
|
|
|
.syncMailboxListStatus(mAccount.mId,
|
|
|
|
EmailServiceStatus.CONNECTION_ERROR, 0);
|
|
|
|
}
|
2009-07-30 20:08:06 +00:00
|
|
|
} catch (RemoteException e1) {
|
|
|
|
// Don't care if this fails
|
|
|
|
}
|
2009-08-11 20:41:13 +00:00
|
|
|
throw e;
|
2009-07-05 19:54:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-08-10 18:25:09 +00:00
|
|
|
void pushFallback(long mailboxId) {
|
|
|
|
Mailbox mailbox = Mailbox.restoreMailboxWithId(mContext, mailboxId);
|
|
|
|
ContentValues cv = new ContentValues();
|
|
|
|
if (mailbox.mType == Mailbox.TYPE_INBOX) {
|
|
|
|
cv.put(Mailbox.SYNC_INTERVAL, PING_FALLBACK_INBOX);
|
|
|
|
errorLog("*** PING LOOP: Turning off push due to ping loop on inbox...");
|
2009-08-05 15:41:16 +00:00
|
|
|
mContentResolver.update(Mailbox.CONTENT_URI, cv,
|
|
|
|
MailboxColumns.ACCOUNT_KEY + '=' + mAccount.mId
|
|
|
|
+ AND_FREQUENCY_PING_PUSH_AND_NOT_ACCOUNT_MAILBOX, null);
|
|
|
|
// Now, change the account as well
|
|
|
|
cv.clear();
|
2009-08-10 18:25:09 +00:00
|
|
|
cv.put(Account.SYNC_INTERVAL, PING_FALLBACK_INBOX);
|
2009-08-05 15:41:16 +00:00
|
|
|
mContentResolver.update(ContentUris.withAppendedId(Account.CONTENT_URI, mAccount.mId),
|
|
|
|
cv, null, null);
|
2009-08-10 18:25:09 +00:00
|
|
|
|
2009-08-09 04:58:54 +00:00
|
|
|
// Let the SyncManager know that something's changed
|
|
|
|
SyncManager.kick("push fallback");
|
|
|
|
|
2009-08-05 15:41:16 +00:00
|
|
|
// TODO Discuss the best way to alert the user
|
|
|
|
// Alert the user about what we've done
|
|
|
|
NotificationManager nm = (NotificationManager)mContext
|
|
|
|
.getSystemService(Context.NOTIFICATION_SERVICE);
|
|
|
|
Notification note =
|
|
|
|
new Notification(R.drawable.stat_notify_email_generic,
|
|
|
|
mContext.getString(R.string.notification_ping_loop_title),
|
|
|
|
System.currentTimeMillis());
|
|
|
|
Intent i = new Intent(mContext, AccountFolderList.class);
|
|
|
|
PendingIntent pi = PendingIntent.getActivity(mContext, 0, i, 0);
|
|
|
|
note.setLatestEventInfo(mContext,
|
|
|
|
mContext.getString(R.string.notification_ping_loop_title),
|
|
|
|
mContext.getString(R.string.notification_ping_loop_text), pi);
|
|
|
|
nm.notify(Eas.EXCHANGE_ERROR_NOTIFICATION, note);
|
2009-08-10 18:25:09 +00:00
|
|
|
} else {
|
|
|
|
// Just change this box to sync every 10 minutes
|
|
|
|
cv.put(Mailbox.SYNC_INTERVAL, PING_FALLBACK_PIM);
|
|
|
|
mContentResolver.update(ContentUris.withAppendedId(Mailbox.CONTENT_URI, mailboxId),
|
|
|
|
cv, null, null);
|
|
|
|
errorLog("*** PING LOOP: Backing off sync of " + mailbox.mDisplayName + " to 10 mins");
|
|
|
|
}
|
2009-08-02 22:33:22 +00:00
|
|
|
}
|
|
|
|
|
2009-07-05 19:54:49 +00:00
|
|
|
void runPingLoop() throws IOException, StaleFolderListException {
|
|
|
|
// Do push for all sync services here
|
2009-08-02 22:33:22 +00:00
|
|
|
ArrayList<Mailbox> pushBoxes = new ArrayList<Mailbox>();
|
2009-08-10 18:25:09 +00:00
|
|
|
long endTime = System.currentTimeMillis() + (30*MINUTES);
|
2009-08-05 15:41:16 +00:00
|
|
|
HashMap<Long, Integer> pingFailureMap = new HashMap<Long, Integer>();
|
2009-07-05 19:54:49 +00:00
|
|
|
|
|
|
|
while (System.currentTimeMillis() < endTime) {
|
|
|
|
// Count of pushable mailboxes
|
|
|
|
int pushCount = 0;
|
|
|
|
// Count of mailboxes that can be pushed right now
|
|
|
|
int canPushCount = 0;
|
2009-07-31 01:17:52 +00:00
|
|
|
Serializer s = new Serializer();
|
2009-07-05 19:54:49 +00:00
|
|
|
int code;
|
|
|
|
Cursor c = mContentResolver.query(Mailbox.CONTENT_URI, Mailbox.CONTENT_PROJECTION,
|
2009-07-16 18:42:10 +00:00
|
|
|
MailboxColumns.ACCOUNT_KEY + '=' + mAccount.mId +
|
|
|
|
AND_FREQUENCY_PING_PUSH_AND_NOT_ACCOUNT_MAILBOX, null, null);
|
2009-07-05 19:54:49 +00:00
|
|
|
|
2009-08-02 22:33:22 +00:00
|
|
|
pushBoxes.clear();
|
|
|
|
|
2009-07-05 19:54:49 +00:00
|
|
|
try {
|
|
|
|
// Loop through our pushed boxes seeing what is available to push
|
|
|
|
while (c.moveToNext()) {
|
|
|
|
pushCount++;
|
|
|
|
// Two requirements for push:
|
|
|
|
// 1) SyncManager tells us the mailbox is syncable (not running, not stopped)
|
|
|
|
// 2) The syncKey isn't "0" (i.e. it's synced at least once)
|
2009-08-05 15:41:16 +00:00
|
|
|
long mailboxId = c.getLong(Mailbox.CONTENT_ID_COLUMN);
|
2009-08-09 04:58:54 +00:00
|
|
|
int pingStatus = SyncManager.pingStatus(mailboxId);
|
|
|
|
String mailboxName = c.getString(Mailbox.CONTENT_DISPLAY_NAME_COLUMN);
|
|
|
|
if (pingStatus == SyncManager.PING_STATUS_OK) {
|
2009-07-05 19:54:49 +00:00
|
|
|
String syncKey = c.getString(Mailbox.CONTENT_SYNC_KEY_COLUMN);
|
|
|
|
if (syncKey == null || syncKey.equals("0")) {
|
2009-08-09 04:58:54 +00:00
|
|
|
pushCount--;
|
2009-07-05 19:54:49 +00:00
|
|
|
continue;
|
|
|
|
}
|
2009-08-05 15:41:16 +00:00
|
|
|
|
|
|
|
// Take a peek at this box's behavior last sync
|
|
|
|
// We do this because some Exchange 2003 servers put themselves (and
|
|
|
|
// therefore our client) into a "ping loop" in which the client is
|
|
|
|
// continuously told of server changes, only to find that there aren't any.
|
|
|
|
// This behavior is seemingly random, and we must code defensively by
|
|
|
|
// backing off of push behavior when this is detected.
|
|
|
|
// The server fix is at http://support.microsoft.com/kb/923282
|
|
|
|
|
|
|
|
// Sync status is encoded as S<type>:<exitstatus>:<changes>
|
|
|
|
String status = c.getString(Mailbox.CONTENT_SYNC_STATUS_COLUMN);
|
|
|
|
int type = SyncManager.getStatusType(status);
|
|
|
|
if (type == SyncManager.SYNC_PING) {
|
|
|
|
int changeCount = SyncManager.getStatusChangeCount(status);
|
2009-08-10 18:25:09 +00:00
|
|
|
if (changeCount > 0) {
|
|
|
|
pingFailureMap.remove(mailboxId);
|
|
|
|
} else if (changeCount == 0) {
|
2009-08-05 15:41:16 +00:00
|
|
|
// This means that a ping failed; we'll keep track of this
|
|
|
|
Integer failures = pingFailureMap.get(mailboxId);
|
|
|
|
if (failures == null) {
|
|
|
|
pingFailureMap.put(mailboxId, 1);
|
2009-08-11 20:41:13 +00:00
|
|
|
} else if (failures > MAX_PING_FAILURES) {
|
2009-08-05 15:41:16 +00:00
|
|
|
// Change all push/ping boxes (except account) to 5 minute sync
|
2009-08-10 18:25:09 +00:00
|
|
|
pushFallback(mailboxId);
|
2009-08-05 15:41:16 +00:00
|
|
|
return;
|
|
|
|
} else {
|
|
|
|
pingFailureMap.put(mailboxId, failures + 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-07-05 19:54:49 +00:00
|
|
|
if (canPushCount++ == 0) {
|
|
|
|
// Initialize the Ping command
|
2009-08-11 20:41:13 +00:00
|
|
|
s.start(Tags.PING_PING)
|
2009-08-11 22:19:50 +00:00
|
|
|
.data(Tags.PING_HEARTBEAT_INTERVAL,
|
|
|
|
Integer.toString(mPingHeartbeat))
|
2009-07-31 01:17:52 +00:00
|
|
|
.start(Tags.PING_FOLDERS);
|
2009-07-05 19:54:49 +00:00
|
|
|
}
|
|
|
|
// When we're ready for Calendar/Contacts, we will check folder type
|
|
|
|
// TODO Save Calendar and Contacts!! Mark as not visible!
|
|
|
|
String folderClass = getTargetCollectionClassFromCursor(c);
|
2009-07-31 01:17:52 +00:00
|
|
|
s.start(Tags.PING_FOLDER)
|
|
|
|
.data(Tags.PING_ID, c.getString(Mailbox.CONTENT_SERVER_ID_COLUMN))
|
|
|
|
.data(Tags.PING_CLASS, folderClass)
|
|
|
|
.end();
|
2009-08-09 04:58:54 +00:00
|
|
|
userLog("Ping ready for: " + folderClass + ", " + mailboxName + " (" +
|
|
|
|
c.getString(Mailbox.CONTENT_SERVER_ID_COLUMN) + ')');
|
2009-08-02 22:33:22 +00:00
|
|
|
pushBoxes.add(new Mailbox().restore(c));
|
2009-08-09 04:58:54 +00:00
|
|
|
} else if (pingStatus == SyncManager.PING_STATUS_RUNNING ||
|
|
|
|
pingStatus == SyncManager.PING_STATUS_WAITING) {
|
|
|
|
userLog(mailboxName + " not ready for ping");
|
|
|
|
} else if (pingStatus == SyncManager.PING_STATUS_UNABLE) {
|
|
|
|
pushCount--;
|
|
|
|
userLog(mailboxName + " in error state; ignore");
|
|
|
|
continue;
|
2009-07-05 19:54:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
c.close();
|
|
|
|
}
|
|
|
|
|
2009-07-16 18:42:10 +00:00
|
|
|
if (canPushCount > 0 && (canPushCount == pushCount)) {
|
2009-07-05 19:54:49 +00:00
|
|
|
// If we have some number that are ready for push, send Ping to the server
|
2009-07-31 01:17:52 +00:00
|
|
|
s.end().end().done();
|
|
|
|
|
2009-07-15 22:08:53 +00:00
|
|
|
Thread.currentThread().setName(mAccount.mDisplayName + ": Ping");
|
2009-08-11 22:19:50 +00:00
|
|
|
userLog("Sending ping, timeout: " + mPingHeartbeat + "s");
|
2009-08-09 01:59:33 +00:00
|
|
|
|
2009-08-11 22:19:50 +00:00
|
|
|
// Sleep for the heartbeat time plus a little bit of slack
|
|
|
|
SyncManager.runAsleep(mMailboxId, (mPingHeartbeat+15)*SECONDS);
|
2009-08-10 18:25:09 +00:00
|
|
|
long time = System.currentTimeMillis();
|
2009-08-09 01:59:33 +00:00
|
|
|
HttpResponse res = sendHttpClientPost(PING_COMMAND, s.toByteArray());
|
|
|
|
SyncManager.runAwake(mMailboxId);
|
2009-08-08 20:26:03 +00:00
|
|
|
|
2009-08-02 22:29:41 +00:00
|
|
|
// Don't send request if we've been asked to stop
|
|
|
|
if (mStop) return;
|
2009-08-08 20:26:03 +00:00
|
|
|
code = res.getStatusLine().getStatusCode();
|
2009-08-02 22:33:22 +00:00
|
|
|
|
2009-08-10 18:25:09 +00:00
|
|
|
// Get elapsed time
|
|
|
|
time = System.currentTimeMillis() - time;
|
|
|
|
userLog("Ping response: " + code + " in " + time + "ms");
|
|
|
|
|
2009-08-02 22:29:41 +00:00
|
|
|
// Return immediately if we've been asked to stop
|
2009-07-24 16:11:17 +00:00
|
|
|
if (mStop) {
|
2009-08-02 22:33:22 +00:00
|
|
|
userLog("Stopping pingLoop");
|
2009-07-24 16:11:17 +00:00
|
|
|
return;
|
|
|
|
}
|
2009-08-02 22:33:22 +00:00
|
|
|
|
2009-07-05 19:54:49 +00:00
|
|
|
if (code == HttpURLConnection.HTTP_OK) {
|
2009-08-08 20:26:03 +00:00
|
|
|
HttpEntity e = res.getEntity();
|
|
|
|
int len = (int)e.getContentLength();
|
|
|
|
InputStream is = res.getEntity().getContent();
|
|
|
|
if (len > 0) {
|
|
|
|
parsePingResult(is, mContentResolver);
|
2009-07-05 19:54:49 +00:00
|
|
|
} else {
|
|
|
|
throw new IOException();
|
|
|
|
}
|
2009-08-02 22:33:22 +00:00
|
|
|
} else if (isAuthError(code)) {
|
2009-07-05 19:54:49 +00:00
|
|
|
mExitStatus = AbstractSyncService.EXIT_LOGIN_FAILURE;
|
|
|
|
userLog("Authorization error during Ping: " + code);
|
|
|
|
throw new IOException();
|
|
|
|
}
|
|
|
|
} else if (pushCount > 0) {
|
|
|
|
// If we want to Ping, but can't just yet, wait 10 seconds and try again
|
2009-07-16 18:42:10 +00:00
|
|
|
userLog("pingLoop waiting for " + (pushCount - canPushCount) + " box(es)");
|
2009-08-10 18:25:09 +00:00
|
|
|
sleep(10*SECONDS);
|
2009-07-05 19:54:49 +00:00
|
|
|
} else {
|
2009-08-10 18:25:09 +00:00
|
|
|
// We've got nothing to do, so we'll check again in 30 minutes at which time
|
|
|
|
// we'll update the folder list.
|
|
|
|
SyncManager.runAsleep(mMailboxId, 30*MINUTES);
|
|
|
|
sleep(30*MINUTES);
|
2009-08-09 04:58:54 +00:00
|
|
|
SyncManager.runAwake(mMailboxId);
|
2009-07-05 19:54:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void sleep(long ms) {
|
|
|
|
try {
|
|
|
|
Thread.sleep(ms);
|
|
|
|
} catch (InterruptedException e) {
|
|
|
|
// Doesn't matter whether we stop early; it's the thought that counts
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-08-08 20:26:03 +00:00
|
|
|
private int parsePingResult(InputStream is, ContentResolver cr)
|
2009-07-05 19:54:49 +00:00
|
|
|
throws IOException, StaleFolderListException {
|
2009-08-08 20:26:03 +00:00
|
|
|
PingParser pp = new PingParser(is, this);
|
2009-07-05 19:54:49 +00:00
|
|
|
if (pp.parse()) {
|
|
|
|
// True indicates some mailboxes need syncing...
|
|
|
|
// syncList has the serverId's of the mailboxes...
|
|
|
|
mBindArguments[0] = Long.toString(mAccount.mId);
|
|
|
|
ArrayList<String> syncList = pp.getSyncList();
|
|
|
|
for (String serverId: syncList) {
|
2009-07-15 22:08:53 +00:00
|
|
|
mBindArguments[1] = serverId;
|
2009-07-05 19:54:49 +00:00
|
|
|
Cursor c = cr.query(Mailbox.CONTENT_URI, Mailbox.CONTENT_PROJECTION,
|
|
|
|
WHERE_ACCOUNT_KEY_AND_SERVER_ID, mBindArguments, null);
|
|
|
|
try {
|
|
|
|
if (c.moveToFirst()) {
|
2009-08-02 22:29:41 +00:00
|
|
|
SyncManager.startManualSync(c.getLong(Mailbox.CONTENT_ID_COLUMN),
|
2009-08-05 15:41:16 +00:00
|
|
|
SyncManager.SYNC_PING, null);
|
2009-07-05 19:54:49 +00:00
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
c.close();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2009-07-31 01:17:52 +00:00
|
|
|
return pp.getSyncList().size();
|
2009-07-05 19:54:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
ByteArrayInputStream readResponse(HttpURLConnection uc) throws IOException {
|
|
|
|
String encoding = uc.getHeaderField("Transfer-Encoding");
|
|
|
|
if (encoding == null) {
|
|
|
|
int len = uc.getHeaderFieldInt("Content-Length", 0);
|
|
|
|
if (len > 0) {
|
|
|
|
InputStream in = uc.getInputStream();
|
|
|
|
byte[] bytes = new byte[len];
|
|
|
|
int remain = len;
|
|
|
|
int offs = 0;
|
|
|
|
while (remain > 0) {
|
|
|
|
int read = in.read(bytes, offs, remain);
|
|
|
|
remain -= read;
|
|
|
|
offs += read;
|
|
|
|
}
|
|
|
|
return new ByteArrayInputStream(bytes);
|
|
|
|
}
|
|
|
|
} else if (encoding.equalsIgnoreCase("chunked")) {
|
|
|
|
// TODO We don't handle this yet
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
String readResponseString(HttpURLConnection uc) throws IOException {
|
|
|
|
String encoding = uc.getHeaderField("Transfer-Encoding");
|
|
|
|
if (encoding == null) {
|
|
|
|
int len = uc.getHeaderFieldInt("Content-Length", 0);
|
|
|
|
if (len > 0) {
|
|
|
|
InputStream in = uc.getInputStream();
|
|
|
|
byte[] bytes = new byte[len];
|
|
|
|
int remain = len;
|
|
|
|
int offs = 0;
|
|
|
|
while (remain > 0) {
|
|
|
|
int read = in.read(bytes, offs, remain);
|
|
|
|
remain -= read;
|
|
|
|
offs += read;
|
|
|
|
}
|
|
|
|
return new String(bytes);
|
|
|
|
}
|
|
|
|
} else if (encoding.equalsIgnoreCase("chunked")) {
|
|
|
|
// TODO We don't handle this yet
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2009-08-02 22:33:22 +00:00
|
|
|
private String getFilterType() {
|
|
|
|
String filter = Eas.FILTER_1_WEEK;
|
|
|
|
switch (mAccount.mSyncLookback) {
|
|
|
|
case com.android.email.Account.SYNC_WINDOW_1_DAY: {
|
|
|
|
filter = Eas.FILTER_1_DAY;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case com.android.email.Account.SYNC_WINDOW_3_DAYS: {
|
|
|
|
filter = Eas.FILTER_3_DAYS;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case com.android.email.Account.SYNC_WINDOW_1_WEEK: {
|
|
|
|
filter = Eas.FILTER_1_WEEK;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case com.android.email.Account.SYNC_WINDOW_2_WEEKS: {
|
|
|
|
filter = Eas.FILTER_2_WEEKS;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case com.android.email.Account.SYNC_WINDOW_1_MONTH: {
|
|
|
|
filter = Eas.FILTER_1_MONTH;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case com.android.email.Account.SYNC_WINDOW_ALL: {
|
|
|
|
filter = Eas.FILTER_ALL;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return filter;
|
|
|
|
}
|
|
|
|
|
2009-07-05 19:54:49 +00:00
|
|
|
/**
|
|
|
|
* Common code to sync E+PIM data
|
|
|
|
*
|
|
|
|
* @param target, an EasMailbox, EasContacts, or EasCalendar object
|
|
|
|
*/
|
2009-07-31 01:17:52 +00:00
|
|
|
public void sync(AbstractSyncAdapter target) throws IOException {
|
2009-07-05 19:54:49 +00:00
|
|
|
mTarget = target;
|
|
|
|
Mailbox mailbox = target.mMailbox;
|
|
|
|
|
|
|
|
boolean moreAvailable = true;
|
|
|
|
while (!mStop && moreAvailable) {
|
|
|
|
waitForConnectivity();
|
|
|
|
|
2009-07-15 22:08:53 +00:00
|
|
|
while (true) {
|
|
|
|
PartRequest req = null;
|
|
|
|
synchronized (mPartRequests) {
|
|
|
|
if (mPartRequests.isEmpty()) {
|
|
|
|
break;
|
|
|
|
} else {
|
|
|
|
req = mPartRequests.get(0);
|
|
|
|
}
|
|
|
|
}
|
2009-07-24 16:11:17 +00:00
|
|
|
getAttachment(req);
|
2009-07-15 22:08:53 +00:00
|
|
|
synchronized(mPartRequests) {
|
|
|
|
mPartRequests.remove(req);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-07-31 01:17:52 +00:00
|
|
|
Serializer s = new Serializer();
|
2009-07-05 19:54:49 +00:00
|
|
|
if (mailbox.mSyncKey == null) {
|
|
|
|
userLog("Mailbox syncKey RESET");
|
|
|
|
mailbox.mSyncKey = "0";
|
|
|
|
}
|
|
|
|
String className = target.getCollectionName();
|
|
|
|
userLog("Sending " + className + " syncKey: " + mailbox.mSyncKey);
|
2009-07-31 01:17:52 +00:00
|
|
|
s.start(Tags.SYNC_SYNC)
|
|
|
|
.start(Tags.SYNC_COLLECTIONS)
|
|
|
|
.start(Tags.SYNC_COLLECTION)
|
|
|
|
.data(Tags.SYNC_CLASS, className)
|
|
|
|
.data(Tags.SYNC_SYNC_KEY, mailbox.mSyncKey)
|
|
|
|
.data(Tags.SYNC_COLLECTION_ID, mailbox.mServerId)
|
|
|
|
.tag(Tags.SYNC_DELETES_AS_MOVES);
|
2009-07-05 19:54:49 +00:00
|
|
|
|
|
|
|
// EAS doesn't like GetChanges if the syncKey is "0"; not documented
|
|
|
|
if (!mailbox.mSyncKey.equals("0")) {
|
2009-07-31 01:17:52 +00:00
|
|
|
s.tag(Tags.SYNC_GET_CHANGES);
|
2009-07-05 19:54:49 +00:00
|
|
|
}
|
2009-08-07 18:43:32 +00:00
|
|
|
s.data(Tags.SYNC_WINDOW_SIZE,
|
|
|
|
className.equals("Email") ? EMAIL_WINDOW_SIZE : PIM_WINDOW_SIZE);
|
2009-07-05 19:54:49 +00:00
|
|
|
boolean options = false;
|
|
|
|
if (!className.equals("Contacts")) {
|
|
|
|
// Set the lookback appropriately (EAS calls this a "filter")
|
2009-08-02 22:33:22 +00:00
|
|
|
s.start(Tags.SYNC_OPTIONS).data(Tags.SYNC_FILTER_TYPE, getFilterType());
|
2009-07-31 01:17:52 +00:00
|
|
|
// No truncation in this version
|
|
|
|
//if (mProtocolVersionDouble < 12.0) {
|
|
|
|
// s.data(Tags.SYNC_TRUNCATION, "7");
|
|
|
|
//}
|
2009-07-17 23:29:35 +00:00
|
|
|
options = true;
|
2009-07-05 19:54:49 +00:00
|
|
|
}
|
2009-07-17 23:29:35 +00:00
|
|
|
if (mProtocolVersionDouble >= 12.0) {
|
2009-07-05 19:54:49 +00:00
|
|
|
if (!options) {
|
|
|
|
options = true;
|
2009-07-31 01:17:52 +00:00
|
|
|
s.start(Tags.SYNC_OPTIONS);
|
2009-07-05 19:54:49 +00:00
|
|
|
}
|
2009-07-31 01:17:52 +00:00
|
|
|
s.start(Tags.BASE_BODY_PREFERENCE)
|
2009-08-02 22:33:22 +00:00
|
|
|
// HTML for email; plain text for everything else
|
2009-08-10 18:25:09 +00:00
|
|
|
.data(Tags.BASE_TYPE, (className.equals("Email") ? Eas.BODY_PREFERENCE_HTML
|
2009-08-02 22:33:22 +00:00
|
|
|
: Eas.BODY_PREFERENCE_TEXT))
|
|
|
|
// No truncation in this version
|
|
|
|
//.data(Tags.BASE_TRUNCATION_SIZE, Eas.DEFAULT_BODY_TRUNCATION_SIZE)
|
2009-07-31 01:17:52 +00:00
|
|
|
.end();
|
2009-07-05 19:54:49 +00:00
|
|
|
}
|
|
|
|
if (options) {
|
2009-07-31 01:17:52 +00:00
|
|
|
s.end();
|
2009-07-05 19:54:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Send our changes up to the server
|
|
|
|
target.sendLocalChanges(s, this);
|
|
|
|
|
2009-07-31 01:17:52 +00:00
|
|
|
s.end().end().end().done();
|
2009-08-10 18:25:09 +00:00
|
|
|
userLog("Sync, deviceId = " + mDeviceId);
|
2009-08-08 20:26:03 +00:00
|
|
|
HttpResponse resp = sendHttpClientPost("Sync", s.toByteArray());
|
|
|
|
int code = resp.getStatusLine().getStatusCode();
|
2009-07-05 19:54:49 +00:00
|
|
|
if (code == HttpURLConnection.HTTP_OK) {
|
2009-08-08 20:26:03 +00:00
|
|
|
InputStream is = resp.getEntity().getContent();
|
2009-07-05 19:54:49 +00:00
|
|
|
if (is != null) {
|
|
|
|
moreAvailable = target.parse(is, this);
|
2009-07-15 00:21:13 +00:00
|
|
|
target.cleanup(this);
|
2009-07-05 19:54:49 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
userLog("Sync response error: " + code);
|
2009-08-02 22:33:22 +00:00
|
|
|
if (isAuthError(code)) {
|
2009-07-05 19:54:49 +00:00
|
|
|
mExitStatus = AbstractSyncService.EXIT_LOGIN_FAILURE;
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* (non-Javadoc)
|
|
|
|
* @see java.lang.Runnable#run()
|
|
|
|
*/
|
|
|
|
public void run() {
|
|
|
|
mThread = Thread.currentThread();
|
|
|
|
TAG = mThread.getName();
|
2009-08-08 05:23:10 +00:00
|
|
|
|
2009-07-05 19:54:49 +00:00
|
|
|
HostAuth ha = HostAuth.restoreHostAuthWithId(mContext, mAccount.mHostAuthKeyRecv);
|
|
|
|
mHostAddress = ha.mAddress;
|
|
|
|
mUserName = ha.mLogin;
|
|
|
|
mPassword = ha.mPassword;
|
|
|
|
|
2009-07-30 20:08:06 +00:00
|
|
|
try {
|
|
|
|
SyncManager.callback().syncMailboxStatus(mMailboxId, EmailServiceStatus.IN_PROGRESS, 0);
|
|
|
|
} catch (RemoteException e1) {
|
|
|
|
// Don't care if this fails
|
|
|
|
}
|
|
|
|
|
2009-07-21 07:02:39 +00:00
|
|
|
// Make sure account and mailbox are always the latest from the database
|
|
|
|
mAccount = Account.restoreAccountWithId(mContext, mAccount.mId);
|
|
|
|
mMailbox = Mailbox.restoreMailboxWithId(mContext, mMailbox.mId);
|
2009-07-31 04:17:19 +00:00
|
|
|
// Whether or not we're the account mailbox
|
2009-07-05 19:54:49 +00:00
|
|
|
try {
|
2009-08-08 05:23:10 +00:00
|
|
|
mDeviceId = SyncManager.getDeviceId();
|
2009-07-27 17:24:58 +00:00
|
|
|
if (mMailbox == null || mAccount == null) {
|
|
|
|
return;
|
2009-07-31 01:17:52 +00:00
|
|
|
} else if (mMailbox.mType == Mailbox.TYPE_EAS_ACCOUNT_MAILBOX) {
|
2009-07-31 04:17:19 +00:00
|
|
|
runAccountMailbox();
|
2009-07-05 19:54:49 +00:00
|
|
|
} else {
|
2009-07-31 01:17:52 +00:00
|
|
|
AbstractSyncAdapter target;
|
2009-07-17 23:29:35 +00:00
|
|
|
mAccount = Account.restoreAccountWithId(mContext, mAccount.mId);
|
|
|
|
mProtocolVersion = mAccount.mProtocolVersion;
|
|
|
|
mProtocolVersionDouble = Double.parseDouble(mProtocolVersion);
|
2009-07-05 19:54:49 +00:00
|
|
|
if (mMailbox.mType == Mailbox.TYPE_CONTACTS)
|
2009-07-31 01:17:52 +00:00
|
|
|
target = new ContactsSyncAdapter(mMailbox, this);
|
2009-07-05 19:54:49 +00:00
|
|
|
else {
|
2009-07-31 01:17:52 +00:00
|
|
|
target = new EmailSyncAdapter(mMailbox, this);
|
2009-07-05 19:54:49 +00:00
|
|
|
}
|
|
|
|
// We loop here because someone might have put a request in while we were syncing
|
|
|
|
// and we've missed that opportunity...
|
|
|
|
do {
|
|
|
|
if (mRequestTime != 0) {
|
|
|
|
userLog("Looping for user request...");
|
|
|
|
mRequestTime = 0;
|
|
|
|
}
|
|
|
|
sync(target);
|
|
|
|
} while (mRequestTime != 0);
|
|
|
|
}
|
|
|
|
mExitStatus = EXIT_DONE;
|
|
|
|
} catch (IOException e) {
|
2009-08-11 16:15:12 +00:00
|
|
|
String message = e.getMessage();
|
|
|
|
userLog("Caught IOException: " + ((message == null) ? "" : message));
|
2009-07-05 19:54:49 +00:00
|
|
|
mExitStatus = EXIT_IO_ERROR;
|
|
|
|
} catch (Exception e) {
|
2009-08-09 04:58:54 +00:00
|
|
|
Log.e(TAG, "Uncaught exception in EasSyncService", e);
|
2009-07-05 19:54:49 +00:00
|
|
|
} finally {
|
2009-08-02 22:29:41 +00:00
|
|
|
if (!mStop) {
|
|
|
|
userLog(mMailbox.mDisplayName + ": sync finished");
|
|
|
|
SyncManager.done(this);
|
|
|
|
// If this is the account mailbox, wake up SyncManager
|
|
|
|
// Because this box has a "push" interval, it will be restarted immediately
|
|
|
|
// which will cause the folder list to be reloaded...
|
2009-08-09 04:21:54 +00:00
|
|
|
int status;
|
|
|
|
switch (mExitStatus) {
|
|
|
|
case EXIT_IO_ERROR:
|
|
|
|
status = EmailServiceStatus.CONNECTION_ERROR;
|
|
|
|
break;
|
|
|
|
case EXIT_DONE:
|
|
|
|
status = EmailServiceStatus.SUCCESS;
|
|
|
|
break;
|
|
|
|
case EXIT_LOGIN_FAILURE:
|
|
|
|
status = EmailServiceStatus.LOGIN_FAILED;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
status = EmailServiceStatus.REMOTE_EXCEPTION;
|
2009-08-09 04:58:54 +00:00
|
|
|
errorLog("Sync ended due to an exception.");
|
2009-08-09 04:21:54 +00:00
|
|
|
break;
|
|
|
|
}
|
2009-08-10 18:25:09 +00:00
|
|
|
|
2009-08-02 22:29:41 +00:00
|
|
|
try {
|
|
|
|
SyncManager.callback().syncMailboxStatus(mMailboxId, status, 0);
|
|
|
|
} catch (RemoteException e1) {
|
|
|
|
// Don't care if this fails
|
|
|
|
}
|
2009-08-09 04:21:54 +00:00
|
|
|
|
|
|
|
// Save the sync time and status
|
|
|
|
ContentValues cv = new ContentValues();
|
|
|
|
cv.put(Mailbox.SYNC_TIME, System.currentTimeMillis());
|
|
|
|
String s = "S" + mSyncReason + ':' + status + ':' + mChangeCount;
|
|
|
|
cv.put(Mailbox.SYNC_STATUS, s);
|
|
|
|
mContentResolver.update(ContentUris
|
|
|
|
.withAppendedId(Mailbox.CONTENT_URI, mMailboxId), cv, null, null);
|
2009-08-02 22:29:41 +00:00
|
|
|
} else {
|
|
|
|
userLog(mMailbox.mDisplayName + ": stopped thread finished.");
|
2009-07-30 20:08:06 +00:00
|
|
|
}
|
2009-08-08 05:23:10 +00:00
|
|
|
|
2009-08-10 18:25:09 +00:00
|
|
|
// Make sure SyncManager knows about this
|
|
|
|
SyncManager.kick("sync finished");
|
2009-08-08 05:23:10 +00:00
|
|
|
}
|
2009-07-05 19:54:49 +00:00
|
|
|
}
|
|
|
|
}
|