Merge change 21281 into eclair
* changes: Implement adaptive ping timeout; release wake lock waiting for connectivity
This commit is contained in:
commit
10e8ecef2f
@ -39,7 +39,7 @@ import java.util.ArrayList;
|
||||
*/
|
||||
public abstract class AbstractSyncService implements Runnable {
|
||||
|
||||
public String TAG = "ProtocolService";
|
||||
public String TAG = "AbstractSyncService";
|
||||
|
||||
public static final String SUMMARY_PROTOCOL = "_SUMMARY_";
|
||||
public static final String SYNCED_PROTOCOL = "_SYNCING_";
|
||||
@ -59,18 +59,13 @@ public abstract class AbstractSyncService implements Runnable {
|
||||
public static final int EXIT_LOGIN_FAILURE = 2;
|
||||
public static final int EXIT_EXCEPTION = 3;
|
||||
|
||||
// Making SSL connections is so slow that I'd prefer that only one be
|
||||
// executed at a time
|
||||
// Kindly subclasses will synchronize on this before making an SSL
|
||||
// connection
|
||||
public static Object sslGovernorToken = new Object();
|
||||
public Mailbox mMailbox;
|
||||
protected long mMailboxId;
|
||||
protected Thread mThread;
|
||||
protected int mExitStatus = EXIT_EXCEPTION;
|
||||
protected String mMailboxName;
|
||||
public Account mAccount;
|
||||
protected Context mContext;
|
||||
public Context mContext;
|
||||
public int mChangeCount = 0;
|
||||
public int mSyncReason = 0;
|
||||
protected volatile boolean mStop = false;
|
||||
@ -108,16 +103,6 @@ public abstract class AbstractSyncService implements Runnable {
|
||||
public abstract void validateAccount(String host, String userName, String password, int port,
|
||||
boolean ssl, Context context) throws MessagingException;
|
||||
|
||||
/**
|
||||
* Sent by SyncManager to determine the state of a running sync This is
|
||||
* currently unused
|
||||
*
|
||||
* @return status code
|
||||
*/
|
||||
public int getSyncStatus() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public AbstractSyncService(Context _context, Mailbox _mailbox) {
|
||||
mContext = _context;
|
||||
mMailbox = _mailbox;
|
||||
@ -255,46 +240,31 @@ public abstract class AbstractSyncService implements Runnable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements a delay until there is some kind of network connectivity available. This method
|
||||
* may be supplanted by functionality in SyncManager.
|
||||
* Waits for up to 10 seconds for network connectivity; returns whether or not there is
|
||||
* network connectivity.
|
||||
*
|
||||
* @return the type of network connected to
|
||||
* @return whether there is network connectivity
|
||||
*/
|
||||
public int waitForConnectivity() {
|
||||
ConnectivityManager cm = (ConnectivityManager)mContext
|
||||
.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
while (true) {
|
||||
public boolean hasConnectivity() {
|
||||
ConnectivityManager cm =
|
||||
(ConnectivityManager)mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
int tries = 0;
|
||||
while (tries++ < 1) {
|
||||
NetworkInfo info = cm.getActiveNetworkInfo();
|
||||
if (info != null && info.isConnected()) {
|
||||
DetailedState state = info.getDetailedState();
|
||||
if (state == DetailedState.CONNECTED) {
|
||||
return info.getType();
|
||||
} else {
|
||||
// TODO Happens sometimes; find out why...
|
||||
userLog("Not quite connected? Pause 1 second");
|
||||
return true;
|
||||
}
|
||||
pause(1000);
|
||||
} else {
|
||||
userLog("Not connected; waiting 15 seconds");
|
||||
pause(NETWORK_WAIT);
|
||||
}
|
||||
try {
|
||||
Thread.sleep(10*SECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method to generate a small wait
|
||||
*
|
||||
* @param ms time to wait in milliseconds
|
||||
*/
|
||||
private void pause(int ms) {
|
||||
try {
|
||||
Thread.sleep(ms);
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
}
|
||||
|
||||
// What's below here is temporary
|
||||
|
||||
/**
|
||||
* PartRequest handling (common functionality)
|
||||
* Can be overridden if desired, but IMAP/EAS both use the next three methods as-is
|
||||
@ -323,13 +293,10 @@ public abstract class AbstractSyncService implements Runnable {
|
||||
return null;
|
||||
}
|
||||
|
||||
// CancelPartRequest is sent in response to user input to stop a request
|
||||
// (attachment load at this point)
|
||||
// that is in progress. This will almost certainly require code overriding
|
||||
// the base functionality, as
|
||||
// sockets may need to be closed, etc. and this functionality will be
|
||||
// service dependent. This returns
|
||||
// the canceled PartRequest or null
|
||||
// cancelPartRequest is sent in response to user input to stop an attachment load
|
||||
// that is in progress. This will almost certainly require code overriding the base
|
||||
// functionality, as sockets may need to be closed, etc. and this functionality will be
|
||||
// service dependent. This returns the canceled PartRequest or null
|
||||
public PartRequest cancelPartRequest(long emailId, String part) {
|
||||
synchronized (mPartRequests) {
|
||||
PartRequest p = null;
|
||||
|
@ -25,6 +25,7 @@ import com.android.email.provider.EmailContent.MessageColumns;
|
||||
import com.android.email.provider.EmailContent.SyncColumns;
|
||||
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.HttpStatus;
|
||||
import org.apache.http.entity.InputStreamEntity;
|
||||
|
||||
import android.content.ContentUris;
|
||||
@ -36,7 +37,6 @@ import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.HttpURLConnection;
|
||||
|
||||
public class EasOutboxService extends EasSyncService {
|
||||
|
||||
@ -74,7 +74,7 @@ public class EasOutboxService extends EasSyncService {
|
||||
sendHttpClientPost("SendMail&SaveInSent=T", inputEntity);
|
||||
inputStream.close();
|
||||
int code = resp.getStatusLine().getStatusCode();
|
||||
if (code == HttpURLConnection.HTTP_OK) {
|
||||
if (code == HttpStatus.SC_OK) {
|
||||
userLog("Deleting message...");
|
||||
mContext.getContentResolver().delete(ContentUris.withAppendedId(
|
||||
Message.CONTENT_URI, msgId), null, null);
|
||||
|
@ -19,6 +19,7 @@ package com.android.exchange;
|
||||
|
||||
import com.android.email.R;
|
||||
import com.android.email.activity.AccountFolderList;
|
||||
import com.android.email.codec.binary.Base64;
|
||||
import com.android.email.mail.AuthenticationFailedException;
|
||||
import com.android.email.mail.MessagingException;
|
||||
import com.android.email.provider.EmailContent.Account;
|
||||
@ -37,11 +38,11 @@ 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;
|
||||
import com.android.exchange.utility.Base64;
|
||||
|
||||
import org.apache.http.Header;
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.HttpStatus;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.methods.HttpOptions;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
@ -61,16 +62,13 @@ import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
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;
|
||||
@ -92,20 +90,41 @@ public class EasSyncService extends AbstractSyncService {
|
||||
private static final String WHERE_PUSH_HOLD_NOT_ACCOUNT_MAILBOX =
|
||||
MailboxColumns.ACCOUNT_KEY + "=? and " + MailboxColumns.SYNC_INTERVAL +
|
||||
'=' + Mailbox.CHECK_INTERVAL_PUSH_HOLD;
|
||||
|
||||
private static final String[] SYNC_STATUS_PROJECTION =
|
||||
new String[] {MailboxColumns.ID, MailboxColumns.SYNC_STATUS};
|
||||
static private final int CHUNK_SIZE = 16*1024;
|
||||
|
||||
static private final String PING_COMMAND = "Ping";
|
||||
static private final int COMMAND_TIMEOUT = 20*SECONDS;
|
||||
static private final int PING_COMMAND_TIMEOUT = 20*MINUTES;
|
||||
|
||||
// For mobile, we use a 5 minute timeout (less a few seconds)
|
||||
static private final int PING_HEARTBEAT_MOBILE = 295;
|
||||
// For wifi, we use a 15 minute timeout
|
||||
static private final int PING_HEARTBEAT_WIFI = 900;
|
||||
/**
|
||||
* We start with an 8 minute timeout, and increase/decrease by 3 minutes at a time. There's
|
||||
* no point having a timeout shorter than 5 minutes, I think; at that point, we can just let
|
||||
* the ping exception out. The maximum I use is 17 minutes, which is really an empirical
|
||||
* choice; too long and we risk silent connection loss and loss of push for that period. Too
|
||||
* short and we lose efficiency/battery life.
|
||||
*
|
||||
* If we ever have to drop the ping timeout, we'll never increase it again. There's no point
|
||||
* going into hysteresis; the NAT timeout isn't going to change without a change in connection,
|
||||
* which will cause the sync service to be restarted at the starting heartbeat and going through
|
||||
* the process again.
|
||||
*
|
||||
* One issue we've got is that there's no foolproof way of knowing that we hit a NAT timeout,
|
||||
* rather than some other disconnect. In my experience, we see a particular IOException, but
|
||||
* this might be too fragile an indicator, so we'll have to consider changes to this approach.
|
||||
*/
|
||||
static private final int PING_MINUTES = 60; // in seconds
|
||||
static private final int PING_FUDGE_LOW = 10;
|
||||
static private final int PING_STARTING_HEARTBEAT = (8*PING_MINUTES)-PING_FUDGE_LOW;
|
||||
static private final int PING_MIN_HEARTBEAT = (5*PING_MINUTES)-PING_FUDGE_LOW;
|
||||
static private final int PING_MAX_HEARTBEAT = (17*PING_MINUTES)-PING_FUDGE_LOW;
|
||||
static private final int PING_HEARTBEAT_INCREMENT = 3*PING_MINUTES;
|
||||
|
||||
static private final int PROTOCOL_PING_STATUS_COMPLETED = 1;
|
||||
static private final int PROTOCOL_PING_STATUS_CHANGES_FOUND = 2;
|
||||
|
||||
// Fallbacks (in minutes) for ping loop failures
|
||||
static private final int MAX_PING_FAILURES = 2;
|
||||
static private final int MAX_PING_FAILURES = 1;
|
||||
static private final int PING_FALLBACK_INBOX = 5;
|
||||
static private final int PING_FALLBACK_PIM = 30;
|
||||
|
||||
@ -114,27 +133,25 @@ public class EasSyncService extends AbstractSyncService {
|
||||
public Double mProtocolVersionDouble;
|
||||
private String mDeviceId = null;
|
||||
private String mDeviceType = "Android";
|
||||
AbstractSyncAdapter mTarget;
|
||||
String mAuthString = null;
|
||||
String mCmdString = null;
|
||||
private String mAuthString = null;
|
||||
private 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;
|
||||
private boolean mSsl = true;
|
||||
public ContentResolver mContentResolver;
|
||||
String[] mBindArguments = new String[2];
|
||||
InputStream mPendingPartInputStream = null;
|
||||
HttpPost mPendingPost = null;
|
||||
int mNetworkType;
|
||||
int mPingHeartbeat;
|
||||
private String[] mBindArguments = new String[2];
|
||||
private ArrayList<String> mPingChangeList;
|
||||
private HttpPost mPendingPost = null;
|
||||
// The ping time (in seconds)
|
||||
private int mPingHeartbeat = PING_STARTING_HEARTBEAT;
|
||||
// The longest successful ping heartbeat
|
||||
private int mPingHighWaterMark = 0;
|
||||
// Whether we've ever lowered the heartbeat
|
||||
private boolean mPingHeartbeatDropped = false;
|
||||
|
||||
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;
|
||||
@ -150,10 +167,12 @@ public class EasSyncService extends AbstractSyncService {
|
||||
|
||||
@Override
|
||||
public void ping() {
|
||||
userLog("We've been pinged!");
|
||||
Object synchronizer = getSynchronizer();
|
||||
synchronized (synchronizer) {
|
||||
synchronizer.notify();
|
||||
userLog("Alarm ping received!");
|
||||
synchronized(getSynchronizer()) {
|
||||
if (mPendingPost != null) {
|
||||
userLog("Aborting pending POST!");
|
||||
mPendingPost.abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -167,15 +186,16 @@ public class EasSyncService extends AbstractSyncService {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSyncStatus() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether an HTTP code represents an authentication error
|
||||
* @param code the HTTP code returned by the server
|
||||
* @return whether or not the code represents an authentication error
|
||||
*/
|
||||
private boolean isAuthError(int code) {
|
||||
return (code == HttpURLConnection.HTTP_UNAUTHORIZED
|
||||
|| code == HttpURLConnection.HTTP_FORBIDDEN
|
||||
|| code == HttpURLConnection.HTTP_INTERNAL_ERROR);
|
||||
return (code == HttpStatus.SC_UNAUTHORIZED
|
||||
|| code == HttpStatus.SC_FORBIDDEN
|
||||
// || code == HttpStatus.SC_INTERNAL_SERVER_ERROR
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -193,7 +213,7 @@ public class EasSyncService extends AbstractSyncService {
|
||||
HttpResponse resp = svc.sendHttpClientOptions();
|
||||
int code = resp.getStatusLine().getStatusCode();
|
||||
userLog("Validation (OPTIONS) response: " + code);
|
||||
if (code == HttpURLConnection.HTTP_OK) {
|
||||
if (code == HttpStatus.SC_OK) {
|
||||
// No exception means successful validation
|
||||
userLog("Validation successful");
|
||||
return;
|
||||
@ -278,7 +298,7 @@ public class EasSyncService extends AbstractSyncService {
|
||||
|
||||
HttpResponse res = client.execute(method);
|
||||
int status = res.getStatusLine().getStatusCode();
|
||||
if (status == HttpURLConnection.HTTP_OK) {
|
||||
if (status == HttpStatus.SC_OK) {
|
||||
HttpEntity e = res.getEntity();
|
||||
int len = (int)e.getContentLength();
|
||||
String type = e.getContentType().getValue();
|
||||
@ -296,7 +316,6 @@ public class EasSyncService extends AbstractSyncService {
|
||||
if (len > 0) {
|
||||
try {
|
||||
mPendingPartRequest = req;
|
||||
mPendingPartInputStream = is;
|
||||
byte[] bytes = new byte[CHUNK_SIZE];
|
||||
int length = len;
|
||||
while (len > 0) {
|
||||
@ -309,7 +328,6 @@ public class EasSyncService extends AbstractSyncService {
|
||||
}
|
||||
} finally {
|
||||
mPendingPartRequest = null;
|
||||
mPendingPartInputStream = null;
|
||||
}
|
||||
}
|
||||
os.flush();
|
||||
@ -338,7 +356,7 @@ public class EasSyncService extends AbstractSyncService {
|
||||
String safeUserName = URLEncoder.encode(mUserName);
|
||||
if (mAuthString == null) {
|
||||
String cs = mUserName + ':' + mPassword;
|
||||
mAuthString = "Basic " + Base64.encodeBytes(cs.getBytes());
|
||||
mAuthString = "Basic " + new String(Base64.encodeBase64(cs.getBytes()));
|
||||
mCmdString = "&User=" + safeUserName + "&DeviceId=" + mDeviceId + "&DeviceType="
|
||||
+ mDeviceType;
|
||||
}
|
||||
@ -362,18 +380,40 @@ public class EasSyncService extends AbstractSyncService {
|
||||
|
||||
private HttpClient getHttpClient(int timeout) {
|
||||
HttpParams params = new BasicHttpParams();
|
||||
HttpConnectionParams.setConnectionTimeout(params, 10*SECONDS);
|
||||
HttpConnectionParams.setConnectionTimeout(params, 15*SECONDS);
|
||||
HttpConnectionParams.setSoTimeout(params, timeout);
|
||||
return new DefaultHttpClient(params);
|
||||
}
|
||||
|
||||
protected HttpResponse sendHttpClientPost(String cmd, byte[] bytes) throws IOException {
|
||||
return sendHttpClientPost(cmd, new ByteArrayEntity(bytes));
|
||||
return sendHttpClientPost(cmd, new ByteArrayEntity(bytes), COMMAND_TIMEOUT);
|
||||
}
|
||||
|
||||
protected HttpResponse sendHttpClientPost(String cmd, HttpEntity entity) throws IOException {
|
||||
HttpClient client =
|
||||
getHttpClient(cmd.equals(PING_COMMAND) ? PING_COMMAND_TIMEOUT : COMMAND_TIMEOUT);
|
||||
return sendHttpClientPost(cmd, entity, COMMAND_TIMEOUT);
|
||||
}
|
||||
|
||||
protected HttpResponse sendPing(byte[] bytes, int pingHeartbeat) throws IOException {
|
||||
int timeout = (pingHeartbeat+15)*SECONDS;
|
||||
Thread.currentThread().setName(mAccount.mDisplayName + ": Ping");
|
||||
|
||||
if (Eas.USER_LOG) {
|
||||
userLog("Sending ping, timeout: " + pingHeartbeat +
|
||||
"s, high water mark: " + mPingHighWaterMark + 's');
|
||||
}
|
||||
|
||||
SyncManager.runAsleep(mMailboxId, timeout);
|
||||
try {
|
||||
HttpResponse res = sendHttpClientPost(PING_COMMAND, new ByteArrayEntity(bytes), timeout);
|
||||
return res;
|
||||
} finally {
|
||||
SyncManager.runAwake(mMailboxId);
|
||||
}
|
||||
}
|
||||
|
||||
protected HttpResponse sendHttpClientPost(String cmd, HttpEntity entity, int timeout)
|
||||
throws IOException {
|
||||
HttpClient client = getHttpClient(timeout);
|
||||
String us = makeUriString(cmd, null);
|
||||
HttpPost method = new HttpPost(URI.create(us));
|
||||
if (cmd.startsWith("SendMail&")) {
|
||||
@ -422,11 +462,6 @@ public class EasSyncService extends AbstractSyncService {
|
||||
*/
|
||||
public void runAccountMailbox() throws IOException, EasParserException {
|
||||
// Initialize exit status to success
|
||||
mNetworkType = waitForConnectivity();
|
||||
mPingHeartbeat = PING_HEARTBEAT_MOBILE;
|
||||
if (mNetworkType == ConnectivityManager.TYPE_WIFI) {
|
||||
mPingHeartbeat = PING_HEARTBEAT_WIFI;
|
||||
}
|
||||
mExitStatus = EmailServiceStatus.SUCCESS;
|
||||
try {
|
||||
try {
|
||||
@ -464,7 +499,7 @@ public class EasSyncService extends AbstractSyncService {
|
||||
HttpResponse resp = sendHttpClientOptions();
|
||||
int code = resp.getStatusLine().getStatusCode();
|
||||
userLog("OPTIONS response: ", code);
|
||||
if (code == HttpURLConnection.HTTP_OK) {
|
||||
if (code == HttpStatus.SC_OK) {
|
||||
Header header = resp.getFirstHeader("ms-asprotocolversions");
|
||||
String versions = header.getValue();
|
||||
if (versions != null) {
|
||||
@ -504,7 +539,7 @@ public class EasSyncService extends AbstractSyncService {
|
||||
HttpResponse resp = sendHttpClientPost("FolderSync", s.toByteArray());
|
||||
if (mStop) break;
|
||||
int code = resp.getStatusLine().getStatusCode();
|
||||
if (code == HttpURLConnection.HTTP_OK) {
|
||||
if (code == HttpStatus.SC_OK) {
|
||||
HttpEntity entity = resp.getEntity();
|
||||
int len = (int)entity.getContentLength();
|
||||
if (len > 0) {
|
||||
@ -515,9 +550,8 @@ public class EasSyncService extends AbstractSyncService {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} else if (code == HttpURLConnection.HTTP_UNAUTHORIZED ||
|
||||
code == HttpURLConnection.HTTP_FORBIDDEN) {
|
||||
mExitStatus = AbstractSyncService.EXIT_LOGIN_FAILURE;
|
||||
} else if (isAuthError(code)) {
|
||||
mExitStatus = EXIT_LOGIN_FAILURE;
|
||||
} else {
|
||||
userLog("FolderSync response error: ", code);
|
||||
}
|
||||
@ -606,25 +640,72 @@ public class EasSyncService extends AbstractSyncService {
|
||||
}
|
||||
}
|
||||
|
||||
void runPingLoop() throws IOException, StaleFolderListException {
|
||||
// Do push for all sync services here
|
||||
ArrayList<Mailbox> pushBoxes = new ArrayList<Mailbox>();
|
||||
long endTime = System.currentTimeMillis() + (30*MINUTES);
|
||||
HashMap<Long, Integer> pingFailureMap = new HashMap<Long, Integer>();
|
||||
/**
|
||||
* Check the boxes reporting changes to see if there really were any...
|
||||
* We do this because bugs in various Exchange servers can put us into a looping
|
||||
* behavior by continually reporting changes in a mailbox, even when there aren't any.
|
||||
*
|
||||
* This behavior is seemingly random, and therefore we must code defensively by
|
||||
* backing off of push behavior when it is detected.
|
||||
*
|
||||
* One known cause, on certain Exchange 2003 servers, is acknowledged by Microsoft, and the
|
||||
* server hotfix for this case can be found at http://support.microsoft.com/kb/923282
|
||||
*/
|
||||
|
||||
while (System.currentTimeMillis() < endTime) {
|
||||
void checkPingErrors(HashMap<String, Integer> errorMap) {
|
||||
mBindArguments[0] = Long.toString(mAccount.mId);
|
||||
for (String serverId: mPingChangeList) {
|
||||
// Find the id and sync status for each box
|
||||
mBindArguments[1] = serverId;
|
||||
Cursor c = mContentResolver.query(Mailbox.CONTENT_URI, SYNC_STATUS_PROJECTION,
|
||||
WHERE_ACCOUNT_KEY_AND_SERVER_ID, mBindArguments, null);
|
||||
try {
|
||||
if (c.moveToFirst()) {
|
||||
String status = c.getString(1);
|
||||
int type = SyncManager.getStatusType(status);
|
||||
// This check should always be true...
|
||||
if (type == SyncManager.SYNC_PING) {
|
||||
int changeCount = SyncManager.getStatusChangeCount(status);
|
||||
if (changeCount > 0) {
|
||||
errorMap.remove(serverId);
|
||||
} else if (changeCount == 0) {
|
||||
// This means that a ping reported changes in error; we keep a count
|
||||
// of consecutive errors of this kind
|
||||
Integer failures = errorMap.get(serverId);
|
||||
if (failures == null) {
|
||||
errorMap.put(serverId, 1);
|
||||
} else if (failures > MAX_PING_FAILURES) {
|
||||
// We'll back off of push for this box
|
||||
pushFallback(c.getLong(0));
|
||||
return;
|
||||
} else {
|
||||
errorMap.put(serverId, failures + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
c.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void runPingLoop() throws IOException, StaleFolderListException {
|
||||
int pingHeartbeat = mPingHeartbeat;
|
||||
|
||||
// Do push for all sync services here
|
||||
long endTime = System.currentTimeMillis() + (30*MINUTES);
|
||||
HashMap<String, Integer> pingErrorMap = new HashMap<String, Integer>();
|
||||
|
||||
while (System.currentTimeMillis() < endTime && !mStop) {
|
||||
// Count of pushable mailboxes
|
||||
int pushCount = 0;
|
||||
// Count of mailboxes that can be pushed right now
|
||||
int canPushCount = 0;
|
||||
Serializer s = new Serializer();
|
||||
int code;
|
||||
Cursor c = mContentResolver.query(Mailbox.CONTENT_URI, Mailbox.CONTENT_PROJECTION,
|
||||
MailboxColumns.ACCOUNT_KEY + '=' + mAccount.mId +
|
||||
AND_FREQUENCY_PING_PUSH_AND_NOT_ACCOUNT_MAILBOX, null, null);
|
||||
|
||||
pushBoxes.clear();
|
||||
|
||||
try {
|
||||
// Loop through our pushed boxes seeing what is available to push
|
||||
while (c.moveToNext()) {
|
||||
@ -642,45 +723,14 @@ public class EasSyncService extends AbstractSyncService {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 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);
|
||||
if (changeCount > 0) {
|
||||
pingFailureMap.remove(mailboxId);
|
||||
} else if (changeCount == 0) {
|
||||
// This means that a ping failed; we'll keep track of this
|
||||
Integer failures = pingFailureMap.get(mailboxId);
|
||||
if (failures == null) {
|
||||
pingFailureMap.put(mailboxId, 1);
|
||||
} else if (failures > MAX_PING_FAILURES) {
|
||||
// Change all push/ping boxes (except account) to 5 minute sync
|
||||
pushFallback(mailboxId);
|
||||
return;
|
||||
} else {
|
||||
pingFailureMap.put(mailboxId, failures + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (canPushCount++ == 0) {
|
||||
// Initialize the Ping command
|
||||
s.start(Tags.PING_PING)
|
||||
.data(Tags.PING_HEARTBEAT_INTERVAL,
|
||||
Integer.toString(mPingHeartbeat))
|
||||
Integer.toString(pingHeartbeat))
|
||||
.start(Tags.PING_FOLDERS);
|
||||
}
|
||||
// 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);
|
||||
s.start(Tags.PING_FOLDER)
|
||||
.data(Tags.PING_ID, c.getString(Mailbox.CONTENT_SERVER_ID_COLUMN))
|
||||
@ -688,7 +738,6 @@ public class EasSyncService extends AbstractSyncService {
|
||||
.end();
|
||||
userLog("Ping ready for: ", folderClass, ", ", mailboxName, " (",
|
||||
c.getString(Mailbox.CONTENT_SERVER_ID_COLUMN), ")");
|
||||
pushBoxes.add(new Mailbox().restore(c));
|
||||
} else if (pingStatus == SyncManager.PING_STATUS_RUNNING ||
|
||||
pingStatus == SyncManager.PING_STATUS_WAITING) {
|
||||
userLog(mailboxName, " not ready for ping");
|
||||
@ -706,48 +755,84 @@ public class EasSyncService extends AbstractSyncService {
|
||||
// If we have some number that are ready for push, send Ping to the server
|
||||
s.end().end().done();
|
||||
|
||||
Thread.currentThread().setName(mAccount.mDisplayName + ": Ping");
|
||||
userLog("Sending ping, timeout: " + mPingHeartbeat + "s");
|
||||
|
||||
// Sleep for the heartbeat time plus a little bit of slack
|
||||
SyncManager.runAsleep(mMailboxId, (mPingHeartbeat+15)*SECONDS);
|
||||
HttpResponse res = sendHttpClientPost(PING_COMMAND, s.toByteArray());
|
||||
SyncManager.runAwake(mMailboxId);
|
||||
|
||||
// Don't send request if we've been asked to stop
|
||||
// If we've been stopped, this is a good time to return
|
||||
if (mStop) return;
|
||||
code = res.getStatusLine().getStatusCode();
|
||||
|
||||
userLog("Ping response: ", code);
|
||||
try {
|
||||
// Send the ping, wrapped by appropriate timeout/alarm
|
||||
HttpResponse res = sendPing(s.toByteArray(), pingHeartbeat);
|
||||
|
||||
// Return immediately if we've been asked to stop
|
||||
if (mStop) {
|
||||
userLog("Stopping pingLoop");
|
||||
return;
|
||||
}
|
||||
int code = res.getStatusLine().getStatusCode();
|
||||
userLog("Ping response: ", code);
|
||||
|
||||
if (code == HttpURLConnection.HTTP_OK) {
|
||||
HttpEntity e = res.getEntity();
|
||||
int len = (int)e.getContentLength();
|
||||
InputStream is = res.getEntity().getContent();
|
||||
if (len > 0) {
|
||||
parsePingResult(is, mContentResolver);
|
||||
} else {
|
||||
// Return immediately if we've been asked to stop during the ping
|
||||
if (mStop) {
|
||||
userLog("Stopping pingLoop");
|
||||
return;
|
||||
}
|
||||
|
||||
if (code == HttpStatus.SC_OK) {
|
||||
HttpEntity e = res.getEntity();
|
||||
int len = (int)e.getContentLength();
|
||||
InputStream is = res.getEntity().getContent();
|
||||
if (len > 0) {
|
||||
int status = parsePingResult(is, mContentResolver);
|
||||
// If our ping completed (status = 1), and we're not at the maximum,
|
||||
// try increasing timeout by two minutes
|
||||
if (status == PROTOCOL_PING_STATUS_COMPLETED &&
|
||||
pingHeartbeat > mPingHighWaterMark) {
|
||||
userLog("Setting ping high water mark at: ", mPingHighWaterMark);
|
||||
mPingHighWaterMark = pingHeartbeat;
|
||||
}
|
||||
if (status == PROTOCOL_PING_STATUS_COMPLETED &&
|
||||
pingHeartbeat < PING_MAX_HEARTBEAT &&
|
||||
!mPingHeartbeatDropped) {
|
||||
pingHeartbeat += PING_HEARTBEAT_INCREMENT;
|
||||
if (pingHeartbeat > PING_MAX_HEARTBEAT) {
|
||||
pingHeartbeat = PING_MAX_HEARTBEAT;
|
||||
}
|
||||
userLog("Increasing ping heartbeat to ", pingHeartbeat, "s");
|
||||
} else if (status == PROTOCOL_PING_STATUS_CHANGES_FOUND) {
|
||||
checkPingErrors(pingErrorMap);
|
||||
}
|
||||
} else {
|
||||
userLog("Ping returned empty result; throwing IOException");
|
||||
throw new IOException();
|
||||
}
|
||||
} else if (isAuthError(code)) {
|
||||
mExitStatus = EXIT_LOGIN_FAILURE;
|
||||
userLog("Authorization error during Ping: ", code);
|
||||
throw new IOException();
|
||||
}
|
||||
} else if (isAuthError(code)) {
|
||||
mExitStatus = AbstractSyncService.EXIT_LOGIN_FAILURE;
|
||||
userLog("Authorization error during Ping: ", code);
|
||||
throw new IOException();
|
||||
} catch (IOException e) {
|
||||
String msg = e.getMessage();
|
||||
// If we get the exception that is indicative of a NAT timeout and if we
|
||||
// haven't yet "fixed" the timeout, back off by two minutes and "fix" it
|
||||
if (msg != null && msg.contains("reset by peer")) {
|
||||
if (pingHeartbeat > PING_MIN_HEARTBEAT &&
|
||||
pingHeartbeat > mPingHighWaterMark) {
|
||||
pingHeartbeat -= PING_HEARTBEAT_INCREMENT;
|
||||
mPingHeartbeatDropped = true;
|
||||
if (pingHeartbeat < PING_MIN_HEARTBEAT) {
|
||||
pingHeartbeat = PING_MIN_HEARTBEAT;
|
||||
}
|
||||
userLog("Decreased ping heartbeat to ", pingHeartbeat, "s");
|
||||
}
|
||||
} else {
|
||||
userLog("IOException detected in runPingLoop: " +
|
||||
((msg != null) ? msg : "no message"));
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
} else if (pushCount > 0) {
|
||||
// If we want to Ping, but can't just yet, wait 10 seconds and try again
|
||||
// No point giving up the wake lock for 10 seconds...
|
||||
userLog("pingLoop waiting for: ", (pushCount - canPushCount), " box(es)");
|
||||
sleep(10*SECONDS);
|
||||
} else {
|
||||
// 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);
|
||||
// we'll update the folder list. Let the device sleep in the meantime...
|
||||
SyncManager.runAsleep(mMailboxId, (30*MINUTES)+(15*SECONDS));
|
||||
sleep(30*MINUTES);
|
||||
SyncManager.runAwake(mMailboxId);
|
||||
}
|
||||
@ -769,8 +854,8 @@ public class EasSyncService extends AbstractSyncService {
|
||||
// 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) {
|
||||
mPingChangeList = pp.getSyncList();
|
||||
for (String serverId: mPingChangeList) {
|
||||
mBindArguments[1] = serverId;
|
||||
Cursor c = cr.query(Mailbox.CONTENT_URI, Mailbox.CONTENT_PROJECTION,
|
||||
WHERE_ACCOUNT_KEY_AND_SERVER_ID, mBindArguments, null);
|
||||
@ -784,53 +869,7 @@ public class EasSyncService extends AbstractSyncService {
|
||||
}
|
||||
}
|
||||
}
|
||||
return pp.getSyncList().size();
|
||||
}
|
||||
|
||||
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;
|
||||
return pp.getSyncStatus();
|
||||
}
|
||||
|
||||
private String getFilterType() {
|
||||
@ -870,12 +909,17 @@ public class EasSyncService extends AbstractSyncService {
|
||||
* @param target, an EasMailbox, EasContacts, or EasCalendar object
|
||||
*/
|
||||
public void sync(AbstractSyncAdapter target) throws IOException {
|
||||
mTarget = target;
|
||||
Mailbox mailbox = target.mMailbox;
|
||||
|
||||
boolean moreAvailable = true;
|
||||
while (!mStop && moreAvailable) {
|
||||
waitForConnectivity();
|
||||
// If we have no connectivity, just exit cleanly. SyncManager will start us up again
|
||||
// when connectivity has returned
|
||||
if (!hasConnectivity()) {
|
||||
userLog("No connectivity in sync; finishing sync");
|
||||
mExitStatus = EXIT_DONE;
|
||||
return;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
PartRequest req = null;
|
||||
@ -917,10 +961,6 @@ public class EasSyncService extends AbstractSyncService {
|
||||
if (!className.equals("Contacts")) {
|
||||
// Set the lookback appropriately (EAS calls this a "filter")
|
||||
s.start(Tags.SYNC_OPTIONS).data(Tags.SYNC_FILTER_TYPE, getFilterType());
|
||||
// No truncation in this version
|
||||
//if (mProtocolVersionDouble < 12.0) {
|
||||
// s.data(Tags.SYNC_TRUNCATION, "7");
|
||||
//}
|
||||
options = true;
|
||||
}
|
||||
if (mProtocolVersionDouble >= 12.0) {
|
||||
@ -932,8 +972,6 @@ public class EasSyncService extends AbstractSyncService {
|
||||
// HTML for email; plain text for everything else
|
||||
.data(Tags.BASE_TYPE, (className.equals("Email") ? Eas.BODY_PREFERENCE_HTML
|
||||
: Eas.BODY_PREFERENCE_TEXT))
|
||||
// No truncation in this version
|
||||
//.data(Tags.BASE_TRUNCATION_SIZE, Eas.DEFAULT_BODY_TRUNCATION_SIZE)
|
||||
.end();
|
||||
}
|
||||
if (options) {
|
||||
@ -947,7 +985,7 @@ public class EasSyncService extends AbstractSyncService {
|
||||
userLog("Sync, deviceId = ", mDeviceId);
|
||||
HttpResponse resp = sendHttpClientPost("Sync", s.toByteArray());
|
||||
int code = resp.getStatusLine().getStatusCode();
|
||||
if (code == HttpURLConnection.HTTP_OK) {
|
||||
if (code == HttpStatus.SC_OK) {
|
||||
InputStream is = resp.getEntity().getContent();
|
||||
if (is != null) {
|
||||
moreAvailable = target.parse(is, this);
|
||||
@ -956,11 +994,14 @@ public class EasSyncService extends AbstractSyncService {
|
||||
} else {
|
||||
userLog("Sync response error: ", code);
|
||||
if (isAuthError(code)) {
|
||||
mExitStatus = AbstractSyncService.EXIT_LOGIN_FAILURE;
|
||||
mExitStatus = EXIT_LOGIN_FAILURE;
|
||||
} else {
|
||||
mExitStatus = EXIT_IO_ERROR;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
mExitStatus = EXIT_DONE;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
@ -1011,7 +1052,6 @@ public class EasSyncService extends AbstractSyncService {
|
||||
sync(target);
|
||||
} while (mRequestTime != 0);
|
||||
}
|
||||
mExitStatus = EXIT_DONE;
|
||||
} catch (IOException e) {
|
||||
String message = e.getMessage();
|
||||
userLog("Caught IOException: ", ((message == null) ? "" : message));
|
||||
|
@ -90,6 +90,9 @@ public class SyncManager extends Service implements Runnable {
|
||||
private static final int MINUTES = 60*SECONDS;
|
||||
private static final int ONE_DAY_MINUTES = 1440;
|
||||
|
||||
private static final int SYNC_MANAGER_HEARTBEAT_TIME = 15*MINUTES;
|
||||
private static final int CONNECTIVITY_WAIT_TIME = 2*MINUTES;
|
||||
|
||||
// Sync hold constants for services with transient errors
|
||||
private static final int HOLD_DELAY_ESCALATION = 30*SECONDS;
|
||||
private static final int HOLD_DELAY_MAXIMUM = 3*MINUTES;
|
||||
@ -135,6 +138,8 @@ public class SyncManager extends Service implements Runnable {
|
||||
|
||||
// We synchronize on this for all actions affecting the service and error maps
|
||||
private static Object sSyncToken = new Object();
|
||||
// All threads can use this lock to wait for connectivity
|
||||
public static Object sConnectivityLock = new Object();
|
||||
|
||||
// Keeps track of running services (by mailbox id)
|
||||
private HashMap<Long, AbstractSyncService> mServiceMap =
|
||||
@ -552,6 +557,12 @@ public class SyncManager extends Service implements Runnable {
|
||||
return mBinder;
|
||||
}
|
||||
|
||||
static public void smLog(String str) {
|
||||
if (INSTANCE != null) {
|
||||
INSTANCE.log(str);
|
||||
}
|
||||
}
|
||||
|
||||
protected void log(String str) {
|
||||
if (Eas.USER_LOG) {
|
||||
Log.d(TAG, str);
|
||||
@ -573,7 +584,6 @@ public class SyncManager extends Service implements Runnable {
|
||||
throw new IOException();
|
||||
}
|
||||
// If we've already got the id, return it
|
||||
|
||||
if (INSTANCE.mDeviceId != null) {
|
||||
return INSTANCE.mDeviceId;
|
||||
}
|
||||
@ -597,10 +607,10 @@ public class SyncManager extends Service implements Runnable {
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
// We'll just use the default below
|
||||
Log.e(TAG, "Can't get device name");
|
||||
Log.e(TAG, "Can't get device name!");
|
||||
} catch (IOException e) {
|
||||
// We'll just use the default below
|
||||
Log.e(TAG, "Can't get device name");
|
||||
Log.e(TAG, "Can't get device name!");
|
||||
}
|
||||
throw new IOException();
|
||||
}
|
||||
@ -627,17 +637,21 @@ public class SyncManager extends Service implements Runnable {
|
||||
log("Attempt to start SyncManager though already started before?");
|
||||
}
|
||||
|
||||
mDeviceId = android.provider.Settings.Secure.getString(getContentResolver(),
|
||||
android.provider.Settings.Secure.ANDROID_ID);
|
||||
|
||||
|
||||
// mDeviceId = android.provider.Settings.Secure.getString(getContentResolver(),
|
||||
// android.provider.Settings.Secure.ANDROID_ID);
|
||||
try {
|
||||
mDeviceId = getDeviceId();
|
||||
} catch (IOException e) {
|
||||
// We can't run in this situation
|
||||
throw new RuntimeException();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
log("!!! MaiLService onDestroy");
|
||||
log("!!! SyncManager onDestroy");
|
||||
stopServices();
|
||||
// Stop receivers and content observerse
|
||||
// Stop receivers and content observers
|
||||
if (mConnectivityReceiver != null) {
|
||||
unregisterReceiver(mConnectivityReceiver);
|
||||
}
|
||||
@ -720,6 +734,7 @@ public class SyncManager extends Service implements Runnable {
|
||||
if (INSTANCE != null) {
|
||||
AccountObserver obs = INSTANCE.mAccountObserver;
|
||||
obs.stopAccountSyncs(acctId, false);
|
||||
kick("reload folder list");
|
||||
}
|
||||
}
|
||||
|
||||
@ -791,7 +806,7 @@ public class SyncManager extends Service implements Runnable {
|
||||
if (pi != null) {
|
||||
AlarmManager alarmManager = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
|
||||
alarmManager.cancel(pi);
|
||||
log("+Alarm cleared for " + alarmOwner(id));
|
||||
//log("+Alarm cleared for " + alarmOwner(id));
|
||||
sPendingIntents.remove(id);
|
||||
}
|
||||
}
|
||||
@ -809,7 +824,7 @@ public class SyncManager extends Service implements Runnable {
|
||||
|
||||
AlarmManager alarmManager = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
|
||||
alarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + millis, pi);
|
||||
INSTANCE.log("+Alarm set for " + alarmOwner(id) + ", " + millis/1000 + "s");
|
||||
//INSTANCE.log("+Alarm set for " + alarmOwner(id) + ", " + millis/1000 + "s");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -852,43 +867,50 @@ public class SyncManager extends Service implements Runnable {
|
||||
}
|
||||
}
|
||||
|
||||
private void releaseConnectivityLock(String reason) {
|
||||
// Clear our sync error map when we get connected
|
||||
mSyncErrorMap.clear();
|
||||
synchronized (sConnectivityLock) {
|
||||
sConnectivityLock.notifyAll();
|
||||
}
|
||||
kick(reason);
|
||||
}
|
||||
|
||||
public class ConnectivityReceiver extends BroadcastReceiver {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
Bundle b = intent.getExtras();
|
||||
if (b != null) {
|
||||
NetworkInfo a = (NetworkInfo)b.get("networkInfo");
|
||||
String info = "CM Info for " + a.getTypeName();
|
||||
NetworkInfo a = (NetworkInfo)b.get(ConnectivityManager.EXTRA_NETWORK_INFO);
|
||||
String info = "Connectivity alert for " + a.getTypeName();
|
||||
State state = a.getState();
|
||||
if (state == State.CONNECTED) {
|
||||
info += " CONNECTED";
|
||||
kick("connected");
|
||||
// Clear our sync error map when we get connected
|
||||
mSyncErrorMap.clear();
|
||||
} else if (state == State.CONNECTING) {
|
||||
info += " CONNECTING";
|
||||
log(info);
|
||||
releaseConnectivityLock("connected");
|
||||
} else if (state == State.DISCONNECTED) {
|
||||
info += " DISCONNECTED";
|
||||
kick("disconnected");
|
||||
} else if (state == State.DISCONNECTING) {
|
||||
info += " DISCONNECTING";
|
||||
} else if (state == State.SUSPENDED) {
|
||||
info += " SUSPENDED";
|
||||
} else if (state == State.UNKNOWN) {
|
||||
info += " UNKNOWN";
|
||||
a = (NetworkInfo)b.get(ConnectivityManager.EXTRA_OTHER_NETWORK_INFO);
|
||||
if (a != null && a.getState() == State.CONNECTED) {
|
||||
info += " (OTHER CONNECTED)";
|
||||
releaseConnectivityLock("disconnect/other");
|
||||
ConnectivityManager cm =
|
||||
(ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
if (cm != null) {
|
||||
NetworkInfo i = cm.getActiveNetworkInfo();
|
||||
if (i == null || i.getState() != State.CONNECTED) {
|
||||
log("CM says we're connected, but no active info?");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log(info);
|
||||
kick("disconnected");
|
||||
}
|
||||
}
|
||||
log(": " + info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void pause(int ms) {
|
||||
try {
|
||||
Thread.sleep(ms);
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
}
|
||||
|
||||
private void startService(AbstractSyncService service, Mailbox m) {
|
||||
synchronized (sSyncToken) {
|
||||
String mailboxName = m.mDisplayName;
|
||||
@ -936,6 +958,38 @@ public class SyncManager extends Service implements Runnable {
|
||||
}
|
||||
}
|
||||
|
||||
private void waitForConnectivity() {
|
||||
int cnt = 0;
|
||||
while (!mStop) {
|
||||
ConnectivityManager cm =
|
||||
(ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
NetworkInfo info = cm.getActiveNetworkInfo();
|
||||
if (info != null) {
|
||||
log("NetworkInfo: " + info.getTypeName() + ", " + info.getState().name());
|
||||
return;
|
||||
} else {
|
||||
|
||||
// If we're waiting for the long haul, shut down running service threads
|
||||
if (++cnt > 1) {
|
||||
stopServices();
|
||||
}
|
||||
|
||||
// Wait until a network is connected, but let the device sleep
|
||||
// We'll set an alarm just in case we don't get notified (bugs happen)
|
||||
synchronized (sConnectivityLock) {
|
||||
runAsleep(SYNC_MANAGER_ID, CONNECTIVITY_WAIT_TIME+5*SECONDS);
|
||||
try {
|
||||
log("Connectivity lock...");
|
||||
sConnectivityLock.wait(CONNECTIVITY_WAIT_TIME);
|
||||
log("Connectivity lock released...");
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
runAwake(SYNC_MANAGER_ID);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void run() {
|
||||
mStop = false;
|
||||
|
||||
@ -963,51 +1017,35 @@ public class SyncManager extends Service implements Runnable {
|
||||
mConnectivityReceiver = new ConnectivityReceiver();
|
||||
registerReceiver(mConnectivityReceiver,
|
||||
new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
|
||||
ConnectivityManager cm =
|
||||
(ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
|
||||
try {
|
||||
while (!mStop) {
|
||||
// Get a wake lock first...
|
||||
runAwake(SYNC_MANAGER_ID);
|
||||
log("Looking for something to do...");
|
||||
int cnt = 0;
|
||||
while (!mStop) {
|
||||
NetworkInfo info = cm.getActiveNetworkInfo();
|
||||
if (info != null && info.isConnected()) {
|
||||
break;
|
||||
} else {
|
||||
if (cnt++ == 2) {
|
||||
stopServices();
|
||||
waitForConnectivity();
|
||||
mNextWaitReason = "Heartbeat";
|
||||
long nextWait = checkMailboxes();
|
||||
try {
|
||||
synchronized (INSTANCE) {
|
||||
if (nextWait < 0) {
|
||||
log("Negative wait? Setting to 1s");
|
||||
nextWait = 1*SECONDS;
|
||||
}
|
||||
pause(10*SECONDS);
|
||||
}
|
||||
}
|
||||
if (!mStop) {
|
||||
mNextWaitReason = "Heartbeat";
|
||||
long nextWait = checkMailboxes();
|
||||
try {
|
||||
synchronized (INSTANCE) {
|
||||
if (nextWait < 0) {
|
||||
log("Negative wait? Setting to 1s");
|
||||
nextWait = 1*SECONDS;
|
||||
}
|
||||
if (nextWait > (30*SECONDS)) {
|
||||
runAsleep(SYNC_MANAGER_ID, nextWait - 1000);
|
||||
}
|
||||
if (nextWait > (30*SECONDS)) {
|
||||
runAsleep(SYNC_MANAGER_ID, nextWait - 1000);
|
||||
}
|
||||
if (nextWait != SYNC_MANAGER_HEARTBEAT_TIME) {
|
||||
log("Next awake in " + (nextWait / 1000) + "s: " + mNextWaitReason);
|
||||
INSTANCE.wait(nextWait);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
// Needs to be caught, but causes no problem
|
||||
INSTANCE.wait(nextWait);
|
||||
}
|
||||
} else {
|
||||
stopServices();
|
||||
log("Shutdown requested");
|
||||
return;
|
||||
} catch (InterruptedException e) {
|
||||
// Needs to be caught, but causes no problem
|
||||
}
|
||||
|
||||
}
|
||||
stopServices();
|
||||
log("Shutdown requested");
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
log("Goodbye");
|
||||
}
|
||||
@ -1047,7 +1085,7 @@ public class SyncManager extends Service implements Runnable {
|
||||
}
|
||||
}
|
||||
|
||||
long nextWait = 10*MINUTES;
|
||||
long nextWait = SYNC_MANAGER_HEARTBEAT_TIME;
|
||||
long now = System.currentTimeMillis();
|
||||
// Start up threads that need it...
|
||||
Cursor c = getContentResolver().query(Mailbox.CONTENT_URI,
|
||||
@ -1066,14 +1104,21 @@ public class SyncManager extends Service implements Runnable {
|
||||
if (service == null) {
|
||||
// Check whether we're in a hold (temporary or permanent)
|
||||
SyncError syncError = mSyncErrorMap.get(mid);
|
||||
if (syncError != null && (syncError.fatal || now < syncError.holdEndTime)) {
|
||||
if (!syncError.fatal) {
|
||||
if (syncError != null) {
|
||||
// Nothing we can do about fatal errors
|
||||
if (syncError.fatal) continue;
|
||||
if (now < syncError.holdEndTime) {
|
||||
// If release time is earlier than next wait time,
|
||||
// move next wait time up to the release time
|
||||
if (syncError.holdEndTime < (now + nextWait)) {
|
||||
nextWait = syncError.holdEndTime - now;
|
||||
mNextWaitReason = "Release hold";
|
||||
}
|
||||
continue;
|
||||
} else {
|
||||
// The hold has ended; remove from the error map
|
||||
mSyncErrorMap.remove(mid);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
long freq = c.getInt(Mailbox.CONTENT_SYNC_INTERVAL_COLUMN);
|
||||
if (freq == Mailbox.CHECK_INTERVAL_PUSH) {
|
||||
@ -1235,7 +1280,6 @@ public class SyncManager extends Service implements Runnable {
|
||||
|
||||
static public AbstractSyncService startManualSync(long mailboxId, int reason, PartRequest req) {
|
||||
if (INSTANCE == null || INSTANCE.mServiceMap == null) return null;
|
||||
INSTANCE.log("startManualSync");
|
||||
synchronized (sSyncToken) {
|
||||
if (INSTANCE.mServiceMap.get(mailboxId) == null) {
|
||||
INSTANCE.mSyncErrorMap.remove(mailboxId);
|
||||
@ -1266,13 +1310,13 @@ public class SyncManager extends Service implements Runnable {
|
||||
*/
|
||||
static public void kick(String reason) {
|
||||
if (INSTANCE != null) {
|
||||
//if (reason != null) {
|
||||
// INSTANCE.log("Kick: " + reason);
|
||||
//}
|
||||
synchronized (INSTANCE) {
|
||||
INSTANCE.notify();
|
||||
}
|
||||
}
|
||||
synchronized (sConnectivityLock) {
|
||||
sConnectivityLock.notify();
|
||||
}
|
||||
}
|
||||
|
||||
static public void kick(long mailboxId) {
|
||||
|
@ -38,11 +38,11 @@ import java.io.InputStream;
|
||||
*/
|
||||
public abstract class AbstractSyncParser extends Parser {
|
||||
|
||||
EasSyncService mService;
|
||||
Mailbox mMailbox;
|
||||
Account mAccount;
|
||||
Context mContext;
|
||||
ContentResolver mContentResolver;
|
||||
protected EasSyncService mService;
|
||||
protected Mailbox mMailbox;
|
||||
protected Account mAccount;
|
||||
protected Context mContext;
|
||||
protected ContentResolver mContentResolver;
|
||||
|
||||
public AbstractSyncParser(InputStream in, EasSyncService _service) throws IOException {
|
||||
super(in);
|
||||
|
@ -17,11 +17,11 @@
|
||||
|
||||
package com.android.exchange.adapter;
|
||||
|
||||
import com.android.email.codec.binary.Base64;
|
||||
import com.android.email.provider.EmailContent.Mailbox;
|
||||
import com.android.email.provider.EmailContent.MailboxColumns;
|
||||
import com.android.exchange.Eas;
|
||||
import com.android.exchange.EasSyncService;
|
||||
import com.android.exchange.utility.Base64;
|
||||
|
||||
import android.content.ContentProviderOperation;
|
||||
import android.content.ContentProviderResult;
|
||||
@ -1025,7 +1025,7 @@ public class ContactsSyncAdapter extends AbstractSyncAdapter {
|
||||
SmartBuilder builder = createBuilder(entity, Photo.CONTENT_ITEM_TYPE, -1);
|
||||
// We're always going to add this; it's not worth trying to figure out whether the
|
||||
// picture is the same as the one stored.
|
||||
byte[] pic = Base64.decode(photo);
|
||||
byte[] pic = Base64.decodeBase64(photo.getBytes());
|
||||
builder.withValue(Photo.PHOTO, pic);
|
||||
add(builder.build());
|
||||
}
|
||||
|
@ -477,8 +477,11 @@ public class EmailSyncAdapter extends AbstractSyncAdapter {
|
||||
"You've got new mail!", pi);
|
||||
boolean vibrate = ((mAccount.getFlags() & EmailContent.Account.FLAGS_VIBRATE) != 0);
|
||||
String ringtone = mAccount.getRingtone();
|
||||
notif.defaults = Notification.DEFAULT_LIGHTS;
|
||||
notif.flags = Notification.FLAG_SHOW_LIGHTS;
|
||||
notif.sound = TextUtils.isEmpty(ringtone) ? null : Uri.parse(ringtone);
|
||||
notif.ledARGB = 0xFF00FF00;
|
||||
notif.ledOnMS = 500;
|
||||
notif.ledOffMS = 500;
|
||||
if (vibrate) {
|
||||
notif.defaults |= Notification.DEFAULT_VIBRATE;
|
||||
}
|
||||
|
@ -100,6 +100,7 @@ public class FolderSyncParser extends AbstractSyncParser {
|
||||
public boolean parse() throws IOException {
|
||||
int status;
|
||||
boolean res = false;
|
||||
boolean resetFolders = false;
|
||||
if (nextTag(START_DOCUMENT) != Tags.FOLDER_FOLDER_SYNC)
|
||||
throw new IOException();
|
||||
while (nextTag(START_DOCUMENT) != END_DOCUMENT) {
|
||||
@ -115,6 +116,7 @@ public class FolderSyncParser extends AbstractSyncParser {
|
||||
// Stop existing syncs and reconstruct _main
|
||||
SyncManager.folderListReloaded(mAccountId);
|
||||
res = true;
|
||||
resetFolders = true;
|
||||
} else {
|
||||
// Other errors are at the server, so let's throw an error that will
|
||||
// cause this sync to be retried at a later time
|
||||
@ -131,7 +133,7 @@ public class FolderSyncParser extends AbstractSyncParser {
|
||||
skipTag();
|
||||
}
|
||||
synchronized (mService.getSynchronizer()) {
|
||||
if (!mService.isStopped()) {
|
||||
if (!mService.isStopped() || resetFolders) {
|
||||
ContentValues cv = new ContentValues();
|
||||
cv.put(AccountColumns.SYNC_KEY, mAccount.mSyncKey);
|
||||
mAccount.update(mContext, cv);
|
||||
|
@ -21,6 +21,8 @@ import com.android.exchange.Eas;
|
||||
import com.android.exchange.EasException;
|
||||
import com.android.exchange.utility.FileLogger;
|
||||
|
||||
import org.kxml2.wap.Wbxml;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
|
@ -31,16 +31,21 @@ import java.util.ArrayList;
|
||||
* by the sync server, which will sync the updated folder list.
|
||||
*/
|
||||
public class PingParser extends Parser {
|
||||
ArrayList<String> syncList = new ArrayList<String>();
|
||||
EasSyncService mService;
|
||||
private ArrayList<String> syncList = new ArrayList<String>();
|
||||
private EasSyncService mService;
|
||||
private int mSyncStatus = 0;
|
||||
|
||||
public ArrayList<String> getSyncList() {
|
||||
return syncList;
|
||||
}
|
||||
|
||||
public PingParser(InputStream in, EasSyncService _service) throws IOException {
|
||||
public int getSyncStatus() {
|
||||
return mSyncStatus;
|
||||
}
|
||||
|
||||
public PingParser(InputStream in, EasSyncService service) throws IOException {
|
||||
super(in);
|
||||
mService = _service;
|
||||
mService = service;
|
||||
}
|
||||
|
||||
public void parsePingFolders(ArrayList<String> syncList) throws IOException {
|
||||
@ -65,10 +70,9 @@ public class PingParser extends Parser {
|
||||
while (nextTag(START_DOCUMENT) != END_DOCUMENT) {
|
||||
if (tag == Tags.PING_STATUS) {
|
||||
int status = getValueInt();
|
||||
mSyncStatus = status;
|
||||
mService.userLog("Ping completed, status = ", status);
|
||||
if (status == 2) {
|
||||
// Status = 2 indicates changes in one folder or other
|
||||
mService.userLog("Changes found");
|
||||
res = true;
|
||||
} else if (status == 7 || status == 4) {
|
||||
// Status of 7 or 4 indicate a stale folder list
|
||||
|
@ -23,6 +23,8 @@
|
||||
|
||||
package com.android.exchange.adapter;
|
||||
|
||||
import org.kxml2.wap.Wbxml;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
@ -1,46 +0,0 @@
|
||||
/* Copyright (c) 2002,2003, Stefan Haustein, Oberhausen, Rhld., Germany
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE. */
|
||||
|
||||
package com.android.exchange.adapter;
|
||||
|
||||
/** contains the WBXML constants */
|
||||
|
||||
public interface Wbxml {
|
||||
static public final int SWITCH_PAGE = 0;
|
||||
static public final int END = 1;
|
||||
static public final int ENTITY = 2;
|
||||
static public final int STR_I = 3;
|
||||
static public final int LITERAL = 4;
|
||||
static public final int EXT_I_0 = 0x40;
|
||||
static public final int EXT_I_1 = 0x41;
|
||||
static public final int EXT_I_2 = 0x42;
|
||||
static public final int PI = 0x43;
|
||||
static public final int LITERAL_C = 0x44;
|
||||
static public final int EXT_T_0 = 0x80;
|
||||
static public final int EXT_T_1 = 0x81;
|
||||
static public final int EXT_T_2 = 0x82;
|
||||
static public final int STR_T = 0x83;
|
||||
static public final int LITERAL_A = 0x084;
|
||||
static public final int EXT_0 = 0x0c0;
|
||||
static public final int EXT_1 = 0x0c1;
|
||||
static public final int EXT_2 = 0x0c2;
|
||||
static public final int OPAQUE = 0x0c3;
|
||||
static public final int LITERAL_AC = 0x0c4;
|
||||
}
|
@ -1,265 +0,0 @@
|
||||
/* Copyright (c) 2002,2003, Stefan Haustein, Oberhausen, Rhld., Germany
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE. */
|
||||
|
||||
//Contributors: Jonathan Cox, Bogdan Onoiu, Jerry Tian
|
||||
//Simplified for Google, Inc. by Marc Blank
|
||||
|
||||
package com.android.exchange.adapter;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
import org.xmlpull.v1.*;
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A class for writing WBXML.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
|
||||
public class WbxmlSerializer implements XmlSerializer {
|
||||
|
||||
Hashtable<String, Integer> stringTable = new Hashtable<String, Integer>();
|
||||
|
||||
OutputStream out;
|
||||
|
||||
ByteArrayOutputStream buf = new ByteArrayOutputStream();
|
||||
ByteArrayOutputStream stringTableBuf = new ByteArrayOutputStream();
|
||||
|
||||
String pending;
|
||||
int depth;
|
||||
String name;
|
||||
|
||||
Hashtable<String, Object> tagTable = new Hashtable<String, Object>();
|
||||
|
||||
private int tagPage;
|
||||
|
||||
public void cdsect (String cdsect) throws IOException{
|
||||
text (cdsect);
|
||||
}
|
||||
|
||||
public void comment (String comment) {
|
||||
}
|
||||
|
||||
|
||||
public void docdecl (String docdecl) {
|
||||
throw new RuntimeException ("Cannot write docdecl for WBXML");
|
||||
}
|
||||
|
||||
|
||||
public void entityRef (String er) {
|
||||
throw new RuntimeException ("EntityReference not supported for WBXML");
|
||||
}
|
||||
|
||||
public int getDepth() {
|
||||
return depth;
|
||||
}
|
||||
|
||||
|
||||
public boolean getFeature (String name) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public String getNamespace() {
|
||||
throw new RuntimeException("NYI");
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
throw new RuntimeException("NYI");
|
||||
}
|
||||
|
||||
public String getPrefix(String nsp, boolean create) {
|
||||
throw new RuntimeException ("NYI");
|
||||
}
|
||||
|
||||
|
||||
public Object getProperty (String name) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void ignorableWhitespace (String sp) {
|
||||
}
|
||||
|
||||
|
||||
public void endDocument() throws IOException {
|
||||
writeInt(out, stringTableBuf.size());
|
||||
out.write(stringTableBuf.toByteArray());
|
||||
out.write(buf.toByteArray());
|
||||
out.flush();
|
||||
}
|
||||
|
||||
|
||||
/** ATTENTION: flush cannot work since Wbxml documents require
|
||||
buffering. Thus, this call does nothing. */
|
||||
|
||||
public void flush() {
|
||||
}
|
||||
|
||||
|
||||
public void checkPending(boolean degenerated) throws IOException {
|
||||
if (pending == null)
|
||||
return;
|
||||
|
||||
int[] idx = (int[]) tagTable.get(pending);
|
||||
|
||||
// if no entry in known table, then add as literal
|
||||
if (idx == null) {
|
||||
throw new IOException("Bad tag: " + pending);
|
||||
}
|
||||
else {
|
||||
if(idx[0] != tagPage) {
|
||||
tagPage=idx[0];
|
||||
buf.write(Wbxml.SWITCH_PAGE);
|
||||
buf.write(tagPage);
|
||||
}
|
||||
|
||||
buf.write(degenerated ? idx[1] : idx[1] | 64);
|
||||
}
|
||||
|
||||
pending = null;
|
||||
}
|
||||
|
||||
|
||||
public void processingInstruction(String pi) {
|
||||
}
|
||||
|
||||
|
||||
public void setFeature(String name, boolean value) {
|
||||
}
|
||||
|
||||
public void setOutput (Writer writer) {
|
||||
}
|
||||
|
||||
public void setOutput (OutputStream out, String encoding) throws IOException {
|
||||
this.out = out;
|
||||
buf = new ByteArrayOutputStream();
|
||||
stringTableBuf = new ByteArrayOutputStream();
|
||||
}
|
||||
|
||||
public OutputStream getOutput () {
|
||||
return out;
|
||||
|
||||
}
|
||||
public void setPrefix(String prefix, String nsp) {
|
||||
throw new RuntimeException("NYI");
|
||||
}
|
||||
|
||||
public void setProperty(String property, Object value) {
|
||||
throw new IllegalArgumentException ("unknown property "+property);
|
||||
}
|
||||
|
||||
|
||||
public void startDocument(String s, Boolean b) throws IOException{
|
||||
out.write(0x03); // version 1.3
|
||||
out.write(0x01); // unknown or missing public identifier
|
||||
out.write(106);
|
||||
}
|
||||
|
||||
|
||||
public XmlSerializer startTag(String namespace, String name) throws IOException {
|
||||
checkPending(false);
|
||||
pending = name;
|
||||
depth++;
|
||||
return this;
|
||||
}
|
||||
|
||||
public XmlSerializer text(char[] chars, int start, int len) throws IOException {
|
||||
checkPending(false);
|
||||
buf.write(Wbxml.STR_I);
|
||||
writeStrI(buf, new String(chars, start, len));
|
||||
return this;
|
||||
}
|
||||
|
||||
public XmlSerializer text(String text) throws IOException {
|
||||
checkPending(false);
|
||||
buf.write(Wbxml.STR_I);
|
||||
writeStrI(buf, text);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/** Used in text() and attribute() to write text */
|
||||
|
||||
|
||||
public XmlSerializer endTag(String namespace, String name) throws IOException {
|
||||
if (pending != null) {
|
||||
checkPending(true);
|
||||
} else {
|
||||
buf.write(Wbxml.END);
|
||||
}
|
||||
depth--;
|
||||
return this;
|
||||
}
|
||||
|
||||
// ------------- internal methods --------------------------
|
||||
|
||||
static void writeInt(OutputStream out, int i) throws IOException {
|
||||
byte[] buf = new byte[5];
|
||||
int idx = 0;
|
||||
|
||||
do {
|
||||
buf[idx++] = (byte) (i & 0x7f);
|
||||
i = i >> 7;
|
||||
} while (i != 0);
|
||||
|
||||
while (idx > 1) {
|
||||
out.write(buf[--idx] | 0x80);
|
||||
}
|
||||
out.write(buf[0]);
|
||||
}
|
||||
|
||||
void writeStrI(OutputStream out, String s) throws IOException {
|
||||
byte[] data = s.getBytes("UTF-8");
|
||||
out.write(data);
|
||||
out.write(0);
|
||||
}
|
||||
|
||||
public Hashtable<String, Object> getTagTable () {
|
||||
return this.tagTable;
|
||||
}
|
||||
|
||||
public void setTagTable (Hashtable<String, Object> tagTable) {
|
||||
this.tagTable = tagTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the tag table for a given page.
|
||||
* The first string in the array defines tag 5, the second tag 6 etc.
|
||||
*/
|
||||
|
||||
public void setTagTable(int page, String[] tagTable) {
|
||||
|
||||
for (int i = 0; i < tagTable.length; i++) {
|
||||
if (tagTable[i] != null) {
|
||||
Object idx = new int[]{page, i+5};
|
||||
this.tagTable.put(tagTable[i], idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public XmlSerializer attribute(String namespace, String name, String value)
|
||||
throws IOException, IllegalArgumentException, IllegalStateException {
|
||||
return null;
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,133 +0,0 @@
|
||||
/*
|
||||
* 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.utility;
|
||||
|
||||
/**
|
||||
* Encode and decode QuotedPrintable text, according to the specification. Since the Email
|
||||
* application already does this elsewhere, the goal would be to use its functionality here.
|
||||
*
|
||||
*/
|
||||
public class QuotedPrintable {
|
||||
static public String toString (String str) {
|
||||
int len = str.length();
|
||||
// Make sure we don't get an index out of bounds error with the = character
|
||||
int max = len - 2;
|
||||
StringBuilder sb = new StringBuilder(len);
|
||||
try {
|
||||
for (int i = 0; i < len; i++) {
|
||||
char c = str.charAt(i);
|
||||
if (c == '=') {
|
||||
if (i < max) {
|
||||
char n = str.charAt(++i);
|
||||
if (n == '\r') {
|
||||
n = str.charAt(++i);
|
||||
if (n == '\n') {
|
||||
continue;
|
||||
} else {
|
||||
// This isn't valid QuotedPrintable, but what to do?
|
||||
// Let's just ignore it because 1) it's extremely unlikely to
|
||||
// happen, and 2) an exception is frankly no better.
|
||||
}
|
||||
} else {
|
||||
// Must be less than 0x80, right?
|
||||
int a;
|
||||
if (n >= '0' && n <= '9') {
|
||||
a = (n - '0') << 4;
|
||||
} else {
|
||||
a = (10 + (n - 'A')) << 4;
|
||||
}
|
||||
n = str.charAt(++i);
|
||||
if (n >= '0' && n <= '9') {
|
||||
c = (char) (a + (n - '0'));
|
||||
} else {
|
||||
c = (char) (a + 10 + (n - 'A'));
|
||||
}
|
||||
}
|
||||
} if (i + 1 == len) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
sb.append(c);
|
||||
}
|
||||
} catch (IndexOutOfBoundsException e) {
|
||||
}
|
||||
String ret = sb.toString();
|
||||
return ret;
|
||||
}
|
||||
|
||||
static public String encode (String str) {
|
||||
int len = str.length();
|
||||
StringBuffer sb = new StringBuffer(len + len>>2);
|
||||
int i = 0;
|
||||
while (i < len) {
|
||||
char c = str.charAt(i++);
|
||||
if (c < 0x80) {
|
||||
sb.append(c);
|
||||
} else {
|
||||
sb.append('&');
|
||||
sb.append('#');
|
||||
sb.append((int)c);
|
||||
sb.append(';');
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
static public int decode (byte[] bytes, int len) {
|
||||
// Make sure we don't get an index out of bounds error with the = character
|
||||
int max = len - 2;
|
||||
int pos = 0;
|
||||
try {
|
||||
for (int i = 0; i < len; i++) {
|
||||
char c = (char)bytes[i];
|
||||
if (c == '=') {
|
||||
if (i < max) {
|
||||
char n = (char)bytes[++i];
|
||||
if (n == '\r') {
|
||||
n = (char)bytes[++i];
|
||||
if (n == '\n') {
|
||||
continue;
|
||||
} else {
|
||||
System.err.println("Not valid QP");
|
||||
}
|
||||
} else {
|
||||
// Must be less than 0x80, right?
|
||||
int a;
|
||||
if (n >= '0' && n <= '9') {
|
||||
a = (n - '0') << 4;
|
||||
} else {
|
||||
a = (10 + (n - 'A')) << 4;
|
||||
}
|
||||
n = (char)bytes[++i];
|
||||
if (n >= '0' && n <= '9') {
|
||||
c = (char) (a + (n - '0'));
|
||||
} else {
|
||||
c = (char) (a + 10 + (n - 'A'));
|
||||
}
|
||||
}
|
||||
} if (i + 1 > len) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
bytes[pos++] = (byte)c;
|
||||
}
|
||||
} catch (IndexOutOfBoundsException e) {
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user