Merge change 21281 into eclair

* changes:
  Implement adaptive ping timeout; release wake lock waiting for connectivity
This commit is contained in:
Android (Google) Code Review 2009-08-16 16:41:26 -07:00
commit 10e8ecef2f
15 changed files with 399 additions and 2507 deletions

View File

@ -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;

View File

@ -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);

View File

@ -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));

View File

@ -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) {

View File

@ -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);

View File

@ -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());
}

View File

@ -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;
}

View File

@ -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);

View File

@ -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;

View File

@ -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

View File

@ -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;

View File

@ -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;
}

View File

@ -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

View File

@ -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;
}
}