Major refactor and cleanup of EAS code
* Rewrote push logic to encompass multiple folders (i.e. calendar/contacts)
* Change inbox from push frequency to ping frequency after initial sync
* Implement upsync logic for email (i.e. sending changes to the server)
* Did cleanup of some files (there's still some to do) re: format, style
* Initial one-way sync of Contacts data - add and delete are implemented
* Created adapter package for all parts of the EAS adapter
* Created utility package for utility code that will eventually be merged
with code in the Email application (Base64, QuotedPrintable, etc.)
* SyncManager/AbstractSyncService can be used in the future for other
protocols, especially IMAP push
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 250c78a..9f3e510 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -24,6 +24,9 @@
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+ <!-- For EAS purposes; could be removed when EAS has a permanent home -->
+ <uses-permission android:name="android.permission.WRITE_CONTACTS"/>
+
<!-- Only required if a store implements push mail and needs to keep network open -->
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
@@ -150,7 +153,9 @@
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
- <receiver android:name=".service.BootReceiver"
+ <receiver android:name="com.android.exchange.UserSyncAlarmReceiver"/>
+ <receiver android:name="com.android.exchange.MailboxAlarmReceiver"/>
+ <receiver android:name=".service.BootReceiver"
android:enabled="false"
>
<intent-filter>
diff --git a/src/com/android/exchange/AbstractSyncService.java b/src/com/android/exchange/AbstractSyncService.java
new file mode 100644
index 0000000..11c2d0f
--- /dev/null
+++ b/src/com/android/exchange/AbstractSyncService.java
@@ -0,0 +1,330 @@
+/*
+ * 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;
+
+import java.util.ArrayList;
+
+import com.android.email.Email;
+import com.android.email.mail.MessagingException;
+import com.android.exchange.EmailContent.Account;
+import com.android.exchange.EmailContent.Mailbox;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.net.NetworkInfo.DetailedState;
+import android.util.Log;
+
+/**
+ * Base class for all protocol services SyncManager (extends Service, implements
+ * Runnable) instantiates subclasses to run a sync (either timed, or push, or
+ * mail placed in outbox, etc.) EasSyncService is currently implemented; my goal
+ * would be to move IMAP to this structure when it comes time to introduce push
+ * functionality.
+ */
+public abstract class AbstractSyncService implements Runnable {
+
+ public String TAG = "ProtocolService";
+
+ public static final String SUMMARY_PROTOCOL = "_SUMMARY_";
+ public static final String SYNCED_PROTOCOL = "_SYNCING_";
+ public static final String MOVE_FAVORITES_PROTOCOL = "_MOVE_FAVORITES_";
+ public static final int CONNECT_TIMEOUT = 30000;
+ public static final int NETWORK_WAIT = 15000;
+ public static final int SECS = 1000;
+ public static final int MINS = 60 * SECS;
+ public static final int HRS = 60 * MINS;
+ public static final int DAYS = 24 * HRS;
+ public static final String IMAP_PROTOCOL = "imap";
+ public static final String EAS_PROTOCOL = "eas";
+ public static final int EXIT_DONE = 0;
+ public static final int EXIT_IO_ERROR = 1;
+ 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;
+ protected long mRequestTime = 0;
+
+ protected ArrayList<PartRequest> mPartRequests = new ArrayList<PartRequest>();
+ protected PartRequest mPendingPartRequest = null;
+
+ /**
+ * Sent by SyncManager to request that the service stop itself cleanly
+ */
+ public abstract void stop();
+
+ /**
+ * Sent by SyncManager to indicate a user request requiring service has been
+ * added to the service's pending request queue
+ */
+ public abstract void ping();
+
+ /**
+ * Called to validate an account; abstract to allow each protocol to do what
+ * is necessary. For consistency with the Email app's original
+ * functionality, success is indicated by a failure to throw an Exception
+ * (ugh). Parameters are self-explanatory
+ *
+ * @param host
+ * @param userName
+ * @param password
+ * @param port
+ * @param ssl
+ * @param context
+ * @throws MessagingException
+ */
+ 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;
+ mMailboxId = _mailbox.mId;
+ mMailboxName = _mailbox.mServerId;
+ mAccount = Account.restoreAccountWithId(_context, _mailbox.mAccountKey);
+ }
+
+ // Will be required when subclasses are instantiated by name
+ public AbstractSyncService(String prefix) {
+ }
+
+ /**
+ * The UI can call this static method to perform account validation. This method wraps each
+ * protocol's validateAccount method. Arguments are self-explanatory, except where noted.
+ *
+ * @param klass the protocol class (EasSyncService.class for example)
+ * @param host
+ * @param userName
+ * @param password
+ * @param port
+ * @param ssl
+ * @param context
+ * @throws MessagingException
+ */
+ static public void validate(Class<? extends AbstractSyncService> klass, String host,
+ String userName, String password, int port, boolean ssl, Context context)
+ throws MessagingException {
+ AbstractSyncService svc;
+ try {
+ svc = klass.newInstance();
+ svc.validateAccount(host, userName, password, port, ssl, context);
+ } catch (IllegalAccessException e) {
+ throw new MessagingException("internal error", e);
+ } catch (InstantiationException e) {
+ throw new MessagingException("internal error", e);
+ }
+ }
+
+ public static class ValidationResult {
+ static final int NO_FAILURE = 0;
+ static final int CONNECTION_FAILURE = 1;
+ static final int VALIDATION_FAILURE = 2;
+ static final int EXCEPTION = 3;
+
+ static final ValidationResult succeeded = new ValidationResult(true, NO_FAILURE, null);
+ boolean success;
+ int failure = NO_FAILURE;
+ String reason = null;
+ Exception exception = null;
+
+ ValidationResult(boolean _success, int _failure, String _reason) {
+ success = _success;
+ failure = _failure;
+ reason = _reason;
+ }
+
+ ValidationResult(boolean _success) {
+ success = _success;
+ }
+
+ ValidationResult(Exception e) {
+ success = false;
+ failure = EXCEPTION;
+ exception = e;
+ }
+
+ public boolean isSuccess() {
+ return success;
+ }
+
+ public String getReason() {
+ return reason;
+ }
+ }
+
+ /**
+ * Asks SyncManager for a WaitLock for this sync
+ */
+ public final void runAwake() {
+ //SyncManager.runAwake(mMailboxId);
+ }
+
+ /**
+ * Asks SyncManager to release any WaitLock and schedule an alarm at a specified number
+ * of milliseconds in the future
+ *
+ * @param millis
+ */
+ public final void runAsleep(long millis) {
+ //SyncManager.runAsleep(mMailboxId, millis);
+ }
+
+ /**
+ * Convenience method to do user logging (i.e. connection activity). Saves a bunch of
+ * repetitive code.
+ *
+ * @param str the String to log
+ */
+ public void userLog(String str) {
+ if (Eas.USER_DEBUG) {
+ Log.i(TAG, str);
+ }
+ }
+
+ public void errorLog(String str) {
+ if (Eas.USER_DEBUG) {
+ Log.e(TAG, str);
+ }
+ }
+
+ /**
+ * Convenience method to do test logging. Saves a bunch of repetitive code.
+ * Unlike user logging, TEST_DEBUG is declared final, so that testLog calls should get
+ * "compiled out" for non-debug builds.
+ *
+ * @param str the String to log
+ */
+ protected void testLog(String str) {
+ if (Eas.TEST_DEBUG) {
+ Log.v(Email.LOG_TAG, str);
+ }
+ }
+
+ /**
+ * Implements a delay until there is some kind of network connectivity available. This method
+ * may be supplanted by functionality in SyncManager.
+ *
+ * @return the type of network connected to
+ */
+ public int waitForConnectivity() {
+ ConnectivityManager cm = (ConnectivityManager)mContext
+ .getSystemService(Context.CONNECTIVITY_SERVICE);
+ while (true) {
+ 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");
+ }
+ pause(1000);
+ } else {
+ userLog("Not connected; waiting 15 seconds");
+ pause(NETWORK_WAIT);
+ }
+ }
+ }
+
+ /**
+ * 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
+ */
+
+ public void addPartRequest(PartRequest req) {
+ synchronized (mPartRequests) {
+ mPartRequests.add(req);
+ }
+ }
+
+ public void removePartRequest(PartRequest req) {
+ synchronized (mPartRequests) {
+ mPartRequests.remove(req);
+ }
+ }
+
+ public PartRequest hasPartRequest(long emailId, String part) {
+ synchronized (mPartRequests) {
+ for (PartRequest pr : mPartRequests) {
+ if (pr.emailId == emailId && pr.loc.equals(part))
+ return pr;
+ }
+ }
+ 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
+ public PartRequest cancelPartRequest(long emailId, String part) {
+ synchronized (mPartRequests) {
+ PartRequest p = null;
+ for (PartRequest pr : mPartRequests) {
+ if (pr.emailId == emailId && pr.loc.equals(part)) {
+ p = pr;
+ break;
+ }
+ }
+ if (p != null) {
+ mPartRequests.remove(p);
+ return p;
+ }
+ }
+ return null;
+ }
+}
diff --git a/src/com/android/exchange/Eas.java b/src/com/android/exchange/Eas.java
new file mode 100644
index 0000000..86b5419
--- /dev/null
+++ b/src/com/android/exchange/Eas.java
@@ -0,0 +1,62 @@
+/*
+ * 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;
+
+/**
+ * Constants used throughout the EAS implementation are stored here.
+ *
+ */
+public class Eas {
+ // For use in collecting user logs
+ public static boolean USER_DEBUG = false; // DO NOT CHECK IN WITH THIS SET TO TRUE
+ // For temporary use while debugging
+ public static boolean TEST_DEBUG = false; // DO NOT CHECK IN WITH THIS SET TO TRUE
+
+ public static String VERSION = "0.1";
+
+ // From EAS spec
+ // Mail Cal
+ // 0 No filter Yes Yes
+ // 1 1 day ago Yes No
+ // 2 3 days ago Yes No
+ // 3 1 week ago Yes No
+ // 4 2 weeks ago Yes Yes
+ // 5 1 month ago Yes Yes
+ // 6 3 months ago No Yes
+ // 7 6 months ago No Yes
+
+ static final String FILTER_ALL = "0";
+ static final String FILTER_1_DAY = "1";
+ static final String FILTER_3_DAYS = "2";
+ static final String FILTER_1_WEEK = "3";
+ static final String FILTER_2_WEEKS = "4";
+ static final String FILTER_1_MONTH = "5";
+ static final String FILTER_3_MONTHS = "6";
+ static final String FILTER_6_MONTHS = "7";
+ static final String BODY_PREFERENCE_TEXT = "1";
+ static final String BODY_PREFERENCE_HTML = "2";
+
+ static final String DEFAULT_BODY_TRUNCATION_SIZE = "50000";
+
+ public static final int FOLDER_STATUS_OK = 1;
+ public static final int FOLDER_STATUS_INVALID_KEY = 9;
+
+ public void setUserDebug(boolean state) {
+ USER_DEBUG = state;
+ }
+}
diff --git a/src/com/android/exchange/EasEmailSyncParser.java b/src/com/android/exchange/EasEmailSyncParser.java
deleted file mode 100644
index e5fd154..0000000
--- a/src/com/android/exchange/EasEmailSyncParser.java
+++ /dev/null
@@ -1,356 +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;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.GregorianCalendar;
-import java.util.TimeZone;
-
-import com.android.email.provider.EmailProvider;
-import com.android.exchange.EmailContent.Account;
-import com.android.exchange.EmailContent.Attachment;
-import com.android.exchange.EmailContent.Mailbox;
-import com.android.exchange.EmailContent.Message;
-import com.android.exchange.EmailContent.MessageColumns;
-import com.android.exchange.EmailContent.SyncColumns;
-
-import android.content.ContentProviderOperation;
-import android.content.ContentProviderResult;
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.OperationApplicationException;
-import android.database.Cursor;
-import android.os.RemoteException;
-import android.util.Log;
-
-public class EasEmailSyncParser extends EasParser {
-
- private static final String TAG = "EmailSyncParser";
-
- private Account mAccount;
- private EasService mService;
- private ContentResolver mContentResolver;
- private Context mContext;
- private Mailbox mMailbox;
- protected boolean mMoreAvailable = false;
- String[] bindArgument = new String[1];
-
- public EasEmailSyncParser(InputStream in, EasService service) throws IOException {
- super(in);
- mService = service;
- mContext = service.mContext;
- mMailbox = service.mMailbox;
- mAccount = service.mAccount;
- //setDebug(true);
- mContentResolver = mContext.getContentResolver();
- }
-
- public void parse() throws IOException {
- int status;
- if (nextTag(START_DOCUMENT) != EasTags.SYNC_SYNC)
- throw new IOException();
- while (nextTag(START_DOCUMENT) != END_DOCUMENT) {
- if (tag == EasTags.SYNC_COLLECTION || tag == EasTags.SYNC_COLLECTIONS) {
- // Ignore
- } else if (tag == EasTags.SYNC_STATUS) {
- status = getValueInt();
- if (status != 1) {
- System.err.println("Sync failed: " + status);
- if (status == 3) {
- // TODO Bad sync key. Must delete everything and start over...?
- mMailbox.mSyncKey = "0";
- Log.w(TAG, "Bad sync key; RESET and delete mailbox contents");
- mContext.getContentResolver()
- .delete(Message.CONTENT_URI,
- Message.MAILBOX_KEY + "=" + mMailbox.mId, null);
- mMoreAvailable = true;
- }
- }
- } else if (tag == EasTags.SYNC_COMMANDS) {
- commandsParser();
- } else if (tag == EasTags.SYNC_RESPONSES) {
- skipTag();
- } else if (tag == EasTags.SYNC_MORE_AVAILABLE) {
- mMoreAvailable = true;
- } else if (tag == EasTags.SYNC_SYNC_KEY) {
- if (mMailbox.mSyncKey.equals("0"))
- mMoreAvailable = true;
- mMailbox.mSyncKey = getValue();
- } else
- skipTag();
- }
- mMailbox.saveOrUpdate(mContext);
- }
-
- public void addParser(ArrayList<Message> emails) throws IOException {
- Message msg = new Message();
- String to = "";
- String from = "";
- String cc = "";
- String replyTo = "";
- int size = 0;
- msg.mAccountKey = mAccount.mId;
- msg.mMailboxKey = mMailbox.mId;
- msg.mFlagLoaded = Message.LOADED;
-
- ArrayList<Attachment> atts = new ArrayList<Attachment>();
- boolean inData = false;
-
- while (nextTag(EasTags.SYNC_ADD) != END) {
- switch (tag) {
- case EasTags.SYNC_SERVER_ID: // same as EasTags.EMAIL_BODY_SIZE
- if (!inData) {
- msg.mServerId = getValue();
- } else {
- size = Integer.parseInt(getValue());
- }
- break;
- case EasTags.SYNC_APPLICATION_DATA:
- inData = true;
- break;
- case EasTags.EMAIL_ATTACHMENTS:
- break;
- case EasTags.EMAIL_ATTACHMENT:
- attachmentParser(atts, msg);
- break;
- case EasTags.EMAIL_TO:
- to = getValue();
- break;
- case EasTags.EMAIL_FROM:
- from = getValue();
- String sender = from;
- int q = from.indexOf('\"');
- if (q >= 0) {
- int qq = from.indexOf('\"', q + 1);
- if (qq > 0) {
- sender = from.substring(q + 1, qq);
- }
- }
- msg.mDisplayName = sender;
- break;
- case EasTags.EMAIL_CC:
- cc = getValue();
- break;
- case EasTags.EMAIL_REPLY_TO:
- replyTo = getValue();
- break;
- case EasTags.EMAIL_DATE_RECEIVED:
- String date = getValue();
- // 2009-02-11T18:03:03.627Z
- GregorianCalendar cal = new GregorianCalendar();
- cal.set(Integer.parseInt(date.substring(0, 4)),
- Integer.parseInt(date.substring(5, 7)) - 1,
- Integer.parseInt(date.substring(8, 10)),
- Integer.parseInt(date.substring(11, 13)),
- Integer.parseInt(date.substring(14, 16)),
- Integer.parseInt(date.substring(17, 19)));
- cal.setTimeZone(TimeZone.getTimeZone("GMT"));
- msg.mTimeStamp = cal.getTimeInMillis();
- break;
- case EasTags.EMAIL_DISPLAY_TO:
- break;
- case EasTags.EMAIL_SUBJECT:
- msg.mSubject = getValue();
- break;
- case EasTags.EMAIL_IMPORTANCE:
- break;
- case EasTags.EMAIL_READ:
- msg.mFlagRead = getValueInt() == 1;
- break;
- case EasTags.EMAIL_BODY:
- msg.mTextInfo = "X;X;8;" + size; // location;encoding;charset;size
- msg.mText = getValue();
- // For now...
- msg.mPreview = "Fake preview"; //Messages.previewFromText(body);
- break;
- case EasTags.EMAIL_MESSAGE_CLASS:
- break;
- default:
- skipTag();
- }
- }
-
- // Tell the provider that this is synced back
- msg.mServerVersion = mMailbox.mSyncKey;
-
- msg.mTo = to;
- msg.mFrom = from;
- msg.mCc = cc;
- msg.mReplyTo = replyTo;
- if (atts.size() > 0) {
- msg.mAttachments = atts;
- }
- emails.add(msg);
- }
-
- public void attachmentParser(ArrayList<Attachment> atts, Message msg)
- throws IOException {
- String fileName = null;
- String length = null;
- String lvl = null;
-
- while (nextTag(EasTags.EMAIL_ATTACHMENT) != END) {
- switch (tag) {
- case EasTags.EMAIL_DISPLAY_NAME:
- fileName = getValue();
- break;
- case EasTags.EMAIL_ATT_NAME:
- lvl = getValue();
- break;
- case EasTags.EMAIL_ATT_SIZE:
- length = getValue();
- break;
- default:
- skipTag();
- }
- }
-
- if (fileName != null && length != null && lvl != null) {
- Attachment att = new Attachment();
- att.mEncoding = "base64";
- att.mSize = Long.parseLong(length);
- att.mFileName = fileName;
- atts.add(att);
- msg.mFlagAttachment = true;
- }
- }
-
- public void deleteParser(ArrayList<Long> deletes) throws IOException {
- while (nextTag(EasTags.SYNC_DELETE) != END) {
- switch (tag) {
- case EasTags.SYNC_SERVER_ID:
- String serverId = getValue();
- Cursor c = mContentResolver.query(Message.CONTENT_URI,
- Message.ID_COLUMN_PROJECTION,
- SyncColumns.SERVER_ID + "=" + serverId, null, null);
- try {
- if (c.moveToFirst()) {
- mService.log("Deleting " + serverId);
- deletes.add(c.getLong(Message.ID_COLUMNS_ID_COLUMN));
- }
- } finally {
- c.close();
- }
- break;
- default:
- skipTag();
- }
- }
- }
-
- public void changeParser(ArrayList<Long> changes) throws IOException {
- String serverId = null;
- boolean oldRead = false;
- boolean read = true;
- long id = 0;
- while (nextTag(EasTags.SYNC_CHANGE) != END) {
- switch (tag) {
- case EasTags.SYNC_SERVER_ID:
- serverId = getValue();
- bindArgument[0] = serverId;
- Cursor c = mContentResolver.query(Message.CONTENT_URI,
- Message.LIST_PROJECTION,
- SyncColumns.SERVER_ID + "=?", bindArgument, null);
- try {
- if (c.moveToFirst()) {
- mService.log("Changing " + serverId);
- oldRead = c.getInt(Message.LIST_READ_COLUMN) == Message.READ;
- id = c.getLong(Message.LIST_ID_COLUMN);
- }
- } finally {
- c.close();
- }
- break;
- case EasTags.EMAIL_READ:
- read = getValueInt() == 1;
- break;
- case EasTags.SYNC_APPLICATION_DATA:
- break;
- default:
- skipTag();
- }
- }
- if (oldRead != read) {
- changes.add(id);
- }
- }
-
- public void commandsParser() throws IOException {
- ArrayList<Message> newEmails = new ArrayList<Message>();
- ArrayList<Long> deletedEmails = new ArrayList<Long>();
- ArrayList<Long> changedEmails = new ArrayList<Long>();
-
- while (nextTag(EasTags.SYNC_COMMANDS) != END) {
- if (tag == EasTags.SYNC_ADD) {
- addParser(newEmails);
- } else if (tag == EasTags.SYNC_DELETE) {
- deleteParser(deletedEmails);
- } else if (tag == EasTags.SYNC_CHANGE) {
- changeParser(changedEmails);
- } else
- skipTag();
- }
-
- // Use a batch operation to handle the changes
- // TODO Notifications
- // TODO Store message bodies
- ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
- for (Message content: newEmails) {
- content.addSaveOps(ops);
- }
- for (Long id: deletedEmails) {
- ops.add(ContentProviderOperation
- .newDelete(ContentUris.withAppendedId(Message.CONTENT_URI, id)).build());
- }
- if (!changedEmails.isEmpty()) {
- ContentValues cv = new ContentValues();
- // TODO Handle proper priority
- // Set this as the correct state (assuming server wins)
- cv.put(SyncColumns.DIRTY_COUNT, 0);
- cv.put(MessageColumns.FLAG_READ, true);
- for (Long id: changedEmails) {
- // For now, don't handle read->unread
- ops.add(ContentProviderOperation.newUpdate(ContentUris
- .withAppendedId(Message.CONTENT_URI, id)).withValues(cv).build());
- }
- }
- ops.add(ContentProviderOperation.newUpdate(ContentUris
- .withAppendedId(Mailbox.CONTENT_URI, mMailbox.mId))
- .withValues(mMailbox.toContentValues()).build());
-
- try {
- ContentProviderResult[] results = mService.mContext.getContentResolver()
- .applyBatch(EmailProvider.EMAIL_AUTHORITY, ops);
- for (ContentProviderResult result: results) {
- if (result.uri == null) {
- Log.v(TAG, "Null result in ContentProviderResult!");
- }
- }
- } catch (RemoteException e) {
- // There is nothing to be done here; fail by returning null
- } catch (OperationApplicationException e) {
- // There is nothing to be done here; fail by returning null
- }
-
- Log.v(TAG, "Mailbox EOS syncKey now: " + mMailbox.mSyncKey);
- }
-}
diff --git a/src/com/android/exchange/EasParserException.java b/src/com/android/exchange/EasException.java
similarity index 80%
copy from src/com/android/exchange/EasParserException.java
copy to src/com/android/exchange/EasException.java
index ee10c23..e7613d7 100644
--- a/src/com/android/exchange/EasParserException.java
+++ b/src/com/android/exchange/EasException.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008-2009 Marc Blank
+ * Copyright (C) 2008-2009 Marc Blank
* Licensed to The Android Open Source Project.
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,6 +17,6 @@
package com.android.exchange;
-public class EasParserException extends Exception {
- private static final long serialVersionUID = 1L;
+public class EasException extends Exception {
+ private static final long serialVersionUID = 5894556952470989968L;
}
diff --git a/src/com/android/exchange/EasFolderSyncParser.java b/src/com/android/exchange/EasFolderSyncParser.java
deleted file mode 100644
index 9a3c14a..0000000
--- a/src/com/android/exchange/EasFolderSyncParser.java
+++ /dev/null
@@ -1,216 +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;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-import com.android.email.provider.EmailProvider;
-import com.android.exchange.EmailContent.Account;
-import com.android.exchange.EmailContent.Mailbox;
-
-import android.content.ContentProviderOperation;
-import android.content.ContentProviderResult;
-import android.content.ContentUris;
-//import android.content.Context;
-import android.content.OperationApplicationException;
-import android.os.RemoteException;
-import android.util.Log;
-
-public class EasFolderSyncParser extends EasParser {
-
- public static final String TAG = "FolderSyncParser";
-
- public static final int USER_FOLDER_TYPE = 1;
- public static final int INBOX_TYPE = 2;
- public static final int DRAFTS_TYPE = 3;
- public static final int DELETED_TYPE = 4;
- public static final int SENT_TYPE = 5;
- public static final int OUTBOX_TYPE = 6;
- public static final int TASKS_TYPE = 7;
- public static final int CALENDAR_TYPE = 8;
- public static final int CONTACTS_TYPE = 9;
- public static final int NOTES_TYPE = 10;
- public static final int JOURNAL_TYPE = 11;
- public static final int USER_MAILBOX_TYPE = 12;
-
- public static final List<Integer> mMailFolderTypes =
- Arrays.asList(INBOX_TYPE,DRAFTS_TYPE,DELETED_TYPE,SENT_TYPE,OUTBOX_TYPE,USER_MAILBOX_TYPE);
-
- private Account mAccount;
- private EasService mService;
- //private Context mContext;
- private MockParserStream mMock = null;
-
- public EasFolderSyncParser(InputStream in, EasService service) throws IOException {
- super(in);
- mService = service;
- mAccount = service.mAccount;
- //mContext = service.mContext;
- if (in instanceof MockParserStream) {
- mMock = (MockParserStream)in;
- }
- }
-
- public void parse() throws IOException {
- //captureOn();
- int status;
- if (nextTag(START_DOCUMENT) != EasTags.FOLDER_FOLDER_SYNC)
- throw new IOException();
- while (nextTag(START_DOCUMENT) != END_DOCUMENT) {
- if (tag == EasTags.FOLDER_STATUS) {
- status = getValueInt();
- if (status != 1) {
- System.err.println("FolderSync failed: " + status);
- }
- } else if (tag == EasTags.FOLDER_SYNC_KEY) {
- mAccount.mSyncKey = getValue();
- } else if (tag == EasTags.FOLDER_CHANGES) {
- changesParser();
- } else
- skipTag();
- }
- //captureOff(mContext, "FolderSyncParser.txt");
- }
-
- public void addParser(ArrayList<Mailbox> boxes) throws IOException {
- String name = null;
- String serverId = null;
- String parentId = null;
- int type = 0;
-
- while (nextTag(EasTags.FOLDER_ADD) != END) {
- switch (tag) {
- case EasTags.FOLDER_DISPLAY_NAME: {
- name = getValue();
- break;
- }
- case EasTags.FOLDER_TYPE: {
- type = getValueInt();
- break;
- }
- case EasTags.FOLDER_PARENT_ID: {
- parentId = getValue();
- break;
- }
- case EasTags.FOLDER_SERVER_ID: {
- serverId = getValue();
- break;
- }
- default:
- skipTag();
- }
- }
- if (mMailFolderTypes.contains(type)) {
- Mailbox m = new Mailbox();
- m.mDisplayName = name;
- m.mServerId = serverId;
- m.mAccountKey = mAccount.mId;
- if (type == INBOX_TYPE) {
- m.mSyncFrequency = Account.CHECK_INTERVAL_PUSH;
- m.mType = Mailbox.TYPE_INBOX;
- } else if (type == OUTBOX_TYPE) {
- //m.mSyncFrequency = MailService.OUTBOX_FREQUENCY;
- m.mSyncFrequency = Account.CHECK_INTERVAL_NEVER;
- m.mType = Mailbox.TYPE_OUTBOX;
- } else {
- if (type == SENT_TYPE) {
- m.mType = Mailbox.TYPE_SENT;
- } else if (type == DRAFTS_TYPE) {
- m.mType = Mailbox.TYPE_DRAFTS;
- } else if (type == DELETED_TYPE) {
- m.mType = Mailbox.TYPE_TRASH;
- }
- m.mSyncFrequency = Account.CHECK_INTERVAL_NEVER;
- }
-
- if (!parentId.equals("0")) {
- m.mParentServerId = parentId;
- }
- Log.v(TAG, "Adding mailbox: " + m.mDisplayName);
- boxes.add(m);
- }
-
- return;
- }
-
- public void changesParser() throws IOException {
- // Keep track of new boxes, deleted boxes, updated boxes
- ArrayList<Mailbox> newBoxes = new ArrayList<Mailbox>();
-
- while (nextTag(EasTags.FOLDER_CHANGES) != END) {
- if (tag == EasTags.FOLDER_ADD) {
- addParser(newBoxes);
- } else if (tag == EasTags.FOLDER_COUNT) {
- getValueInt();
- } else
- skipTag();
- }
-
- for (Mailbox m: newBoxes) {
- String parent = m.mParentServerId;
- if (parent != null) {
- // Wrong except first time! Need to check existing boxes!
- //**PROVIDER
- m.mFlagVisible = true; //false;
- for (Mailbox mm: newBoxes) {
- if (mm.mServerId.equals(parent)) {
- //mm.parent = true;
- }
- }
- }
- }
-
- if (mMock != null) {
- mMock.setResult(newBoxes);
- return;
- }
-
- if (!newBoxes.isEmpty()) {
- ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
- for (Mailbox content: newBoxes) {
- ContentProviderOperation.Builder b = ContentProviderOperation
- .newInsert(Mailbox.CONTENT_URI);
- b.withValues(content.toContentValues());
- ops.add(b.build());
- }
- ops.add(ContentProviderOperation.newUpdate(ContentUris
- .withAppendedId(Account.CONTENT_URI, mAccount.mId))
- .withValues(mAccount.toContentValues()).build());
-
- try {
- ContentProviderResult[] results = mService.mContext.getContentResolver()
- .applyBatch(EmailProvider.EMAIL_AUTHORITY, ops);
- for (ContentProviderResult result: results) {
- if (result.uri == null) {
- return;
- }
- }
- Log.v(TAG, "New syncKey: " + mAccount.mSyncKey);
- } catch (RemoteException e) {
- // There is nothing to be done here; fail by returning null
- } catch (OperationApplicationException e) {
- // There is nothing to be done here; fail by returning null
- }
- }
- }
-
-}
diff --git a/src/com/android/exchange/EasParser.java b/src/com/android/exchange/EasParser.java
deleted file mode 100644
index d096729..0000000
--- a/src/com/android/exchange/EasParser.java
+++ /dev/null
@@ -1,297 +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;
-
-import java.io.*;
-import java.util.ArrayList;
-
-import android.content.Context;
-import android.util.Log;
-
-
-public abstract class EasParser {
-
- private static final String TAG = "EasParser";
-
- public static final int START_DOCUMENT = 0;
- public static final int DONE = 1;
- public static final int START = 2;
- public static final int END = 3;
- public static final int TEXT = 4;
- public static final int END_DOCUMENT = 3;
-
- private static final int NOT_FETCHED = Integer.MIN_VALUE;
- private static final int NOT_ENDED = Integer.MIN_VALUE;
-
- private static final int EOF_BYTE = -1;
-
- private boolean debug = false;
- private boolean capture = false;
- private ArrayList<Integer> captureArray;
- private InputStream in;
- private int depth;
- private int nextId = NOT_FETCHED;
- private String[] tagTable;
- private String[][] tagTables = new String[24][];
- private String[] nameArray = new String[32];
- private int[] tagArray = new int[32];
- private boolean noContent;
-
- // Available to all to avoid method calls
- public int endTag = NOT_ENDED;
- public int type;
- public int tag;
- public String name;
- public String text;
- public int num;
-
- public void parse () throws IOException {
- }
-
- public EasParser (InputStream in) throws IOException {
- String[][] pages = EasTags.pages;
- for (int i = 0; i < pages.length; i++) {
- String[] page = pages[i];
- if (page.length > 0) {
- setTagTable(i, page);
- }
- }
- setInput(in);
- }
-
- public void setDebug (boolean val) {
- debug = val;
- }
-
- public void captureOn () {
- capture = true;
- captureArray = new ArrayList<Integer>();
- }
-
- public void captureOff (Context context, String file) {
- try {
- FileOutputStream out = context.openFileOutput(file, Context.MODE_WORLD_WRITEABLE);
- out.write(captureArray.toString().getBytes());
- out.close();
- } catch (FileNotFoundException e) {
- } catch (IOException e) {
- }
- }
-
- public String getValue () throws IOException {
- getNext(false);
- String val = text;
- getNext(false);
- if (type != END) {
- throw new IOException("No END found!");
- }
- endTag = tag;
- return val;
- }
-
- public int getValueInt () throws IOException {
- getNext(true);
- int val = num;
- getNext(false);
- if (type != END) {
- throw new IOException("No END found!");
- }
- endTag = tag;
- return val;
- }
-
- public int nextTag (int endTag) throws IOException {
- while (getNext(false) != DONE) {
- if (type == START) {
- return tag;
- } else if (type == END && tag == endTag) {
- return END;
- }
- }
- if (endTag == START_DOCUMENT) {
- return END_DOCUMENT;
- }
- throw new EodException();
- }
-
- public void skipTag () throws IOException {
- int thisTag = tag;
- while (getNext(false) != DONE) {
- if (type == END && tag == thisTag) {
- return;
- }
- }
- throw new EofException();
- }
-
- public int nextToken() throws IOException {
- getNext(false);
- return type;
- }
-
- public void setInput(InputStream in) throws IOException {
- this.in = in;
- readByte(); // version
- readInt(); // ?
- readInt(); // 106 (UTF-8)
- readInt(); // string table length
- tagTable = tagTables[0];
- }
-
- public int next () throws IOException {
- getNext(false);
- return type;
- }
-
- private final int getNext(boolean asInt) throws IOException {
- if (type == END) {
- depth--;
- } else {
- endTag = NOT_ENDED;
- }
-
- if (noContent) {
- type = END;
- noContent = false;
- return type;
- }
-
- text = null;
- name = null;
-
- int id = nextId ();
- while (id == Wbxml.SWITCH_PAGE) {
- nextId = NOT_FETCHED;
- tagTable = tagTables[(readByte())];
- id = nextId();
- }
- nextId = NOT_FETCHED;
-
- switch (id) {
- case -1 :
- type = DONE;
- break;
-
- case Wbxml.END :
- type = END;
- if (debug) {
- name = nameArray[depth];
- Log.v(TAG, "</" + name + '>');
- }
- tag = endTag = tagArray[depth];
- break;
-
- case Wbxml.STR_I :
- type = TEXT;
- if (asInt) {
- num = readInlineInt();
- } else {
- text = readInlineString();
- }
- if (debug) {
- Log.v(TAG, asInt ? Integer.toString(num) : text);
- }
- break;
-
- default :
- type = START;
- tag = id & 0x3F;
- noContent = (id & 0x40) == 0;
- depth++;
- if (debug) {
- name = tagTable[tag - 5];
- Log.v(TAG, '<' + name + '>');
- nameArray[depth] = name;
- }
- tagArray[depth] = tag;
- }
-
- return type;
- }
-
- private int read () throws IOException {
- int i = in.read();
- if (capture) {
- captureArray.add(i);
- }
- return i;
- }
-
- private int nextId () throws IOException {
- if (nextId == NOT_FETCHED) {
- nextId = read();
- }
- return nextId;
- }
-
- private int readByte() throws IOException {
- int i = read();
- if (i == EOF_BYTE) {
- throw new EofException();
- }
- return i;
- }
-
- private int readInlineInt() throws IOException {
- int result = 0;
-
- while (true) {
- int i = readByte();
- if (i == 0) {
- return result;
- }
- if (i >= '0' && i <= '9') {
- result = (result * 10) + (i - '0');
- } else {
- throw new IOException("Non integer");
- }
- }
- }
-
- private int readInt() throws IOException {
- int result = 0;
- int i;
-
- do {
- i = readByte();
- result = (result << 7) | (i & 0x7f);
- } while ((i & 0x80) != 0);
-
- return result;
- }
-
- private String readInlineString() throws IOException {
- StringBuilder sb = new StringBuilder(4096);
-
- while (true){
- int i = read();
- if (i == 0) {
- break;
- } else if (i == EOF_BYTE) {
- throw new EofException();
- }
- sb.append((char)i);
- }
- String res = sb.toString();
- return res;
- }
-
- public void setTagTable(int page, String[] table) {
- tagTables[page] = table;
- }
-}
\ No newline at end of file
diff --git a/src/com/android/exchange/EasPingService.java b/src/com/android/exchange/EasPingService.java
deleted file mode 100644
index b8ad048..0000000
--- a/src/com/android/exchange/EasPingService.java
+++ /dev/null
@@ -1,103 +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;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.HttpURLConnection;
-
-import com.android.exchange.EmailContent.Mailbox;
-
-import android.content.Context;
-
-public class EasPingService extends EasService {
-
- EasService mCaller;
- HttpURLConnection mConnection = null;
-
- public EasPingService(Context _context, Mailbox _mailbox, EasService _caller) {
- super(_context, _mailbox);
- mCaller = _caller;
- mHostAddress = _caller.mHostAddress;
- mUserName = _caller.mUserName;
- mPassword = _caller.mPassword;
- }
-
- class EASPingParser extends EasParser {
- protected boolean mMoreAvailable = false;
-
- public EASPingParser(InputStream in, EasService service) throws IOException {
- super(in);
- mMailbox = service.mMailbox;
- setDebug(true);
- }
-
- public void parse() throws IOException {
- int status;
- if (nextTag(START_DOCUMENT) != EasTags.PING_PING) {
- throw new IOException();
- }
- while (nextTag(START_DOCUMENT) != END_DOCUMENT) {
- if (tag == EasTags.PING_STATUS) {
- status = getValueInt();
- log("Ping completed, status = " + status);
- if (status == 1 || status == 2) {
- }
- mCaller.ping();
- } else {
- skipTag();
- }
- }
- }
- }
-
- public void stop () {
- mConnection.disconnect();
- }
-
- public void run () {
- try {
- EASSerializer s = new EASSerializer();
- s.start("Ping").data("HeartbeatInterval", "900").start("PingFolders")
- .start("PingFolder").data("PingId", mMailbox.mServerId).data("PingClass", "Email")
- .end("PingFolder").end("PingFolders").end("Ping").end();
- String data = s.toString();
- HttpURLConnection uc = sendEASPostCommand("Ping", data);
- mConnection = uc;
- log("Sending ping, read timeout: " + uc.getReadTimeout() / 1000 + "s");
- int code = uc.getResponseCode();
- log("Response code: " + code);
- if (code == HttpURLConnection.HTTP_OK) {
- String encoding = uc.getHeaderField("Transfer-Encoding");
- if (encoding == null) {
- int len = uc.getHeaderFieldInt("Content-Length", 0);
- if (len > 0) {
- new EASPingParser(uc.getInputStream(), this).parse();
- }
- }
- }
- } catch (IOException e1) {
- e1.printStackTrace();
- } catch (RuntimeException e1) {
- e1.printStackTrace();
- }
-
- mCaller.ping();
- log(Thread.currentThread().getName() + " thread completed...");
- }
-}
diff --git a/src/com/android/exchange/EasService.java b/src/com/android/exchange/EasService.java
deleted file mode 100644
index 1204f11..0000000
--- a/src/com/android/exchange/EasService.java
+++ /dev/null
@@ -1,800 +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;
-
-import java.io.BufferedReader;
-import java.io.BufferedWriter;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.FileReader;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStreamWriter;
-import java.net.HttpURLConnection;
-import java.net.MalformedURLException;
-import java.net.ProtocolException;
-import java.net.URI;
-import java.net.URL;
-
-import java.util.Hashtable;
-
-import javax.net.ssl.HttpsURLConnection;
-
-import org.apache.http.HttpEntity;
-import org.apache.http.HttpResponse;
-import org.apache.http.client.methods.HttpPost;
-import org.apache.http.conn.ssl.AllowAllHostnameVerifier;
-import org.apache.http.impl.client.DefaultHttpClient;
-
-import com.android.email.Account;
-import com.android.email.mail.AuthenticationFailedException;
-import com.android.email.mail.MessagingException;
-import com.android.exchange.EmailContent.AttachmentColumns;
-import com.android.exchange.EmailContent.HostAuth;
-import com.android.exchange.EmailContent.Mailbox;
-import com.android.exchange.EmailContent.Message;
-
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.util.Log;
-
-public class EasService extends ProtocolService {
-
- public static final String TAG = "EasService";
-
- private static final String WINDOW_SIZE = "10";
-
- // From EAS spec
- // Mail Cal
- // 0 No filter Yes Yes
- // 1 1 day ago Yes No
- // 2 3 days ago Yes No
- // 3 1 week ago Yes No
- // 4 2 weeks ago Yes Yes
- // 5 1 month ago Yes Yes
- // 6 3 months ago No Yes
- // 7 6 months ago No Yes
-
- private static final String FILTER_ALL = "0";
- private static final String FILTER_1_DAY = "1";
- private static final String FILTER_3_DAYS = "2";
- private static final String FILTER_1_WEEK = "3";
- private static final String FILTER_2_WEEKS = "4";
- private static final String FILTER_1_MONTH = "5";
- //private static final String FILTER_3_MONTHS = "6";
- //private static final String FILTER_6_MONTHS = "7";
-
- private static final String BODY_PREFERENCE_TEXT = "1";
- //private static final String BODY_PREFERENCE_HTML = "2";
-
- // Reasonable to be static for now
- static String mProtocolVersion = "12.0"; //"2.5";
- static String mDeviceId = null;
- static String mDeviceType = "Android";
-
- String mAuthString = null;
- String mCmdString = null;
- String mVersions;
- String mHostAddress;
- String mUserName;
- String mPassword;
- boolean mSentCommands;
- boolean mIsIdle = false;
- Context mContext;
- InputStream mPendingPartInputStream = null;
-
- private boolean mStop = false;
- private Object mWaitTarget = new Object();
-
- public EasService (Context _context, Mailbox _mailbox) {
- // A comment
- super(_context, _mailbox);
- mContext = _context;
- }
-
- private EasService (String prefix) {
- super(prefix);
- }
-
- public EasService () {
- this("EAS Validation");
- }
-
- @Override
- public void ping() {
- // TODO Auto-generated method stub
- log("We've been pinged!");
- synchronized (mWaitTarget) {
- mWaitTarget.notify();
- }
- }
-
- @Override
- public void stop() {
- // TODO Auto-generated method stub
- mStop = true;
- }
-
- public int getSyncStatus () {
- return 0;
- }
-
- public void validateAccount (String hostAddress, String userName, String password,
- int port, boolean ssl, Context context) throws MessagingException {
- try {
- log("Testing EAS: " + hostAddress + ", " + userName + ", ssl = " + ssl);
- EASSerializer s = new EASSerializer();
- s.start("FolderSync").start("FolderSyncKey").text("0").end("FolderSyncKey")
- .end("FolderSync").end();
- String data = s.toString();
- EasService svc = new EasService("%TestAccount%");
- svc.mHostAddress = hostAddress;
- svc.mUserName = userName;
- svc.mPassword = password;
- HttpURLConnection uc = svc.sendEASPostCommand("FolderSync", data);
- int code = uc.getResponseCode();
- Log.v(TAG, "Validation response code: " + code);
- if (code == HttpURLConnection.HTTP_OK) {
- return;
- }
- if (code == 401 || code == 403) {
- Log.v(TAG, "Authentication failed");
- throw new AuthenticationFailedException("Validation failed");
- }
- else {
- //TODO Need to catch other kinds of errors (e.g. policy related)
- Log.v(TAG, "Validation failed, reporting I/O error");
- throw new MessagingException(MessagingException.IOERROR);
- }
- } catch (IOException e) {
- Log.v(TAG, "IOException caught, reporting I/O error: " + e.getMessage());
- throw new MessagingException(MessagingException.IOERROR);
- }
-
- }
-
- protected HttpURLConnection sendEASPostCommand (String cmd, String data) throws IOException {
- HttpURLConnection uc = setupEASCommand("POST", cmd);
- if (uc != null) {
- uc.setRequestProperty("Content-Length", Integer.toString(data.length() + 2));
- OutputStreamWriter w = new OutputStreamWriter(uc.getOutputStream(), "UTF-8");
- w.write(data);
- w.write("\r\n");
- w.flush();
- w.close();
- }
- return uc;
- }
-
- static private final int CHUNK_SIZE = 16*1024;
-
- protected void getAttachment (PartRequest req) throws IOException {
- DefaultHttpClient client = new DefaultHttpClient();
- String us = makeUriString("GetAttachment", "&AttachmentName=" + req.att.mLocation);
- HttpPost method = new HttpPost(URI.create(us));
- method.setHeader("Authorization", mAuthString);
-
- HttpResponse res = client.execute(method);
- int status = res.getStatusLine().getStatusCode();
- if (status == HttpURLConnection.HTTP_OK) {
- HttpEntity e = res.getEntity();
- int len = (int)e.getContentLength();
- String type = e.getContentType().getValue();
- Log.v(TAG, "Attachment code: " + status + ", Length: " + len + ", Type: " + type);
- InputStream is = res.getEntity().getContent();
- File f = null; //Attachment.openAttachmentFile(req);
- if (f != null) {
- FileOutputStream os = new FileOutputStream(f);
- if (len > 0) {
- try {
- mPendingPartRequest = req;
- mPendingPartInputStream = is;
- byte[] bytes = new byte[CHUNK_SIZE];
- int length = len;
- while (len > 0) {
- int n = (len > CHUNK_SIZE ? CHUNK_SIZE : len);
- int read = is.read(bytes, 0, n);
- os.write(bytes, 0, read);
- len -= read;
- if (req.handler != null) {
- long pct = ((length - len) * 100 / length);
- req.handler.sendEmptyMessage((int)pct);
- }
- }
- } finally {
- mPendingPartRequest = null;
- mPendingPartInputStream = null;
- }
- }
- os.flush();
- os.close();
-
- ContentValues cv = new ContentValues();
- cv.put(AttachmentColumns.CONTENT_URI, f.getAbsolutePath());
- cv.put(AttachmentColumns.MIME_TYPE, type);
- req.att.update(mContext, cv);
- // TODO Inform UI that we're done
- }
- }
- }
-
- private HttpURLConnection setupEASCommand (String method, String cmd) {
- return setupEASCommand(method, cmd, null);
- }
-
- private String makeUriString (String cmd, String extra) {
- if (mAuthString == null) {
- String cs = mUserName + ':' + mPassword;
- mAuthString = "Basic " + Base64.encodeBytes(cs.getBytes());
- mCmdString = "&User=" + mUserName + "&DeviceId=" + mDeviceId
- + "&DeviceType=" + mDeviceType;
- }
-
- boolean ssl = true;
-
- // TODO Remove after testing
- if (mHostAddress.equalsIgnoreCase("owa.electricmail.com")) {
- ssl = false;
- }
-
- String scheme = ssl ? "https" : "http";
- String us = scheme + "://" + mHostAddress + "/Microsoft-Server-ActiveSync";
- if (cmd != null) {
- us += "?Cmd=" + cmd + mCmdString;
- }
- if (extra != null) {
- us += extra;
- }
- return us;
- }
-
- private HttpURLConnection setupEASCommand (String method, String cmd, String extra) {
-
- // Hack for now
- boolean ssl = true;
-
- // TODO Remove this when no longer needed
- if (mHostAddress.equalsIgnoreCase("owa.electricmail.com")) {
- ssl = false;
- }
-
- try {
- String us = makeUriString(cmd, extra);
- URL u = new URL(us);
- HttpURLConnection uc = (HttpURLConnection)u.openConnection();
- try {
- HttpURLConnection.setFollowRedirects(true);
- } catch (Exception e) {
- }
-
- if (ssl) {
- ((HttpsURLConnection)uc).setHostnameVerifier(new AllowAllHostnameVerifier());
- }
-
- uc.setConnectTimeout(10*SECS);
- uc.setReadTimeout(20*MINS);
- if (method.equals("POST")) {
- uc.setDoOutput(true);
- }
- uc.setRequestMethod(method);
- uc.setRequestProperty("Authorization", mAuthString);
-
- if (extra == null) {
- if (cmd != null && cmd.startsWith("SendMail&")) {
- uc.setRequestProperty("Content-Type", "message/rfc822");
- } else {
- uc.setRequestProperty("Content-Type", "application/vnd.ms-sync.wbxml");
- }
- uc.setRequestProperty("MS-ASProtocolVersion", mProtocolVersion);
- uc.setRequestProperty("Connection", "keep-alive");
- uc.setRequestProperty("User-Agent", mDeviceType + "/0.3");
- } else {
- uc.setRequestProperty("Content-Length", "0");
- }
-
- return uc;
- } catch (MalformedURLException e) {
- e.printStackTrace();
- } catch (ProtocolException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- }
- return null;
- }
-
- static class EASSerializer extends WbxmlSerializer {
- ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
- static Hashtable<String, Object> tagTable = null;
-
- EASSerializer () {
- super();
- try {
- setOutput(byteStream, null);
- if (tagTable == null) {
- String[][] pages = EasTags.pages;
- for (int i = 0; i < pages.length; i++) {
- String[] page = pages[i];
- if (page.length > 0)
- setTagTable(i, page);
- }
- tagTable = getTagTable();
- } else {
- setTagTable(tagTable);
- }
- startDocument("UTF-8", false);
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
-
- EASSerializer start (String tag) throws IOException {
- startTag(null, tag);
- return this;
- }
-
- EASSerializer end (String tag) throws IOException {
- endTag(null, tag);
- return this;
- }
-
- EASSerializer end () throws IOException {
- endDocument();
- return this;
- }
-
- EASSerializer data (String tag, String value) throws IOException {
- startTag(null, tag);
- text(value);
- endTag(null, tag);
- return this;
- }
-
- EASSerializer tag (String tag) throws IOException {
- startTag(null, tag);
- endTag(null, tag);
- return this;
- }
-
- public EASSerializer text (String str) throws IOException {
- super.text(str);
- return this;
- }
-
- ByteArrayOutputStream getByteStream () {
- return byteStream;
- }
-
- public String toString () {
- return byteStream.toString();
- }
- }
-
- public void runMain () {
- try {
- if (mAccount.mSyncKey == null) {
- mAccount.mSyncKey = "0";
- Log.w(TAG, "Account syncKey RESET");
- mAccount.saveOrUpdate(mContext);
- }
- Log.v(TAG, "Account syncKey: " + mAccount.mSyncKey);
- HttpURLConnection uc = setupEASCommand("OPTIONS", null);
- if (uc != null) {
- int code = uc.getResponseCode();
- Log.v(TAG, "OPTIONS response: " + code);
- if (code == HttpURLConnection.HTTP_OK) {
- mVersions = uc.getHeaderField("ms-asprotocolversions");
- if (mVersions != null) {
- // Determine which version we want to use..
- //List<String> versions = new Chain(mVersions, ',').toList();
- //if (versions.contains("12.0")) {
- // mProtocolVersion = "12.0";
- //} else if (versions.contains("2.5"))
- mProtocolVersion = "2.5";
- Log.v(TAG, mVersions);
- }
- else {
- String s = readResponseString(uc);
- Log.e(TAG, "No EAS versions: " + s);
- }
-
- while (!mStop) {
- EASSerializer s = new EASSerializer();
- s.start("FolderSync").start("FolderSyncKey").text(mAccount.mSyncKey)
- .end("FolderSyncKey").end("FolderSync").end();
- String data = s.toString();
- uc = sendEASPostCommand("FolderSync", data);
- code = uc.getResponseCode();
- Log.v(TAG, "FolderSync response code: " + code);
- if (code == HttpURLConnection.HTTP_OK) {
- String encoding = uc.getHeaderField("Transfer-Encoding");
- if (encoding == null) {
- int len = uc.getHeaderFieldInt("Content-Length", 0);
- if (len > 0) {
- try {
- new EasFolderSyncParser(uc.getInputStream(), this).parse();
- } catch (IOException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- } else if (encoding.equalsIgnoreCase("chunked")) {
- // TODO We don't handle this yet
- }
- }
-
- // For now, we'll just loop
- try {
- Thread.sleep(15*MINS);
- } catch (InterruptedException e) {
- }
- }
- }
- }
-
- } catch (MalformedURLException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
-
- long handleLocalDeletes (EASSerializer s) throws IOException {
- long maxDeleteId = -1;
-// //**PROVIDER
-// Cursor c = Email.getLocalDeletedCursor(mDatabase, mMailboxId);
-// try {
-// if (c.moveToFirst()) {
-// s.start("Commands");
-// mSentCommands = true;
-// do {
-// String serverId = c.getString(Email.MPN_UID_COLUMN);
-// s.start("Delete").data("ServerId", serverId).end("Delete");
-// mLogger.log("Sending delete of " + serverId);
-// long id = c.getLong(Email.MPN_ID_COLUMN);
-// if (id > maxDeleteId)
-// maxDeleteId = id;
-// } while (c.moveToNext());
-// }
-// } finally {
-// c.close();
-// }
- return maxDeleteId;
- }
-
- void handleLocalMoves () throws IOException {
-// long maxMoveId = -1;
-//
-// Cursor c = LocalChange.getCursorWhere(mDatabase, "mailbox=\"" + mMailbox.mServerId + "\" and type=" + LocalChange.MOVE_TYPE);
-// try {
-// if (c.moveToFirst()) {
-// EASSerializer s = new EASSerializer();
-// s.start("MoveItems");
-//
-// do {
-// s.start("Move").data("SrcMsgId", c.getString(LocalChange.EMAIL_ID_COLUMN)).data("SrcFldId", c.getString(LocalChange.MAILBOX_COLUMN)).data("DstFldId", c.getString(LocalChange.VALUE_COLUMN)).end("Move");
-// } while (c.moveToNext());
-//
-// s.end("MoveItems").end();
-// HttpURLConnection uc = sendEASPostCommand("MoveItems", s.toString());
-// int code = uc.getResponseCode();
-// System.err.println("Response code: " + code);
-// if (code == HttpURLConnection.HTTP_OK) {
-// ByteArrayInputStream is = readResponse(uc);
-// if (is != null) {
-// EASMoveParser p = new EASMoveParser(is, this);
-// p.parse();
-// if (maxMoveId > -1)
-// LocalChange.deleteWhere(mDatabase, "_id<=" + maxMoveId + " AND mailbox=" + mMailboxId + " AND type=" + LocalChange.MOVE_TYPE);
-// }
-// } else {
-// // TODO What?
-// }
-// }
-// } finally {
-// c.close();
-// }
- }
-
- long handleLocalReads (EASSerializer s) throws IOException {
- Cursor c = mContext.getContentResolver().query(Message.CONTENT_URI, Message.LIST_PROJECTION, "mailboxKey=" + mMailboxId, null, null);
- long maxReadId = -1;
- try {
- // if (c.moveToFirst()) {
- // if (!mSentCommands) {
- // s.start("Commands");
- // mSentCommands = true;
- // }
- // do {
- // String serverId = c.getString(LocalChange.STRING_ARGS_COLUMN);
- // if (serverId == null) {
- // long id = c.getInt(LocalChange.EMAIL_ID_COLUMN);
- // Email.Message msg = Messages.restoreFromId(mContext, id);
- // serverId = msg.serverId;
- // if (serverId == null)
- // serverId = "0:0";
- // }
- //
- // String value = c.getString(LocalChange.VALUE_COLUMN);
- // s.start("Change").data("ServerId", serverId).start("ApplicationData").data("Read", value).end("ApplicationData").end("Change");
- // mLogger.log("Sending read of " + serverId + " = " + value);
- // long id = c.getLong(LocalChange.ID_COLUMN);
- // if (id > maxReadId)
- // maxReadId = id;
- // } while (c.moveToNext());
- // }
- } finally {
- c.close();
- }
- return maxReadId;
- //return -1;
- }
-
- 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;
- }
-
- private String getSimulatedDeviceId () {
- try {
- File f = mContext.getFileStreamPath("deviceName");
- BufferedReader rdr = null;
- String id;
- if (f.exists()) {
- rdr = new BufferedReader(new FileReader(f));
- id = rdr.readLine();
- rdr.close();
- return id;
- } else if (f.createNewFile()) {
- BufferedWriter w = new BufferedWriter(new FileWriter(f));
- id = "emu" + System.currentTimeMillis();
- w.write(id);
- w.close();
- }
- } catch (FileNotFoundException e) {
- } catch (IOException e) {
- }
- return null;
- }
-
- public void run() {
- mThread = Thread.currentThread();
- mDeviceId = android.provider.Settings.System
- .getString(mContext.getContentResolver(), android.provider.Settings.System.ANDROID_ID);
-
- HostAuth ha = HostAuth.restoreHostAuthWithId(mContext, mAccount.mHostAuthKeyRecv);
- mHostAddress = ha.mAddress;
- mUserName = ha.mLogin;
- mPassword = ha.mPassword;
-
- if (mDeviceId == null)
- mDeviceId = getSimulatedDeviceId();
- Log.v(TAG, "Device id: " + mDeviceId);
-
- if (mMailbox.mServerId.equals("_main")) {
- runMain();
- return;
- }
- try {
- while (!mStop) {
- runAwake();
- waitForConnectivity();
- try {
-// while (true) {
-// PartRequest req = null;
-// synchronized(mPartRequests) {
-// if (mPartRequests.isEmpty()) {
-// break;
-// }
-// req = mPartRequests.get(0);
-// getAttachment(req);
-// }
-//
-// synchronized(mPartRequests) {
-// mPartRequests.remove(req);
-// }
-// }
-
- boolean moreAvailable = true;
- while (!mStop && moreAvailable) {
- EASSerializer s = new EASSerializer();
- if (mMailbox.mSyncKey == null) {
- Log.w(TAG, "Mailbox syncKey RESET");
- mMailbox.mSyncKey = "0";
- }
- Log.v(TAG, "Mailbox syncKey: " + mMailbox.mSyncKey);
- s.start("Sync").start("Collections").start("Collection")
- .data("Class", "Email")
- .data("SyncKey", mMailbox.mSyncKey)
- .data("CollectionId", mMailbox.mServerId);
-
- // Set the lookback appropriately (EAS calls it a "filter")
- String filter = FILTER_1_WEEK;
- switch (mAccount.mSyncLookback) {
- case Account.SYNC_WINDOW_1_DAY: {
- filter = FILTER_1_DAY;
- break;
- }
- case Account.SYNC_WINDOW_3_DAYS: {
- filter = FILTER_3_DAYS;
- break;
- }
- case Account.SYNC_WINDOW_1_WEEK: {
- filter = FILTER_1_WEEK;
- break;
- }
- case Account.SYNC_WINDOW_2_WEEKS: {
- filter = FILTER_2_WEEKS;
- break;
- }
- case Account.SYNC_WINDOW_1_MONTH: {
- filter = FILTER_1_MONTH;
- break;
- }
- case Account.SYNC_WINDOW_ALL: {
- filter = FILTER_ALL;
- break;
- }
- }
-
- // For some crazy reason, GetChanges can't be used with a SyncKey of 0
- if (!mMailbox.mSyncKey.equals("0")) {
- if (mProtocolVersion.equals("12.0"))
- s.tag("DeletesAsMoves")
- .tag("GetChanges")
- .data("WindowSize", WINDOW_SIZE)
- .start("Options")
- .data("FilterType", filter)
- .start("BodyPreference")
- .data("BodyPreferenceType", BODY_PREFERENCE_TEXT) // Plain text to start
- .data("BodyPreferenceTruncationSize", "50000")
- .end("BodyPreference")
- .end("Options");
- else
- s.tag("DeletesAsMoves")
- .tag("GetChanges")
- .data("WindowSize", WINDOW_SIZE)
- .start("Options")
- .data("FilterType", filter)
- .end("Options");
- }
-
- // Send our changes up to the server
- mSentCommands = false;
-// // Send local deletes to server
-// long maxDeleteId = handleLocalDeletes(s);
-// // Send local read changes
-// long maxReadId = handleLocalReads(s);
- if (mSentCommands) {
- s.end("Commands");
- }
-
- s.end("Collection").end("Collections").end("Sync").end();
- HttpURLConnection uc = sendEASPostCommand("Sync", s.toString());
- int code = uc.getResponseCode();
- Log.v(TAG, "Sync response code: " + code);
- if (code == HttpURLConnection.HTTP_OK) {
- ByteArrayInputStream is = readResponse(uc);
- if (is != null) {
- EasEmailSyncParser p = new EasEmailSyncParser(is, this);
- p.parse();
-// if (maxDeleteId > -1)
-// Messages.deleteFromLocalDeletedWhere(mContext, "_id<=" + maxDeleteId);
-// if (maxReadId > -1)
-// LocalChange.deleteWhere(mDatabase, "_id<=" + maxReadId + " AND mailbox=" + mMailboxId + " AND type=" + LocalChange.READ_TYPE);
- moreAvailable = p.mMoreAvailable;
- }
- } else {
- // TODO What?
- }
- }
-
- // Handle local moves
- handleLocalMoves();
-
- if (mMailbox.mSyncFrequency != Account.CHECK_INTERVAL_PUSH) {
- return;
- }
-
- // Handle push here...
- Thread pingThread = null;
- EasPingService pingService = new EasPingService(mContext, mMailbox, this);
- runAsleep(10*MINS);
- synchronized (mWaitTarget) {
- mIsIdle = true;
- try {
- log("Wait...");
- pingThread = new Thread(pingService);
- pingThread.setName("Ping " + pingThread.getId());
- log("Starting thread " + pingThread.getName());
- pingThread.start();
- mWaitTarget.wait(14*MINS);
- } catch (InterruptedException e) {
- } finally {
- runAwake();
- }
- log("Wait terminated.");
- if (pingThread != null && pingThread.isAlive()) {
- // Make the ping service stop, one way or another
- log("Stopping " + pingThread.getName());
- pingService.stop();
- pingThread.interrupt();
- }
- mIsIdle = false;
- }
- } catch (IOException e) {
- log("IOException: " + e.getMessage());
- //logException(e);
- }
- }
- } catch (Exception e) {
- log("Exception: " + e.getMessage());
- //logException(e);
- } finally {
- log("EAS sync finished.");
- //MailService.done(this);
- }
- }
-
-}
diff --git a/src/com/android/exchange/EasSyncService.java b/src/com/android/exchange/EasSyncService.java
new file mode 100644
index 0000000..22a0d37
--- /dev/null
+++ b/src/com/android/exchange/EasSyncService.java
@@ -0,0 +1,769 @@
+/*
+ * 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;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStreamWriter;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.ProtocolException;
+import java.net.URI;
+import java.net.URL;
+import java.net.URLEncoder;
+
+import java.util.ArrayList;
+
+import javax.net.ssl.HttpsURLConnection;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.conn.ssl.AllowAllHostnameVerifier;
+import org.apache.http.impl.client.DefaultHttpClient;
+
+import com.android.email.mail.AuthenticationFailedException;
+import com.android.email.mail.MessagingException;
+import com.android.exchange.EmailContent.Account;
+import com.android.exchange.EmailContent.Attachment;
+import com.android.exchange.EmailContent.AttachmentColumns;
+import com.android.exchange.EmailContent.HostAuth;
+import com.android.exchange.EmailContent.Mailbox;
+import com.android.exchange.EmailContent.MailboxColumns;
+import com.android.exchange.adapter.EasContactsSyncAdapter;
+import com.android.exchange.adapter.EasEmailSyncAdapter;
+import com.android.exchange.adapter.EasFolderSyncParser;
+import com.android.exchange.adapter.EasPingParser;
+import com.android.exchange.adapter.EasSerializer;
+import com.android.exchange.adapter.EasSyncAdapter;
+import com.android.exchange.adapter.EasParser.EasParserException;
+import com.android.exchange.utility.Base64;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.util.Log;
+
+public class EasSyncService extends InteractiveSyncService {
+
+ private static final String WINDOW_SIZE = "10";
+ private static final String WHERE_ACCOUNT_KEY_AND_SERVER_ID =
+ MailboxColumns.ACCOUNT_KEY + "=? and " + MailboxColumns.SERVER_ID + "=?";
+ private static final String WHERE_SYNC_FREQUENCY_PING =
+ Mailbox.SYNC_FREQUENCY + '=' + Account.CHECK_INTERVAL_PING;
+ private static final String SYNC_FREQUENCY_PING =
+ MailboxColumns.SYNC_FREQUENCY + '=' + Account.CHECK_INTERVAL_PING;
+
+ // Reasonable default
+ String mProtocolVersion = "2.5";
+ static String mDeviceId = null;
+ static String mDeviceType = "Android";
+ EasSyncAdapter mTarget;
+ String mAuthString = null;
+ String mCmdString = null;
+ String mVersions;
+ public String mHostAddress;
+ public String mUserName;
+ public String mPassword;
+ String mDomain = null;
+ boolean mSentCommands;
+ boolean mIsIdle = false;
+ boolean mSsl = true;
+ public Context mContext;
+ public ContentResolver mContentResolver;
+ String[] mBindArguments = new String[2];
+ InputStream mPendingPartInputStream = null;
+ private boolean mStop = false;
+ private Object mWaitTarget = new Object();
+
+ public EasSyncService(Context _context, Mailbox _mailbox) {
+ super(_context, _mailbox);
+ mContext = _context;
+ mContentResolver = _context.getContentResolver();
+ HostAuth ha = HostAuth.restoreHostAuthWithId(_context, mAccount.mHostAuthKeyRecv);
+ mSsl = (ha.mFlags & HostAuth.FLAG_SSL) != 0;
+ }
+
+ private EasSyncService(String prefix) {
+ super(prefix);
+ }
+
+ public EasSyncService() {
+ this("EAS Validation");
+ }
+
+ @Override
+ public void ping() {
+ userLog("We've been pinged!");
+ synchronized (mWaitTarget) {
+ mWaitTarget.notify();
+ }
+ }
+
+ @Override
+ public void stop() {
+ mStop = true;
+ }
+
+ public int getSyncStatus() {
+ return 0;
+ }
+
+ /* (non-Javadoc)
+ * @see com.android.exchange.SyncService#validateAccount(java.lang.String, java.lang.String, java.lang.String, int, boolean, android.content.Context)
+ */
+ public void validateAccount(String hostAddress, String userName, String password, int port,
+ boolean ssl, Context context) throws MessagingException {
+ try {
+ if (Eas.USER_DEBUG) {
+ userLog("Testing EAS: " + hostAddress + ", " + userName + ", ssl = " + ssl);
+ }
+ EasSerializer s = new EasSerializer();
+ s.start("FolderSync").start("FolderSyncKey").text("0").end("FolderSyncKey")
+ .end("FolderSync").end();
+ EasSyncService svc = new EasSyncService("%TestAccount%");
+ svc.mHostAddress = hostAddress;
+ svc.mUserName = userName;
+ svc.mPassword = password;
+ svc.mSsl = ssl;
+ HttpURLConnection uc = svc.sendEASPostCommand("FolderSync", s.toString());
+ int code = uc.getResponseCode();
+ userLog("Validation response code: " + code);
+ if (code == HttpURLConnection.HTTP_OK) {
+ // No exception means successful validation
+ userLog("Validation successful");
+ return;
+ }
+ if (code == HttpURLConnection.HTTP_UNAUTHORIZED ||
+ code == HttpURLConnection.HTTP_FORBIDDEN) {
+ userLog("Authentication failed");
+ throw new AuthenticationFailedException("Validation failed");
+ } else {
+ // TODO Need to catch other kinds of errors (e.g. policy) For now, report the code.
+ userLog("Validation failed, reporting I/O error: " + code);
+ throw new MessagingException(MessagingException.IOERROR);
+ }
+ } catch (IOException e) {
+ userLog("IOException caught, reporting I/O error: " + e.getMessage());
+ throw new MessagingException(MessagingException.IOERROR);
+ }
+
+ }
+
+
+ @Override
+ public void loadAttachment(Attachment att, ISyncManagerCallback cb) {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void reloadFolderList() {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void startSync() {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void stopSync() {
+ // TODO Auto-generated method stub
+ }
+
+ protected HttpURLConnection sendEASPostCommand(String cmd, String data) throws IOException {
+ HttpURLConnection uc = setupEASCommand("POST", cmd);
+ if (uc != null) {
+ uc.setRequestProperty("Content-Length", Integer.toString(data.length() + 2));
+ OutputStreamWriter w = new OutputStreamWriter(uc.getOutputStream(), "UTF-8");
+ w.write(data);
+ w.write("\r\n");
+ w.flush();
+ w.close();
+ }
+ return uc;
+ }
+
+ static private final int CHUNK_SIZE = 16 * 1024;
+
+ protected void getAttachment(PartRequest req) throws IOException {
+ DefaultHttpClient client = new DefaultHttpClient();
+ String us = makeUriString("GetAttachment", "&AttachmentName=" + req.att.mLocation);
+ HttpPost method = new HttpPost(URI.create(us));
+ method.setHeader("Authorization", mAuthString);
+
+ HttpResponse res = client.execute(method);
+ int status = res.getStatusLine().getStatusCode();
+ if (status == HttpURLConnection.HTTP_OK) {
+ HttpEntity e = res.getEntity();
+ int len = (int)e.getContentLength();
+ String type = e.getContentType().getValue();
+ if (Eas.TEST_DEBUG) {
+ Log.v(TAG, "Attachment code: " + status + ", Length: " + len + ", Type: " + type);
+ }
+ InputStream is = res.getEntity().getContent();
+ // TODO Use the request data, when it's defined. For now, stubbed out
+ File f = null; // Attachment.openAttachmentFile(req);
+ if (f != null) {
+ FileOutputStream os = new FileOutputStream(f);
+ if (len > 0) {
+ try {
+ mPendingPartRequest = req;
+ mPendingPartInputStream = is;
+ byte[] bytes = new byte[CHUNK_SIZE];
+ int length = len;
+ while (len > 0) {
+ int n = (len > CHUNK_SIZE ? CHUNK_SIZE : len);
+ int read = is.read(bytes, 0, n);
+ os.write(bytes, 0, read);
+ len -= read;
+ if (req.handler != null) {
+ long pct = ((length - len) * 100 / length);
+ req.handler.sendEmptyMessage((int)pct);
+ }
+ }
+ } finally {
+ mPendingPartRequest = null;
+ mPendingPartInputStream = null;
+ }
+ }
+ os.flush();
+ os.close();
+
+ ContentValues cv = new ContentValues();
+ cv.put(AttachmentColumns.CONTENT_URI, f.getAbsolutePath());
+ cv.put(AttachmentColumns.MIME_TYPE, type);
+ req.att.update(mContext, cv);
+ // TODO Inform UI that we're done
+ }
+ }
+ }
+
+ private HttpURLConnection setupEASCommand(String method, String cmd) throws IOException {
+ return setupEASCommand(method, cmd, null);
+ }
+
+ private String makeUriString(String cmd, String extra) {
+ // Cache the authentication string and the command string
+ if (mDeviceId == null)
+ mDeviceId = "droidfu";
+ String safeUserName = URLEncoder.encode(mUserName);
+ if (mAuthString == null) {
+ String cs = mUserName + ':' + mPassword;
+ mAuthString = "Basic " + Base64.encodeBytes(cs.getBytes());
+ mCmdString = "&User=" + safeUserName + "&DeviceId=" + mDeviceId + "&DeviceType="
+ + mDeviceType;
+ }
+
+ String us = (mSsl ? "https" : "http") + "://" + mHostAddress +
+ "/Microsoft-Server-ActiveSync";
+ if (cmd != null) {
+ us += "?Cmd=" + cmd + mCmdString;
+ }
+ if (extra != null) {
+ us += extra;
+ }
+ return us;
+ }
+
+ private HttpURLConnection setupEASCommand(String method, String cmd, String extra)
+ throws IOException {
+ try {
+ String us = makeUriString(cmd, extra);
+ URL u = new URL(us);
+ HttpURLConnection uc = (HttpURLConnection)u.openConnection();
+ HttpURLConnection.setFollowRedirects(true);
+
+ if (mSsl) {
+ ((HttpsURLConnection)uc).setHostnameVerifier(new AllowAllHostnameVerifier());
+ }
+
+ uc.setConnectTimeout(10 * SECS);
+ uc.setReadTimeout(20 * MINS);
+ if (method.equals("POST")) {
+ uc.setDoOutput(true);
+ }
+ uc.setRequestMethod(method);
+ uc.setRequestProperty("Authorization", mAuthString);
+
+ if (extra == null) {
+ if (cmd != null && cmd.startsWith("SendMail&")) {
+ uc.setRequestProperty("Content-Type", "message/rfc822");
+ } else {
+ uc.setRequestProperty("Content-Type", "application/vnd.ms-sync.wbxml");
+ }
+ uc.setRequestProperty("MS-ASProtocolVersion", mProtocolVersion);
+ uc.setRequestProperty("Connection", "keep-alive");
+ uc.setRequestProperty("User-Agent", mDeviceType + '/' + Eas.VERSION);
+ } else {
+ uc.setRequestProperty("Content-Length", "0");
+ }
+
+ return uc;
+ } catch (MalformedURLException e) {
+ // TODO See if there is a better exception to throw here and below
+ throw new IOException();
+ } catch (ProtocolException e) {
+ throw new IOException();
+ }
+ }
+
+ String getTargetCollectionClassFromCursor(Cursor c) {
+ int type = c.getInt(Mailbox.CONTENT_TYPE_COLUMN);
+ if (type == Mailbox.TYPE_CONTACTS) {
+ return "Contacts";
+ } else if (type == Mailbox.TYPE_CALENDAR) {
+ return "Calendar";
+ } else {
+ return "Email";
+ }
+ }
+
+ /**
+ * Performs FolderSync
+ *
+ * @throws IOException
+ * @throws EasParserException
+ */
+ public void runMain() throws IOException, EasParserException {
+ try {
+ if (mAccount.mSyncKey == null) {
+ mAccount.mSyncKey = "0";
+ userLog("Account syncKey RESET");
+ mAccount.saveOrUpdate(mContext);
+ }
+
+ // When we first start up, change all ping mailboxes to push.
+ ContentValues cv = new ContentValues();
+ cv.put(Mailbox.SYNC_FREQUENCY, Account.CHECK_INTERVAL_PUSH);
+ if (mContentResolver.update(Mailbox.CONTENT_URI, cv,
+ WHERE_SYNC_FREQUENCY_PING, null) > 0) {
+ SyncManager.kick();
+ }
+
+ userLog("Account syncKey: " + mAccount.mSyncKey);
+ HttpURLConnection uc = setupEASCommand("OPTIONS", null);
+ if (uc != null) {
+ int code = uc.getResponseCode();
+ userLog("OPTIONS response: " + code);
+ if (code == HttpURLConnection.HTTP_OK) {
+ mVersions = uc.getHeaderField("ms-asprotocolversions");
+ if (mVersions != null) {
+ if (mVersions.contains("12.0")) {
+ mProtocolVersion = "12.0";
+ }
+ // TODO We only do 2.5 at the moment; add 'else' above when fixed
+ mProtocolVersion = "2.5";
+ userLog(mVersions);
+ } else {
+ throw new IOException();
+ }
+
+ while (!mStop) {
+ EasSerializer s = new EasSerializer();
+ s.start("FolderSync").start("FolderSyncKey").text(mAccount.mSyncKey).end(
+ "FolderSyncKey").end("FolderSync").end();
+ uc = sendEASPostCommand("FolderSync", s.toString());
+ code = uc.getResponseCode();
+ if (code == HttpURLConnection.HTTP_OK) {
+ String encoding = uc.getHeaderField("Transfer-Encoding");
+ if (encoding == null) {
+ int len = uc.getHeaderFieldInt("Content-Length", 0);
+ if (len > 0) {
+ InputStream is = uc.getInputStream();
+ // Returns true if we need to sync again
+ if (new EasFolderSyncParser(is, this).parse()) {
+ continue;
+ }
+ }
+ } else if (encoding.equalsIgnoreCase("chunked")) {
+ // TODO We don't handle this yet
+ }
+ } else {
+ userLog("FolderSync response error: " + code);
+ }
+
+ // Wait for push notifications.
+ try {
+ runPingLoop();
+ } catch (StaleFolderListException e) {
+ // We break out if we get told about a stale folder list
+ userLog("Ping interrupted; folder list requires sync...");
+ }
+ }
+ }
+ }
+ } catch (MalformedURLException e) {
+ throw new IOException();
+ }
+ }
+
+ void runPingLoop() throws IOException, StaleFolderListException {
+ // Do push for all sync services here
+ long endTime = System.currentTimeMillis() + (30*MINS);
+
+ while (System.currentTimeMillis() < endTime) {
+ // Count of pushable mailboxes
+ int pushCount = 0;
+ // Count of mailboxes that can be pushed right now
+ int canPushCount = 0;
+ EasSerializer s = new EasSerializer();
+ HttpURLConnection uc;
+ int code;
+ Cursor c = mContentResolver.query(Mailbox.CONTENT_URI, Mailbox.CONTENT_PROJECTION,
+ MailboxColumns.ACCOUNT_KEY + '=' + mAccount.mId + " and " + SYNC_FREQUENCY_PING,
+ null, null);
+
+ try {
+ // Loop through our pushed boxes seeing what is available to push
+ while (c.moveToNext()) {
+ pushCount++;
+ // Two requirements for push:
+ // 1) SyncManager tells us the mailbox is syncable (not running, not stopped)
+ // 2) The syncKey isn't "0" (i.e. it's synced at least once)
+ if (SyncManager.canSync(c.getLong(Mailbox.CONTENT_ID_COLUMN))) {
+ String syncKey = c.getString(Mailbox.CONTENT_SYNC_KEY_COLUMN);
+ if (syncKey == null || syncKey.equals("0")) {
+ continue;
+ }
+ if (canPushCount++ == 0) {
+ // Initialize the Ping command
+ s.start("Ping").data("HeartbeatInterval", "900").start("PingFolders");
+ }
+ // 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("PingFolder")
+ .data("PingId", c.getString(Mailbox.CONTENT_SERVER_ID_COLUMN))
+ .data("PingClass", folderClass)
+ .end("PingFolder");
+ }
+ }
+ } finally {
+ c.close();
+ }
+
+ if (canPushCount > 0) {
+ // If we have some number that are ready for push, send Ping to the server
+ s.end("PingFolders").end("Ping").end();
+ uc = sendEASPostCommand("Ping", s.toString());
+ userLog("Sending ping, timeout: " + uc.getReadTimeout() / 1000 + "s");
+ code = uc.getResponseCode();
+ userLog("Ping response: " + code);
+ if (code == HttpURLConnection.HTTP_OK) {
+ String encoding = uc.getHeaderField("Transfer-Encoding");
+ if (encoding == null) {
+ int len = uc.getHeaderFieldInt("Content-Length", 0);
+ if (len > 0) {
+ parsePingResult(uc, mContentResolver);
+ } else {
+ // This implies a connection issue that we can't handle
+ throw new IOException();
+ }
+ } else {
+ // It shouldn't be possible for EAS server to send chunked data here
+ throw new IOException();
+ }
+ } else if (code == HttpURLConnection.HTTP_UNAUTHORIZED ||
+ code == HttpURLConnection.HTTP_FORBIDDEN) {
+ mExitStatus = AbstractSyncService.EXIT_LOGIN_FAILURE;
+ userLog("Authorization error during Ping: " + code);
+ throw new IOException();
+ }
+ } else if (pushCount > 0) {
+ // If we want to Ping, but can't just yet, wait 10 seconds and try again
+ sleep(10*SECS);
+ } else {
+ // We've got nothing to do, so let's hang out for a while
+ sleep(10*MINS);
+ }
+ }
+ }
+
+ void sleep(long ms) {
+ try {
+ Thread.sleep(ms);
+ } catch (InterruptedException e) {
+ // Doesn't matter whether we stop early; it's the thought that counts
+ }
+ }
+
+ void parsePingResult(HttpURLConnection uc, ContentResolver cr)
+ throws IOException, StaleFolderListException {
+ EasPingParser pp = new EasPingParser(uc.getInputStream(), this);
+ if (pp.parse()) {
+ // True indicates some mailboxes need syncing...
+ // syncList has the serverId's of the mailboxes...
+ mBindArguments[0] = Long.toString(mAccount.mId);
+ ArrayList<String> syncList = pp.getSyncList();
+ for (String serverId: syncList) {
+ mBindArguments[1] = serverId;
+ Cursor c = cr.query(Mailbox.CONTENT_URI, Mailbox.CONTENT_PROJECTION,
+ WHERE_ACCOUNT_KEY_AND_SERVER_ID, mBindArguments, null);
+ try {
+ if (c.moveToFirst()) {
+ SyncManager.startManualSync(c.getLong(Mailbox.CONTENT_ID_COLUMN));
+ }
+ } finally {
+ c.close();
+ }
+ }
+ }
+ }
+
+ 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;
+ }
+
+ /**
+ * EAS requires a unique device id, so that sync is possible from a variety of different
+ * devices (e.g. the syncKey is specific to a device) If we're on an emulator or some other
+ * device that doesn't provide one, we can create it as droid<n> where <n> is system time.
+ * This would work on a real device as well, but it would be better to use the "real" id if
+ * it's available
+ */
+ private String getSimulatedDeviceId() {
+ try {
+ File f = mContext.getFileStreamPath("deviceName");
+ BufferedReader rdr = null;
+ String id;
+ if (f.exists() && f.canRead()) {
+ rdr = new BufferedReader(new FileReader(f));
+ id = rdr.readLine();
+ rdr.close();
+ return id;
+ } else if (f.createNewFile()) {
+ BufferedWriter w = new BufferedWriter(new FileWriter(f));
+ id = "droid" + System.currentTimeMillis();
+ w.write(id);
+ w.close();
+ }
+ } catch (FileNotFoundException e) {
+ // We'll just use the default below
+ } catch (IOException e) {
+ // We'll just use the default below
+ }
+ return "droid0";
+ }
+
+ /**
+ * Common code to sync E+PIM data
+ *
+ * @param target, an EasMailbox, EasContacts, or EasCalendar object
+ */
+ public void sync(EasSyncAdapter target) throws IOException {
+ mTarget = target;
+ Mailbox mailbox = target.mMailbox;
+
+ boolean moreAvailable = true;
+ while (!mStop && moreAvailable) {
+ runAwake();
+ waitForConnectivity();
+
+ EasSerializer s = new EasSerializer();
+ if (mailbox.mSyncKey == null) {
+ userLog("Mailbox syncKey RESET");
+ mailbox.mSyncKey = "0";
+ mailbox.mSyncFrequency = Account.CHECK_INTERVAL_PUSH;
+ }
+ String className = target.getCollectionName();
+ userLog("Sending " + className + " syncKey: " + mailbox.mSyncKey);
+ s.start("Sync")
+ .start("Collections")
+ .start("Collection")
+ .data("Class", className)
+ .data("SyncKey", mailbox.mSyncKey)
+ .data("CollectionId", mailbox.mServerId)
+ .tag("DeletesAsMoves");
+
+ // EAS doesn't like GetChanges if the syncKey is "0"; not documented
+ if (!mailbox.mSyncKey.equals("0")) {
+ s.tag("GetChanges");
+ }
+ s.data("WindowSize", WINDOW_SIZE);
+ boolean options = false;
+ if (!className.equals("Contacts")) {
+ options = true;
+ // Set the lookback appropriately (EAS calls this a "filter")
+ String filter = Eas.FILTER_1_WEEK;
+ switch (mAccount.mSyncLookback) {
+ case com.android.email.Account.SYNC_WINDOW_1_DAY: {
+ filter = Eas.FILTER_1_DAY;
+ break;
+ }
+ case com.android.email.Account.SYNC_WINDOW_3_DAYS: {
+ filter = Eas.FILTER_3_DAYS;
+ break;
+ }
+ case com.android.email.Account.SYNC_WINDOW_1_WEEK: {
+ filter = Eas.FILTER_1_WEEK;
+ break;
+ }
+ case com.android.email.Account.SYNC_WINDOW_2_WEEKS: {
+ filter = Eas.FILTER_2_WEEKS;
+ break;
+ }
+ case com.android.email.Account.SYNC_WINDOW_1_MONTH: {
+ filter = Eas.FILTER_1_MONTH;
+ break;
+ }
+ case com.android.email.Account.SYNC_WINDOW_ALL: {
+ filter = Eas.FILTER_ALL;
+ break;
+ }
+ }
+ s.start("Options")
+ .data("FilterType", filter);
+ }
+ if (mProtocolVersion.equals("12.0")) {
+ if (!options) {
+ options = true;
+ s.start("Options");
+ s.start("BodyPreference")
+ // Plain text to start
+ .data("BodyPreferenceType", Eas.BODY_PREFERENCE_TEXT)
+ .data("BodyPreferenceTruncationSize", Eas.DEFAULT_BODY_TRUNCATION_SIZE)
+ .end("BodyPreference");
+ }
+ }
+ if (options) {
+ s.end("Options");
+ }
+
+ // Send our changes up to the server
+ target.sendLocalChanges(s, this);
+
+ s.end("Collection").end("Collections").end("Sync").end();
+ HttpURLConnection uc = sendEASPostCommand("Sync", s.toString());
+ int code = uc.getResponseCode();
+ if (code == HttpURLConnection.HTTP_OK) {
+ ByteArrayInputStream is = readResponse(uc);
+ if (is != null) {
+ moreAvailable = target.parse(is, this);
+ }
+ } else {
+ userLog("Sync response error: " + code);
+ if (code == HttpURLConnection.HTTP_UNAUTHORIZED ||
+ code == HttpURLConnection.HTTP_FORBIDDEN) {
+ mExitStatus = AbstractSyncService.EXIT_LOGIN_FAILURE;
+ }
+ return;
+ }
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Runnable#run()
+ */
+ public void run() {
+ mThread = Thread.currentThread();
+ TAG = mThread.getName();
+ mDeviceId = android.provider.Settings.System.getString(mContext.getContentResolver(),
+ android.provider.Settings.System.ANDROID_ID);
+ // Generate a device id if we don't have one
+ if (mDeviceId == null) {
+ mDeviceId = getSimulatedDeviceId();
+ }
+ HostAuth ha = HostAuth.restoreHostAuthWithId(mContext, mAccount.mHostAuthKeyRecv);
+ mHostAddress = ha.mAddress;
+ mUserName = ha.mLogin;
+ mPassword = ha.mPassword;
+
+ try {
+ if (mMailbox.mServerId.equals("_main")) {
+ runMain();
+ } else {
+ EasSyncAdapter target;
+ if (mMailbox.mType == Mailbox.TYPE_CONTACTS)
+ target = new EasContactsSyncAdapter(mMailbox);
+ else {
+ target = new EasEmailSyncAdapter(mMailbox);
+ }
+ // We loop here because someone might have put a request in while we were syncing
+ // and we've missed that opportunity...
+ do {
+ if (mRequestTime != 0) {
+ userLog("Looping for user request...");
+ mRequestTime = 0;
+ }
+ sync(target);
+ } while (mRequestTime != 0);
+ }
+ mExitStatus = EXIT_DONE;
+ } catch (IOException e) {
+ userLog("Caught IOException");
+ mExitStatus = EXIT_IO_ERROR;
+ } catch (Exception e) {
+ e.printStackTrace();
+ } finally {
+ userLog(mMailbox.mDisplayName + ": sync finished");
+ SyncManager.done(this);
+ }
+ }
+}
diff --git a/src/com/android/exchange/EasTags.java b/src/com/android/exchange/EasTags.java
deleted file mode 100644
index f68da26..0000000
--- a/src/com/android/exchange/EasTags.java
+++ /dev/null
@@ -1,345 +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;
-
-public class EasTags {
-
- static final int AIRSYNC = 0x00;
- static final int CONTACTS = 0x01;
- static final int EMAIL = 0x02;
- static final int FOLDER = 0x07;
- static final int PING = 0x0D;
- static final int GAL = 0x10;
-
- static final int SYNC_SYNC = 5;
- static final int SYNC_RESPONSES = 6;
- static final int SYNC_ADD = 7;
- static final int SYNC_CHANGE = 8;
- static final int SYNC_DELETE = 9;
- static final int SYNC_FETCH = 0xA;
- static final int SYNC_SYNC_KEY = 0xB;
- static final int SYNC_CLIENT_ID = 0xC;
- static final int SYNC_SERVER_ID = 0xD;
- static final int SYNC_STATUS = 0xE;
- static final int SYNC_COLLECTION = 0xF;
- static final int SYNC_CLASS = 0x10;
- static final int SYNC_VERSION = 0x11;
- static final int SYNC_COLLECTION_ID = 0x12;
- static final int SYNC_GET_CHANGES = 0x13;
- static final int SYNC_MORE_AVAILABLE = 0x14;
- static final int SYNC_WINDOW_SIZE = 0x15;
- static final int SYNC_COMMANDS = 0x16;
- static final int SYNC_OPTIONS = 0x17;
- static final int SYNC_FILTER_TYPE = 0x18;
- static final int SYNC_TRUNCATION = 0x19;
- static final int SYNC_RTF_TRUNCATION = 0x1A;
- static final int SYNC_CONFLICT = 0x1B;
- static final int SYNC_COLLECTIONS = 0x1C;
- static final int SYNC_APPLICATION_DATA = 0x1D;
- static final int SYNC_DELETES_AS_MOVES = 0x1E;
- static final int SYNC_NOTIFY_GUID = 0x1F;
- static final int SYNC_SUPPORTED = 0x20;
- static final int SYNC_SOFT_DELETE = 0x21;
- static final int SYNC_MIME_SUPPORT = 0x22;
- static final int SYNC_MIME_TRUNCATION = 0x23;
- static final int SYNC_WAIT = 0x24;
- static final int SYNC_LIMIT = 0x25;
- static final int SYNC_PARTIAL = 0x26;
-
- static final int CALENDAR_TIME_ZONE = 5;
- static final int CALENDAR_ALL_DAY_EVENT = 6;
- static final int CALENDAR_ATTENDEES = 7;
- static final int CALENDAR_ATTENDEE = 8;
- static final int CALENDAR_ATTENDEE_EMAIL = 9;
- static final int CALENDAR_ATTENDEE_NAME = 0xA;
- static final int CALENDAR_BODY = 0xB;
- static final int CALENDAR_BODY_TRUNCATED = 0xC;
- static final int CALENDAR_BUSY_STATUS = 0xD;
- static final int CALENDAR_CATEGORIES = 0xE;
- static final int CALENDAR_CATEGORY = 0xF;
- static final int CALENDAR_COMPRESSED_RTF = 0x10;
- static final int CALENDAR_DTSTAMP = 0x11;
- static final int CALENDAR_END_TIME = 0x12;
- static final int CALENDAR_EXCEPTION = 0x13;
- static final int CALENDAR_EXCEPTIONS = 0x14;
- static final int CALENDAR_EXCEPTION_IS_DELETED = 0x15;
- static final int CALENDAR_EXCEPTION_START_TIME = 0x16;
- static final int CALENDAR_LOCATION = 0x17;
- static final int CALENDAR_MEETING_STATUS = 0x18;
- static final int CALENDAR_ORGANIZER_EMAIL = 0x19;
- static final int CALENDAR_ORGANIZER_NAME = 0x1A;
- static final int CALENDAR_RECURRENCE = 0x1B;
- static final int CALENDAR_RECURRENCE_TYPE = 0x1C;
- static final int CALENDAR_RECURRENCE_UNTIL = 0x1D;
- static final int CALENDAR_RECURRENCE_OCCURRENCES = 0x1E;
- static final int CALENDAR_RECURRENCE_INTERVAL = 0x1F;
- static final int CALENDAR_RECURRENCE_DAYOFWEEK = 0x20;
- static final int CALENDAR_RECURRENCE_DAYOFMONTH = 0x21;
- static final int CALENDAR_RECURRENCE_WEEKOFMONTH = 0x22;
- static final int CALENDAR_RECURRENCE_MONTHOFYEAR = 0x23;
- static final int CALENDAR_REMINDER_MINS_BEFORE = 0x24;
- static final int CALENDAR_SENSITIVITY = 0x25;
- static final int CALENDAR_SUBJECT = 0x26;
- static final int CALENDAR_START_TIME = 0x27;
- static final int CALENDAR_UID = 0x28;
- static final int CALENDAR_ATTENDEE_STATUS = 0x29;
- static final int CALENDAR_ATTENDEE_TYPE = 0x2A;
-
- static final int FOLDER_FOLDERS = 5;
- static final int FOLDER_FOLDER = 6;
- static final int FOLDER_DISPLAY_NAME = 7;
- static final int FOLDER_SERVER_ID = 8;
- static final int FOLDER_PARENT_ID = 9;
- static final int FOLDER_TYPE = 0xA;
- static final int FOLDER_RESPONSE = 0xB;
- static final int FOLDER_STATUS = 0xC;
- static final int FOLDER_CONTENT_CLASS = 0xD;
- static final int FOLDER_CHANGES = 0xE;
- static final int FOLDER_ADD = 0xF;
- static final int FOLDER_DELETE = 0x10;
- static final int FOLDER_UPDATE = 0x11;
- static final int FOLDER_SYNC_KEY = 0x12;
- static final int FOLDER_FOLDER_CREATE = 0x13;
- static final int FOLDER_FOLDER_DELETE= 0x14;
- static final int FOLDER_FOLDER_UPDATE = 0x15;
- static final int FOLDER_FOLDER_SYNC = 0x16;
- static final int FOLDER_COUNT = 0x17;
- static final int FOLDER_VERSION = 0x18;
-
- static final int EMAIL_ATTACHMENT = 5;
- static final int EMAIL_ATTACHMENTS = 6;
- static final int EMAIL_ATT_NAME = 7;
- static final int EMAIL_ATT_SIZE = 8;
- static final int EMAIL_ATT0ID = 9;
- static final int EMAIL_ATT_METHOD = 0xA;
- static final int EMAIL_ATT_REMOVED = 0xB;
- static final int EMAIL_BODY = 0xC;
- static final int EMAIL_BODY_SIZE = 0xD;
- static final int EMAIL_BODY_TRUNCATED = 0xE;
- static final int EMAIL_DATE_RECEIVED = 0xF;
- static final int EMAIL_DISPLAY_NAME = 0x10;
- static final int EMAIL_DISPLAY_TO = 0x11;
- static final int EMAIL_IMPORTANCE = 0x12;
- static final int EMAIL_MESSAGE_CLASS = 0x13;
- static final int EMAIL_SUBJECT = 0x14;
- static final int EMAIL_READ = 0x15;
- static final int EMAIL_TO = 0x16;
- static final int EMAIL_CC = 0x17;
- static final int EMAIL_FROM = 0x18;
- static final int EMAIL_REPLY_TO = 0x19;
- static final int EMAIL_ALL_DAY_EVENT = 0x1A;
- static final int EMAIL_CATEGORIES = 0x1B;
- static final int EMAIL_CATEGORY = 0x1C;
- static final int EMAIL_DTSTAMP = 0x1D;
- static final int EMAIL_END_TIME = 0x1E;
- static final int EMAIL_INSTANCE_TYPE = 0x1F;
- static final int EMAIL_INTD_BUSY_STATUS = 0x20;
- static final int EMAIL_LOCATION = 0x21;
- static final int EMAIL_MEETING_REQUEST = 0x22;
- static final int EMAIL_ORGANIZER = 0x23;
- static final int EMAIL_RECURRENCE_ID = 0x24;
- static final int EMAIL_REMINDER = 0x25;
- static final int EMAIL_RESPONSE_REQUESTED = 0x26;
- static final int EMAIL_RECURRENCES = 0x27;
- static final int EMAIL_RECURRENCE = 0x28;
- static final int EMAIL_RECURRENCE_TYPE = 0x29;
- static final int EMAIL_RECURRENCE_UNTIL = 0x2A;
- static final int EMAIL_RECURRENCE_OCCURRENCES = 0x2B;
- static final int EMAIL_RECURRENCE_INTERVAL = 0x2C;
- static final int EMAIL_RECURRENCE_DAYOFWEEK = 0x2D;
- static final int EMAIL_RECURRENCE_DAYOFMONTH = 0x2E;
- static final int EMAIL_RECURRENCE_WEEKOFMONTH = 0x2F;
- static final int EMAIL_RECURRENCE_MONTHOFYEAR = 0x30;
- static final int EMAIL_START_TIME = 0x31;
- static final int EMAIL_SENSITIVITY = 0x32;
- static final int EMAIL_TIME_ZONE = 0x33;
- static final int EMAIL_GLOBAL_OBJID = 0x34;
- static final int EMAIL_THREAD_TOPIC = 0x35;
- static final int EMAIL_MIME_DATA = 0x36;
- static final int EMAIL_MIME_TRUNCATED = 0x37;
- static final int EMAIL_MIME_SIZE = 0x38;
- static final int EMAIL_INTERNET_CPID = 0x39;
- static final int EMAIL_FLAG = 0x3A;
- static final int EMAIL_FLAG_STATUS = 0x3B;
- static final int EMAIL_CONTENT_CLASS = 0x3C;
- static final int EMAIL_FLAG_TYPE = 0x3D;
- static final int EMAIL_COMPLETE_TIME = 0x3E;
-
- static final int MOVE_MOVE_ITEMS = 5;
- static final int MOVE_MOVE = 6;
- static final int MOVE_SRCMSGID = 7;
- static final int MOVE_SRCFLDID = 8;
- static final int MOVE_DSTFLDID = 9;
- static final int MOVE_RESPONSE = 0xA;
- static final int MOVE_STATUS = 0xB;
- static final int MOVE_DSTMSGID = 0xC;
-
- static final int PING_PING = 5;
- static final int PING_AUTD_STATE = 6;
- static final int PING_STATUS = 7;
- static final int PING_HEARTBEAT_INTERVAL = 8;
- static final int PING_FOLDERS = 9;
- static final int PING_FOLDER = 0xA;
- static final int PING_ID = 0xB;
- static final int PING_CLASS = 0xC;
- static final int PING_MAX_FOLDERS = 0xD;
-
- static final int BASE_BODY_PREFERENCE = 5;
- static final int BASE_TYPE = 6;
- static final int BASE_TRUNCATION_SIZE = 7;
- static final int BASE_ALL_OR_NONE = 8;
- static final int BASE_RESERVED = 9;
- static final int BASE_BODY = 0xA;
- static final int BASE_DATA = 0xB;
- static final int BASE_ESTIMATED_DATA_SIZE = 0xC;
- static final int BASE_TRUNCATED = 0xD;
- static final int BASE_ATTACHMENTS = 0xE;
- static final int BASE_ATTACHMENT = 0xF;
- static final int BASE_DISPLAY_NAME = 0x10;
- static final int BASE_FILE_REFERENCE = 0x11;
- static final int BASE_METHOD = 0x12;
- static final int BASE_CONTENT_ID = 0x13;
- static final int BASE_CONTENT_LOCATION = 0x14;
- static final int BASE_IS_INLINE = 0x15;
- static final int BASE_NATIVE_BODY_TYPE = 0x16;
- static final int BASE_CONTENT_TYPE = 0x17;
-
- static public String[][] pages = {
- { // 0x00 AirSync
- "Sync", "Responses", "Add", "Change", "Delete", "Fetch", "SyncKey", "ClientId",
- "ServerId", "Status", "Collection", "Class", "Version", "CollectionId", "GetChanges",
- "MoreAvailable", "WindowSize", "Commands", "Options", "FilterType", "Truncation",
- "RTFTruncation", "Conflict", "Collections", "ApplicationData", "DeletesAsMoves",
- "NotifyGUID", "Supported", "SoftDelete", "MIMESupport", "MIMETruncation", "Wait",
- "Limit", "Partial"
- },
- {
- // 0x01 Contacts
- },
- {
- // 0x02 Email
- "Attachment", "Attachments", "AttName", "AttSize", "Add0Id", "AttMethod", "AttRemoved",
- "Body", "BodySize", "BodyTruncated", "DateReceived", "DisplayName", "DisplayTo",
- "Importance", "MessageClass", "Subject", "Read", "To", "CC", "From", "ReplyTo",
- "AllDayEvent", "Categories", "Category", "DTStamp", "EndTime", "InstanceType",
- "IntDBusyStatus", "Location", "MeetingRequest", "Organizer", "RecurrenceId", "Reminder",
- "ResponseRequested", "Recurrences", "Recurence", "Recurrence_Type", "Recurrence_Until",
- "Recurrence_Occurrences", "Recurrence_Interval", "Recurrence_DayOfWeek",
- "Recurrence_DayOfMonth", "Recurrence_WeekOfMonth", "Recurrence_MonthOfYear",
- "StartTime", "Sensitivity", "TimeZone", "GlobalObjId", "ThreadTopic", "MIMEData",
- "MIMETruncated", "MIMESize", "InternetCPID", "Flag", "FlagStatus", "ContentClass",
- "FlagType", "CompleteTime"
- },
- {
- // 0x03 AirNotify
- },
- {
- // 0x04 Calendar
- "CalTimeZone", "CalAllDayEvent", "CalAttendees", "CalAttendee", "CalAttendee_Email",
- "CalAttendee_Name", "CalBody", "CalBodyTruncated", "CalBusyStatus", "CalCategories",
- "CalCategory", "CalCompressed_RTF", "CalDTStamp", "CalEndTime", "CalExeption",
- "CalExceptions", "CalException_IsDeleted", "CalException_StartTime", "CalLocation",
- "CalMeetingStatus", "CalOrganizer_Email", "CalOrganizer_Name", "CalRecurrence",
- "CalRecurrence_Type", "CalRecurrence_Until", "CalRecurrence_Occurrences",
- "CalRecurrence_Interval", "CalRecurrence_DayOfWeek", "CalRecurrence_DayOfMonth",
- "CalRecurrence_WeekOfMonth", "CalRecurrence_MonthOfYear", "CalReminder_MinsBefore",
- "CalSensitivity", "CalSubject", "CalStartTime", "CalUID", "CalAttendee_Status",
- "CalAttendee_Type"
- },
- {
- // 0x05 Move
- "MoveItems", "Move", "SrcMsgId", "SrcFldId", "DstFldId", "Response", "Status",
- "DstMsgId"
- },
- {
- // 0x06 ItemEstimate
- },
- {
- // 0x07 FolderHierarchy
- "Folders", "Folder", "FolderDisplayName", "FolderServerId", "FolderParentId", "Type",
- "Response", "Status", "ContentClass", "Changes", "FolderAdd", "FolderDelete",
- "FolderUpdate", "FolderSyncKey", "FolderCreate", "FolderDelete", "FolderUpdate",
- "FolderSync", "Count", "Version"
- },
- {
- // 0x08 MeetingResponse
- },
- {
- // 0x09 Tasks
- },
- {
- // 0x0A ResolveRecipients
- },
- {
- // 0x0B ValidateCert
- },
- {
- // 0x0C Contacts2
- },
- {
- // 0x0D Ping
- "Ping", "AutdState", "Status", "HeartbeatInterval", "PingFolders", "PingFolder",
- "PingId", "PingClass", "MaxFolders"
- },
- {
- // 0x0E Provision
- "Provision", "Policies", "Policy", "PolicyType", "PolicyKey", "Data", "Status",
- "RemoteWipe", "EASProvidionDoc", "DevicePasswordEnabled",
- "AlphanumericDevicePasswordRequired",
- "DeviceEncryptionEnabled", "-unused-", "AttachmentsEnabled", "MinDevicePasswordLength",
- "MaxInactivityTimeDeviceLock", "MaxDevicePasswordFailedAttempts", "MaxAttachmentSize",
- "AllowSimpleDevicePassword", "DevicePasswordExpiration", "DevicePasswordHistory",
- "AllowStorageCard", "AllowCamera", "RequireDeviceEncryption",
- "AllowUnsignedApplications", "AllowUnsignedInstallationPackages",
- "MinDevicePasswordComplexCharacters", "AllowWiFi", "AllowTextMessaging",
- "AllowPOPIMAPEmail", "AllowBluetooth", "AllowIrDA", "RequireManualSyncWhenRoaming",
- "AllowDesktopSync",
- "MaxCalendarAgeFilder", "AllowHTMLEmail", "MaxEmailAgeFilder",
- "MaxEmailBodyTruncationSize", "MaxEmailHTMLBodyTruncationSize",
- "RequireSignedSMIMEMessages", "RequireEncryptedSMIMEMessages",
- "RequireSignedSMIMEAlgorithm", "RequireEncryptionSMIMEAlgorithm",
- "AllowSMIMEEncryptionAlgorithmNegotiation", "AllowSMIMESoftCerts", "AllowBrowser",
- "AllowConsumerEmail", "AllowRemoteDesktop", "AllowInternetSharing",
- "UnapprovedInROMApplicationList", "ApplicationName", "ApprovedApplicationList", "Hash"
- },
- {
- // 0x0F Search
- },
- {
- // 0x10 Gal
- "DisplayName", "Phone", "Office", "Title", "Company", "Alias", "FirstName", "LastName",
- "HomePhone", "MobilePhone", "EmailAddress"
- },
- {
- // 0x11 AirSyncBase
- "BodyPreference", "BodyPreferenceType", "BodyPreferenceTruncationSize", "AllOrNone",
- "Body", "Data", "EstimatedDataSize", "Truncated", "Attachments", "Attachment",
- "DisplayName", "FileReference", "Method", "ContentId", "ContentLocation", "IsInline",
- "NativeBodyType", "ContentType"
- },
- {
- // 0x12 Settings
- },
- {
- // 0x13 DocumentLibrary
- },
- {
- // 0x14 ItemOperations
- }
- };
-}
diff --git a/src/com/android/exchange/EmailContent.java b/src/com/android/exchange/EmailContent.java
index e75a534..5998b25 100644
--- a/src/com/android/exchange/EmailContent.java
+++ b/src/com/android/exchange/EmailContent.java
@@ -14,6 +14,12 @@
* limitations under the License.
*/
+/**
+ * This is a local copy of com.android.email.EmailProvider
+ *
+ * Last copied from com.android.email.EmailProvider on 7/2/09
+ */
+
package com.android.exchange;
import com.android.email.R;
@@ -223,10 +229,7 @@
public String mHtmlContent;
public String mTextContent;
- /**
- * no public constructor since this is a utility class
- */
- private Body() {
+ public Body() {
mBaseUri = CONTENT_URI;
}
@@ -283,7 +286,7 @@
@Override
@SuppressWarnings("unchecked")
public EmailContent.Body restore(Cursor c) {
- mBaseUri = EmailContent.Message.CONTENT_URI;
+ mBaseUri = EmailContent.Body.CONTENT_URI;
mMessageKey = c.getLong(CONTENT_MESSAGE_KEY_COLUMN);
mHtmlContent = c.getString(CONTENT_HTML_CONTENT_COLUMN);
mTextContent = c.getString(CONTENT_TEXT_CONTENT_COLUMN);
@@ -352,8 +355,17 @@
public static final class Message extends EmailContent implements SyncColumns, MessageColumns {
public static final String TABLE_NAME = "Message";
- public static final String UPDATES_TABLE_NAME = "Message_Updates";
+ public static final String UPDATED_TABLE_NAME = "Message_Updates";
+ public static final String DELETED_TABLE_NAME = "Message_Deletes";
+
+ // To refer to a specific message, use ContentUris.withAppendedId(CONTENT_URI, id)
public static final Uri CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/message");
+ public static final Uri SYNCED_CONTENT_URI =
+ Uri.parse(EmailContent.CONTENT_URI + "/syncedMessage");
+ public static final Uri DELETED_CONTENT_URI =
+ Uri.parse(EmailContent.CONTENT_URI + "/deletedMessage");
+ public static final Uri UPDATED_CONTENT_URI =
+ Uri.parse(EmailContent.CONTENT_URI + "/updatedMessage");
public static final String KEY_TIMESTAMP_DESC = MessageColumns.TIMESTAMP + " desc";
@@ -489,9 +501,6 @@
mBaseUri = CONTENT_URI;
}
- public static final Uri UPDATED_CONTENT_URI =
- Uri.parse(EmailContent.CONTENT_URI + "/updatedMessage");
-
@Override
public ContentValues toContentValues() {
ContentValues values = new ContentValues();
@@ -500,6 +509,7 @@
values.put(MessageColumns.DISPLAY_NAME, mDisplayName);
values.put(MessageColumns.TIMESTAMP, mTimeStamp);
values.put(MessageColumns.SUBJECT, mSubject);
+ values.put(MessageColumns.PREVIEW, mPreview);
values.put(MessageColumns.FLAG_READ, mFlagRead);
values.put(MessageColumns.FLAG_LOADED, mFlagLoaded);
values.put(MessageColumns.FLAG_FAVORITE, mFlagFavorite);
@@ -517,6 +527,7 @@
values.put(MessageColumns.CLIENT_ID, mClientId);
values.put(MessageColumns.MESSAGE_ID, mMessageId);
+ values.put(MessageColumns.THREAD_ID, mThreadId);
values.put(MessageColumns.MAILBOX_KEY, mMailboxKey);
values.put(MessageColumns.ACCOUNT_KEY, mAccountKey);
@@ -744,6 +755,7 @@
public static final int CHECK_INTERVAL_NEVER = -1;
public static final int CHECK_INTERVAL_PUSH = -2;
+ public static final int CHECK_INTERVAL_PING = -3;
public static final int SYNC_WINDOW_USER = -1;
@@ -1653,6 +1665,12 @@
// Holds junk mail
public static final int TYPE_JUNK = 7;
+ // Types after this are used for non-mail mailboxes (as in EAS)
+ public static final int TYPE_NOT_EMAIL = 0x40;
+ public static final int TYPE_CALENDAR = 0x41;
+ public static final int TYPE_CONTACTS = 0x42;
+ public static final int TYPE_TASKS = 0x43;
+
// Bit field flags
public static final int FLAG_HAS_CHILDREN = 1<<0;
public static final int FLAG_CHILDREN_VISIBLE = 1<<1;
@@ -1746,9 +1764,9 @@
public static final String TABLE_NAME = "HostAuth";
public static final Uri CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/hostauth");
- private static final int FLAG_SSL = 1;
- private static final int FLAG_TLS = 2;
- private static final int FLAG_AUTHENTICATE = 4;
+ public static final int FLAG_SSL = 1;
+ public static final int FLAG_TLS = 2;
+ public static final int FLAG_AUTHENTICATE = 4;
public String mProtocol;
public String mAddress;
diff --git a/src/com/android/exchange/EodException.java b/src/com/android/exchange/EodException.java
deleted file mode 100644
index e1b4905..0000000
--- a/src/com/android/exchange/EodException.java
+++ /dev/null
@@ -1,24 +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;
-
-import java.io.IOException;
-
-public class EodException extends IOException {
- private static final long serialVersionUID = 1L;
-}
diff --git a/src/com/android/exchange/EofException.java b/src/com/android/exchange/EofException.java
deleted file mode 100644
index b9d8504..0000000
--- a/src/com/android/exchange/EofException.java
+++ /dev/null
@@ -1,24 +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;
-
-import java.io.IOException;
-
-public class EofException extends IOException {
- private static final long serialVersionUID = 1L;
-}
diff --git a/src/com/android/exchange/InteractiveSyncService.java b/src/com/android/exchange/InteractiveSyncService.java
new file mode 100644
index 0000000..23b116f
--- /dev/null
+++ b/src/com/android/exchange/InteractiveSyncService.java
@@ -0,0 +1,50 @@
+/*
+ * 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;
+
+import android.content.Context;
+
+import com.android.exchange.EmailContent.Attachment;
+import com.android.exchange.EmailContent.Mailbox;
+
+/**
+ * The parent class of all SyncServices that are interactive (i.e. need to
+ * respond to user input in a timely way. The abstract methods for the most part
+ * track the service methods available in the ISyncManager interface. The
+ * SyncManager is responsible for ensuring that an InteractiveSyncService has
+ * been started, and then passes the appropriate call into it. Each ISS will
+ * interpret/handle the method as it deems appropriate.
+ */
+public abstract class InteractiveSyncService extends AbstractSyncService {
+
+ public InteractiveSyncService(Context _context, Mailbox _mailbox) {
+ super(_context, _mailbox);
+ }
+
+ public InteractiveSyncService(String prefix) {
+ super(prefix);
+ }
+
+ public abstract void startSync();
+
+ public abstract void stopSync();
+
+ public abstract void reloadFolderList();
+
+ public abstract void loadAttachment(Attachment att, ISyncManagerCallback cb);
+}
diff --git a/src/com/android/exchange/KeepAliveReceiver.java b/src/com/android/exchange/MailboxAlarmReceiver.java
similarity index 66%
rename from src/com/android/exchange/KeepAliveReceiver.java
rename to src/com/android/exchange/MailboxAlarmReceiver.java
index 1d4aa53..61af05a 100644
--- a/src/com/android/exchange/KeepAliveReceiver.java
+++ b/src/com/android/exchange/MailboxAlarmReceiver.java
@@ -1,6 +1,5 @@
/*
-/*
- * Copyright (C) 2008-2009 Marc Blank
+ * Copyright (C) 2008-2009 Marc Blank
* Licensed to The Android Open Source Project.
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -22,15 +21,19 @@
import android.content.Context;
import android.content.Intent;
-public class KeepAliveReceiver extends BroadcastReceiver {
+/**
+ * MailboxAlarmReceiver is used to "wake up" the SyncManager at the appropriate time(s). It may
+ * also be used for individual sync adapters, but this isn't implemented at the present time.
+ *
+ */
+public class MailboxAlarmReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
long mid = intent.getLongExtra("mailbox", -1);
- if (mid < 0) {
- SyncManager.kick();
+ if (SyncManager.INSTANCE != null) {
+ SyncManager.INSTANCE.log("Alarm received for: " + mid);
}
- else {
- SyncManager.ping(mid);
- }
+ SyncManager.ping(mid);
}
}
+
diff --git a/src/com/android/exchange/ProtocolService.java b/src/com/android/exchange/ProtocolService.java
deleted file mode 100644
index 57cc7d3..0000000
--- a/src/com/android/exchange/ProtocolService.java
+++ /dev/null
@@ -1,243 +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;
-
-import java.util.ArrayList;
-
-import com.android.email.Email;
-import com.android.email.mail.MessagingException;
-import com.android.exchange.EmailContent.Account;
-import com.android.exchange.EmailContent.Mailbox;
-
-import android.content.Context;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
-import android.net.NetworkInfo.DetailedState;
-import android.util.Log;
-
-// Base class for all protocol services
-// Some common functionality is included here; note that each protocol will implement run() individually
-// MailService (extends Service, implements Runnable) instantiates subclasses when it's time to run a sync
-// (either timed, or push, or mail placed in outbox, etc.)
-// Current subclasses are IMAPService, EASService, and SMTPService, with POP3Service to come...
-public abstract class ProtocolService implements Runnable {
-
- public static final String TAG = "ProtocolService";
-
- public static final String SUMMARY_PROTOCOL = "_SUMMARY_";
- public static final String SYNCED_PROTOCOL = "_SYNCING_";
- public static final String MOVE_FAVORITES_PROTOCOL = "_MOVE_FAVORITES_";
-
- public static final int CONNECT_TIMEOUT = 30000;
- public static final int NETWORK_WAIT = 15000;
-
- public static final int SECS = 1000;
- public static final int MINS = 60*SECS;
- public static final int HRS = 60*MINS;
- public static final int DAYS = 24*HRS;
-
- public static final String IMAP_PROTOCOL = "imap";
- public static final String EAS_PROTOCOL = "eas";
-
- // 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();
-
- protected Mailbox mMailbox;
- protected long mMailboxId;
- protected Thread mThread;
- protected String mMailboxName;
- protected Account mAccount;
- protected Context mContext;
- protected long mRequestTime;
- protected ArrayList<PartRequest> mPartRequests = new ArrayList<PartRequest>();
- protected PartRequest mPendingPartRequest = null;
-
- // Stop is sent by the MailService to request that the service stop itself cleanly. An example
- // would be for the implementation of sleep hours
- public abstract void stop ();
- // Ping is sent by the MailService to indicate that a user request requiring service has been added to
- // request queue; response is service dependent
- public abstract void ping ();
- // MailService calls this to determine the sync state of the protocol service. By default,
- // this is "SYNC", but it might, for example, be "IDLE" (i.e. push), in which case the method will be
- // overridden. Could be abstract, but ... nah.
- public int getSyncStatus() {
- return 0;
- //return MailService.SyncStatus.SYNC;
- }
-
- public ProtocolService (Context _context, Mailbox _mailbox) {
- mContext = _context;
- mMailbox = _mailbox;
- mMailboxId = _mailbox.mId;
- mMailboxName = _mailbox.mServerId;
- mAccount = Account.restoreAccountWithId(_context, _mailbox.mAccountKey);
- }
-
- // Will be required when subclasses are instantiated by name
- public ProtocolService (String prefix) {
- }
-
- public abstract void validateAccount (String host, String userName, String password,
- int port, boolean ssl, Context context) throws MessagingException;
-
- static public void validate (Class<? extends ProtocolService> klass, String host,
- String userName, String password, int port, boolean ssl, Context context)
- throws MessagingException {
- ProtocolService svc;
- try {
- svc = klass.newInstance();
- svc.validateAccount(host, userName, password, port, ssl, context);
- } catch (IllegalAccessException e) {
- throw new MessagingException("internal error", e);
- } catch (InstantiationException e) {
- throw new MessagingException("internal error", e);
- }
- }
-
- public static class ValidationResult {
- static final int NO_FAILURE = 0;
- static final int CONNECTION_FAILURE = 1;
- static final int VALIDATION_FAILURE = 2;
- static final int EXCEPTION = 3;
- static final ValidationResult succeeded = new ValidationResult(true, NO_FAILURE, null);
- boolean success;
- int failure = NO_FAILURE;
- String reason = null;
- Exception exception = null;
-
- ValidationResult (boolean _success, int _failure, String _reason) {
- success = _success;
- failure = _failure;
- reason = _reason;
- }
-
- ValidationResult (boolean _success) {
- success = _success;
- }
-
- ValidationResult (Exception e) {
- success = false;
- failure = EXCEPTION;
- exception = e;
- }
-
- public boolean isSuccess () {
- return success;
- }
-
- public String getReason () {
- return reason;
- }
- }
-
- public final void runAwake () {
- //MailService.runAwake(mMailboxId);
- }
-
- public final void runAsleep (long millis) {
- //MailService.runAsleep(mMailboxId, millis);
- }
-
- // Common call used by the various protocols to send a "mail" message to the UI
- protected void updateUI () {
- }
-
- protected void log (String str) {
- if (Email.DEBUG) {
- Log.v(Email.LOG_TAG, str);
- }
- }
-
- // Delay until there is some kind of network connectivity
- // Subclasses should allow some number of retries before failing, and kicking the ball back to MailService
- public int waitForConnectivity () {
- ConnectivityManager cm = (ConnectivityManager)mContext
- .getSystemService(Context.CONNECTIVITY_SERVICE);
- while (true) {
- 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...
- log("Not quite connected? Pause 1 second");
- }
- pause(1000);
- } else {
- log("Not connected; waiting 15 seconds");
- pause(NETWORK_WAIT);
- }
- }
- }
-
- // Convenience
- private void pause (int ms) {
- try {
- Thread.sleep(ms);
- } catch (InterruptedException e) {
- }
- }
-
- // PartRequest handling (common functionality)
- // Can be overridden if desired, but IMAP/EAS both use the next three methods as-is
- public void addPartRequest (PartRequest req) {
- synchronized(mPartRequests) {
- mPartRequests.add(req);
- }
- }
-
- public void removePartRequest (PartRequest req) {
- synchronized(mPartRequests) {
- mPartRequests.remove(req);
- }
- }
-
- public PartRequest hasPartRequest(long emailId, String part) {
- synchronized(mPartRequests) {
- for (PartRequest pr: mPartRequests) {
- if (pr.emailId == emailId && pr.loc.equals(part))
- return pr;
- }
- }
- 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
- public PartRequest cancelPartRequest(long emailId, String part) {
- synchronized(mPartRequests) {
- PartRequest p = null;
- for (PartRequest pr: mPartRequests) {
- if (pr.emailId == emailId && pr.loc.equals(part)) {
- p = pr;
- break;
- }
- }
- if (p != null) {
- mPartRequests.remove(p);
- return p;
- }
- }
- return null;
- }
-}
diff --git a/src/com/android/exchange/EasParserException.java b/src/com/android/exchange/StaleFolderListException.java
similarity index 87%
rename from src/com/android/exchange/EasParserException.java
rename to src/com/android/exchange/StaleFolderListException.java
index ee10c23..70ac032 100644
--- a/src/com/android/exchange/EasParserException.java
+++ b/src/com/android/exchange/StaleFolderListException.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008-2009 Marc Blank
+ * Copyright (C) 2008-2009 Marc Blank
* Licensed to The Android Open Source Project.
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,6 +17,6 @@
package com.android.exchange;
-public class EasParserException extends Exception {
+public class StaleFolderListException extends EasException {
private static final long serialVersionUID = 1L;
}
diff --git a/src/com/android/exchange/SyncManager.java b/src/com/android/exchange/SyncManager.java
index adc8f94..906564f 100644
--- a/src/com/android/exchange/SyncManager.java
+++ b/src/com/android/exchange/SyncManager.java
@@ -23,7 +23,6 @@
import android.util.Log;
import java.util.ArrayList;
-import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
@@ -33,6 +32,9 @@
import com.android.exchange.EmailContent.HostAuth;
import com.android.exchange.EmailContent.Mailbox;
import com.android.exchange.EmailContent.Message;
+import com.android.exchange.EmailContent.MessageColumns;
+import com.android.exchange.EmailContent.SyncColumns;
+import com.android.exchange.adapter.EasOutboxService;
import android.app.AlarmManager;
import android.app.PendingIntent;
@@ -47,91 +49,116 @@
import android.net.Uri;
import android.net.NetworkInfo.State;
import android.os.Bundle;
-import android.os.Debug;
import android.os.Handler;
import android.os.PowerManager;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.PowerManager.WakeLock;
-import android.preference.PreferenceManager;
import android.database.ContentObserver;
+/**
+ * The SyncManager handles all aspects of starting, maintaining, and stopping the various sync
+ * adapters used by Exchange. However, it is capable of handing any kind of email sync, and it
+ * would be appropriate to use for IMAP push, when that functionality is added to the Email
+ * application.
+ *
+ * The Email application communicates with EAS sync adapters via SyncManager's binder interface,
+ * which exposes UI-related functionality to the application (see the definitions below)
+ *
+ * SyncManager uses ContentObservers to detect changes to accounts, mailboxes, and messages in
+ * order to maintain proper 2-way syncing of data. (More documentation to follow)
+ *
+ */
public class SyncManager extends Service implements Runnable {
- public static final int AWAKE = 0;
- public static final int SLEEP_WEEKEND = 1;
- public static final int SLEEP_HOURS = 2;
- public static final int OFFLINE = 3;
+ public static final String TAG = "EAS SyncManager";
public static final int DEFAULT_WINDOW = Integer.MIN_VALUE;
-
public static final int SECS = 1000;
- public static final int MINS = 60*SECS;
-
+ public static final int MINS = 60 * SECS;
static SyncManager INSTANCE;
- static int mStatus = AWAKE;
- static boolean mToothpicks = false;
static Object mSyncToken = new Object();
static Thread mServiceThread = null;
-
- HashMap<Long, ProtocolService> serviceMap = new HashMap<Long, ProtocolService> ();
+ HashMap<Long, AbstractSyncService> mServiceMap = new HashMap<Long, AbstractSyncService>();
+ HashMap<Long, SyncError> mSyncErrorMap = new HashMap<Long, SyncError>();
boolean mStop = false;
SharedPreferences mSettings;
Handler mHandler = new Handler();
AccountObserver mAccountObserver;
MailboxObserver mMailboxObserver;
+ SyncedMessageObserver mSyncedMessageObserver;
+ String mNextWaitReason;
- final RemoteCallbackList<ISyncManagerCallback> mCallbacks
- = new RemoteCallbackList<ISyncManagerCallback>();
+ static private HashMap<Long, Boolean> mWakeLocks = new HashMap<Long, Boolean>();
+ static private HashMap<Long, PendingIntent> mPendingIntents =
+ new HashMap<Long, PendingIntent>();
+ static private WakeLock mWakeLock = null;
+
+ final RemoteCallbackList<ISyncManagerCallback> mCallbacks =
+ new RemoteCallbackList<ISyncManagerCallback>();
private final ISyncManager.Stub mBinder = new ISyncManager.Stub() {
public int validate(String protocol, String host, String userName, String password,
int port, boolean ssl) throws RemoteException {
try {
- ProtocolService.validate(EasService.class, host, userName, password, port, ssl,
+ AbstractSyncService.validate(EasSyncService.class, host, userName, password, port, ssl,
SyncManager.this);
return MessagingException.NO_ERROR;
} catch (MessagingException e) {
return e.getExceptionType();
}
}
+
public void registerCallback(ISyncManagerCallback cb) {
- if (cb != null) mCallbacks.register(cb);
+ if (cb != null) {
+ mCallbacks.register(cb);
+ }
}
+
public void unregisterCallback(ISyncManagerCallback cb) {
- if (cb != null) mCallbacks.unregister(cb);
+ if (cb != null) {
+ mCallbacks.unregister(cb);
+ }
}
+
public boolean startSync(long mailboxId) throws RemoteException {
// TODO Auto-generated method stub
return false;
}
+
public boolean stopSync(long mailboxId) throws RemoteException {
// TODO Auto-generated method stub
return false;
}
+
public boolean updateFolderList(long accountId) throws RemoteException {
// TODO Auto-generated method stub
return false;
}
+
public boolean loadMore(long messageId, ISyncManagerCallback cb) throws RemoteException {
// TODO Auto-generated method stub
return false;
}
+
public boolean createFolder(long accountId, String name) throws RemoteException {
// TODO Auto-generated method stub
return false;
}
+
public boolean deleteFolder(long accountId, String name) throws RemoteException {
// TODO Auto-generated method stub
return false;
}
+
public boolean renameFolder(long accountId, String oldName, String newName)
- throws RemoteException {
+ throws RemoteException {
// TODO Auto-generated method stub
return false;
}
+
public boolean loadAttachment(long messageId, Attachment att, ISyncManagerCallback cb)
- throws RemoteException {
+ throws RemoteException {
// TODO Auto-generated method stub
return false;
}
@@ -145,38 +172,38 @@
Context context = getContext();
// At startup, we want to see what EAS accounts exist and cache them
- Cursor c = getContentResolver().query(Account.CONTENT_URI,
- Account.CONTENT_PROJECTION, null, null, null);
+ Cursor c = getContentResolver().query(Account.CONTENT_URI, Account.CONTENT_PROJECTION,
+ null, null, null);
try {
collectEasAccounts(c, mAccountIds);
} finally {
c.close();
}
- for (long accountId: mAccountIds) {
- int cnt = Mailbox.count(context, Mailbox.CONTENT_URI,
- "accountKey=" + accountId, null);
+ for (long accountId : mAccountIds) {
+ int cnt = Mailbox.count(context, Mailbox.CONTENT_URI, "accountKey=" + accountId,
+ null);
if (cnt == 0) {
initializeAccount(accountId);
}
}
}
- public void onChange (boolean selfChange) {
+ public void onChange(boolean selfChange) {
// A change to the list requires us to scan for deletions (to stop running syncs)
// At startup, we want to see what accounts exist and cache them
ArrayList<Long> currentIds = new ArrayList<Long>();
- Cursor c = getContentResolver().query(Account.CONTENT_URI,
- Account.CONTENT_PROJECTION, null, null, null);
+ Cursor c = getContentResolver().query(Account.CONTENT_URI, Account.CONTENT_PROJECTION,
+ null, null, null);
try {
collectEasAccounts(c, currentIds);
- for (long accountId: mAccountIds) {
+ for (long accountId : mAccountIds) {
if (!currentIds.contains(accountId)) {
// This is a deletion; shut down any account-related syncs
accountDeleted(accountId);
}
}
- for (long accountId: currentIds) {
+ for (long accountId : currentIds) {
if (!mAccountIds.contains(accountId)) {
// This is an addition; create our magic hidden mailbox...
initializeAccount(accountId);
@@ -191,7 +218,7 @@
kick();
}
- private void collectEasAccounts (Cursor c, ArrayList<Long> ids) {
+ void collectEasAccounts(Cursor c, ArrayList<Long> ids) {
Context context = getContext();
while (c.moveToNext()) {
long hostAuthId = c.getLong(Account.CONTENT_HOST_AUTH_KEY_RECV_COLUMN);
@@ -204,7 +231,7 @@
}
}
- private void initializeAccount (long acctId) {
+ void initializeAccount(long acctId) {
Account acct = Account.restoreAccountWithId(getContext(), acctId);
Mailbox main = new Mailbox();
main.mDisplayName = "_main";
@@ -217,15 +244,14 @@
INSTANCE.log("Initializing account: " + acct.mDisplayName);
}
- private void accountDeleted (long acctId) {
+ void accountDeleted(long acctId) {
synchronized (mSyncToken) {
List<Long> deletedBoxes = new ArrayList<Long>();
- for (Long mid : INSTANCE.serviceMap.keySet()) {
- Mailbox box =
- Mailbox.restoreMailboxWithId(INSTANCE, mid);
+ for (Long mid : INSTANCE.mServiceMap.keySet()) {
+ Mailbox box = Mailbox.restoreMailboxWithId(INSTANCE, mid);
if (box != null) {
if (box.mAccountKey == acctId) {
- ProtocolService svc = INSTANCE.serviceMap.get(mid);
+ AbstractSyncService svc = INSTANCE.mServiceMap.get(mid);
if (svc != null) {
svc.stop();
svc.mThread.interrupt();
@@ -235,7 +261,7 @@
}
}
for (Long mid : deletedBoxes) {
- INSTANCE.serviceMap.remove(mid);
+ INSTANCE.mServiceMap.remove(mid);
}
}
}
@@ -246,27 +272,86 @@
super(handler);
}
- public void onChange (boolean selfChange) {
+ public void onChange(boolean selfChange) {
// See if there's anything to do...
kick();
}
}
+ class SyncedMessageObserver extends ContentObserver {
+ long maxChangedId = 0;
+ long maxDeletedId = 0;
+ Intent syncAlarmIntent = new Intent(INSTANCE, UserSyncAlarmReceiver.class);
+ PendingIntent syncAlarmPendingIntent =
+ PendingIntent.getBroadcast(INSTANCE, 0, syncAlarmIntent, 0);
+ AlarmManager alarmManager = (AlarmManager)INSTANCE.getSystemService(Context.ALARM_SERVICE);
+ final String[] MAILBOX_DATA_PROJECTION = {MessageColumns.MAILBOX_KEY, SyncColumns.DATA};
+
+ public SyncedMessageObserver(Handler handler) {
+ super(handler);
+ }
+
+ public void onChange(boolean selfChange) {
+ INSTANCE.log("SyncedMessage changed: (re)setting alarm for 10s");
+ alarmManager.set(AlarmManager.RTC_WAKEUP,
+ System.currentTimeMillis() + (10*SECS), syncAlarmPendingIntent);
+ }
+ }
+
+ public class SyncStatus {
+ static public final int NOT_RUNNING = 0;
+
+ static public final int DIED = 1;
+
+ static public final int SYNC = 2;
+
+ static public final int IDLE = 3;
+ }
+
+ class SyncError {
+ int reason;
+ boolean fatal = false;
+ long holdEndTime;
+ long holdDelay = 0;
+
+ SyncError(int _reason, boolean _fatal) {
+ reason = _reason;
+ fatal = _fatal;
+ escalate();
+ }
+
+ /**
+ * We increase the hold on I/O errors in 30 second increments to 5 minutes
+ */
+ void escalate() {
+ if (holdDelay < 5*MINS) {
+ holdDelay += 30*SECS;
+ }
+ holdEndTime = System.currentTimeMillis() + holdDelay;
+ }
+ }
+
@Override
public IBinder onBind(Intent arg0) {
return mBinder;
}
- public void log (String str) {
- Log.v("EmailApp:MailService", str);
+ public void log(String str) {
+ if (Eas.USER_DEBUG) {
+ Log.d(TAG, str);
+ }
}
@Override
- public void onCreate () {
+ public void onCreate() {
+ if (INSTANCE != null) {
+ throw new RuntimeException("\n************ ALREADY RUNNING *************\n");
+ }
INSTANCE = this;
mAccountObserver = new AccountObserver(mHandler);
mMailboxObserver = new MailboxObserver(mHandler);
+ mSyncedMessageObserver = new SyncedMessageObserver(mHandler);
// Start our thread...
if (mServiceThread == null || !mServiceThread.isAlive()) {
@@ -278,19 +363,29 @@
}
}
- static private HashMap<Long, Boolean> mWakeLocks = new HashMap<Long, Boolean>();
- static private HashMap<Long, PendingIntent> mPendingIntents =
- new HashMap<Long, PendingIntent>();
- static private WakeLock mWakeLock = null;
+ /**
+ * Informs SyncManager that an account has a new folder list; as a result, any existing folder
+ * might have become invalid. Therefore, we act as if the account has been deleted, and then
+ * we reinitialize it.
+ *
+ * @param acctId
+ */
+ static public void folderListReloaded(long acctId) {
+ if (INSTANCE != null) {
+ AccountObserver obs = INSTANCE.mAccountObserver;
+ obs.accountDeleted(acctId);
+ obs.initializeAccount(acctId);
+ }
+ }
- static public void acquireWakeLock (long id) {
+ static public void acquireWakeLock(long id) {
synchronized (mWakeLocks) {
Boolean lock = mWakeLocks.get(id);
if (lock == null) {
INSTANCE.log("+WakeLock requested for " + id);
if (mWakeLock == null) {
- PowerManager pm =
- (PowerManager) INSTANCE.getSystemService(Context.POWER_SERVICE);
+ PowerManager pm = (PowerManager)INSTANCE
+ .getSystemService(Context.POWER_SERVICE);
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MAIL_SERVICE");
mWakeLock.acquire();
INSTANCE.log("+WAKE LOCK ACQUIRED");
@@ -300,7 +395,7 @@
}
}
- static public void releaseWakeLock (long id) {
+ static public void releaseWakeLock(long id) {
synchronized (mWakeLocks) {
Boolean lock = mWakeLocks.get(id);
if (lock != null) {
@@ -313,21 +408,21 @@
}
}
}
- }
-
- static private String alarmOwner (long id) {
- if (id == -1) {
- return "MailService";
- }
- else return "Mailbox " + Long.toString(id);
}
- static private void clearAlarm (long id) {
+ static private String alarmOwner(long id) {
+ if (id == -1) {
+ return "MailService";
+ } else
+ return "Mailbox " + Long.toString(id);
+ }
+
+ static private void clearAlarm(long id) {
synchronized (mPendingIntents) {
PendingIntent pi = mPendingIntents.get(id);
if (pi != null) {
- AlarmManager alarmManager =
- (AlarmManager)INSTANCE.getSystemService(Context.ALARM_SERVICE);
+ AlarmManager alarmManager = (AlarmManager)INSTANCE
+ .getSystemService(Context.ALARM_SERVICE);
alarmManager.cancel(pi);
INSTANCE.log("+Alarm cleared for " + alarmOwner(id));
mPendingIntents.remove(id);
@@ -335,25 +430,25 @@
}
}
- static private void setAlarm (long id, long millis) {
+ static private void setAlarm(long id, long millis) {
synchronized (mPendingIntents) {
PendingIntent pi = mPendingIntents.get(id);
if (pi == null) {
- Intent i = new Intent(INSTANCE, KeepAliveReceiver.class);
+ Intent i = new Intent(INSTANCE, MailboxAlarmReceiver.class);
i.putExtra("mailbox", id);
i.setData(Uri.parse("Box" + id));
pi = PendingIntent.getBroadcast(INSTANCE, 0, i, 0);
mPendingIntents.put(id, pi);
- AlarmManager alarmManager =
- (AlarmManager)INSTANCE.getSystemService(Context.ALARM_SERVICE);
+ AlarmManager alarmManager = (AlarmManager)INSTANCE
+ .getSystemService(Context.ALARM_SERVICE);
alarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + millis, pi);
INSTANCE.log("+Alarm set for " + alarmOwner(id) + ", " + millis + "ms");
}
}
}
- static private void clearAlarms () {
+ static private void clearAlarms() {
AlarmManager alarmManager = (AlarmManager)INSTANCE.getSystemService(Context.ALARM_SERVICE);
synchronized (mPendingIntents) {
for (PendingIntent pi : mPendingIntents.values()) {
@@ -361,33 +456,36 @@
}
mPendingIntents.clear();
}
- }
+ }
- static public void runAwake (long id) {
+ static public void runAwake(long id) {
acquireWakeLock(id);
clearAlarm(id);
}
- static public void runAsleep (long id, long millis) {
+ static public void runAsleep(long id, long millis) {
setAlarm(id, millis);
releaseWakeLock(id);
}
- static public void ping (long id) {
- ProtocolService service = INSTANCE.serviceMap.get(id);
- if (service != null) {
- Mailbox m = Mailbox.restoreMailboxWithId(INSTANCE, id);
- if (m != null) {
- service.mAccount =
- Account.restoreAccountWithId(INSTANCE, m.mAccountKey);
- service.mMailbox = m;
- service.ping();
+ static public void ping(long id) {
+ if (id < 0) {
+ kick();
+ } else {
+ AbstractSyncService service = INSTANCE.mServiceMap.get(id);
+ if (service != null) {
+ Mailbox m = Mailbox.restoreMailboxWithId(INSTANCE, id);
+ if (m != null) {
+ service.mAccount = Account.restoreAccountWithId(INSTANCE, m.mAccountKey);
+ service.mMailbox = m;
+ service.ping();
+ }
}
}
}
@Override
- public void onDestroy () {
+ public void onDestroy() {
log("!!! MaiLService onDestroy");
if (mWakeLock != null) {
mWakeLock.release();
@@ -398,7 +496,7 @@
public class ConnectivityReceiver extends BroadcastReceiver {
@Override
- public void onReceive (Context context, Intent intent) {
+ public void onReceive(Context context, Intent intent) {
Bundle b = intent.getExtras();
if (b != null) {
NetworkInfo a = (NetworkInfo)b.get("networkInfo");
@@ -406,10 +504,12 @@
State state = a.getState();
if (state == State.CONNECTED) {
info += " CONNECTED";
+ kick();
} else if (state == State.CONNECTING) {
info += " CONNECTING";
} else if (state == State.DISCONNECTED) {
info += " DISCONNECTED";
+ kick();
} else if (state == State.DISCONNECTING) {
info += " DISCONNECTING";
} else if (state == State.SUSPENDED) {
@@ -422,71 +522,56 @@
}
}
- private void pause (int ms) {
+ private void pause(int ms) {
try {
Thread.sleep(ms);
} catch (InterruptedException e) {
}
}
- private void startService (ProtocolService service, Mailbox m) {
+ private void startService(AbstractSyncService service, Mailbox m) {
synchronized (mSyncToken) {
String mailboxName = m.mDisplayName;
String accountName = service.mAccount.mDisplayName;
Thread thread = new Thread(service, mailboxName + "(" + accountName + ")");
log("Starting thread for " + mailboxName + " in account " + accountName);
thread.start();
- serviceMap.put(m.mId, service);
+ mServiceMap.put(m.mId, service);
}
}
- private void startService (Mailbox m) {
+ private void startService(Mailbox m) {
synchronized (mSyncToken) {
- Account acct =
- Account.restoreAccountWithId(this, m.mAccountKey);
+ Account acct = Account.restoreAccountWithId(this, m.mAccountKey);
if (acct != null) {
- ProtocolService service;
- service = new EasService(this, m);
+ AbstractSyncService service;
+ service = new EasSyncService(this, m);
startService(service, m);
}
}
}
- private void startSleep () {
+ private void stopServices() {
synchronized (mSyncToken) {
- // Shut everything down
- boolean stoppedOne = false;
- // Keep track of which services we've stopped
ArrayList<Long> toStop = new ArrayList<Long>();
- // Shut down all of our running services
- for (Long mid : serviceMap.keySet()) {
- toStop.add(mid);
- stoppedOne = true;
+
+ // Keep track of which services to stop
+ for (Long mailboxId : mServiceMap.keySet()) {
+ toStop.add(mailboxId);
}
- for (Long mid: toStop) {
- ProtocolService svc = serviceMap.get(mid);
- log("Going to sleep: shutting down " + svc.mAccount.mDisplayName +
- "/" + svc.mMailboxName);
+ // Shut down all of those running services
+ for (Long mailboxId : toStop) {
+ AbstractSyncService svc = mServiceMap.get(mailboxId);
+ log("Shutting down " + svc.mAccount.mDisplayName + '/' + svc.mMailbox.mDisplayName);
svc.stop();
svc.mThread.interrupt();
- stoppedOne = true;
- }
- // Remove the stopped services from the map
- //for (Long mid : stopped)
- // serviceMap.remove(mid);
- // Let the UI know
- if (stoppedOne) {
}
}
}
- private void broadcastSleep () {
- }
-
- public void run () {
- log("MailService: run");
- Debug.waitForDebugger();
+ public void run() {
+ log("Running");
mStop = false;
runAwake(-1);
@@ -494,229 +579,157 @@
ContentResolver resolver = getContentResolver();
resolver.registerContentObserver(Account.CONTENT_URI, false, mAccountObserver);
resolver.registerContentObserver(Mailbox.CONTENT_URI, false, mMailboxObserver);
+ resolver.registerContentObserver(Message.SYNCED_CONTENT_URI, true, mSyncedMessageObserver);
ConnectivityReceiver cr = new ConnectivityReceiver();
registerReceiver(cr, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
ConnectivityManager cm =
- (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
-
- mSettings = PreferenceManager.getDefaultSharedPreferences(this);
- GregorianCalendar calendar = new GregorianCalendar();
-
- mStatus = AWAKE;
+ (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
try {
while (!mStop) {
runAwake(-1);
- log("%%MailService heartbeat");
+ 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();
+ }
pause(10*SECS);
}
}
-
- long nextWait = 10*MINS;
- long now = System.currentTimeMillis();
-
- // We could send notices of sleep time changes and otherwise cache all of this...
- long sleepHours = mSettings.getLong("sleep_hours", 0);
- if (sleepHours != 0) {
- boolean wantSleep = false;
- calendar.setTimeInMillis(now);
- int nowHour = calendar.get(GregorianCalendar.HOUR_OF_DAY);
- int nowMinute = calendar.get(GregorianCalendar.MINUTE);
-
- long sleepStart = sleepHours >> 32;
- int startHour = (int)(sleepStart / 100);
- int startMinute = (int)(sleepStart % 100);
-
- long sleepEnd = sleepHours & 0x00000000FFFFFFFFL;
- int endHour = (int)(sleepEnd / 100);
- int endMinute = (int)(sleepEnd % 100);
-
- if (sleepStart > sleepEnd) {
- if ((nowHour > startHour) ||
- (nowHour == startHour && nowMinute >= startMinute) ||
- (nowHour < endHour) ||
- (nowHour == endHour && nowMinute <= endMinute))
- wantSleep = true;
- } else if (((startHour < nowHour ||
- (startHour == nowHour && nowMinute >= startMinute)) &&
- ((nowHour < endHour) ||
- (nowHour == endHour && nowMinute <= endMinute))))
- wantSleep = true;
-
- if (wantSleep && (mStatus == AWAKE)) {
- mStatus = SLEEP_HOURS;
- startSleep();
- broadcastSleep();
- } else if (!wantSleep && (mStatus == SLEEP_HOURS)) {
- mStatus = AWAKE;
- broadcastSleep();
- }
- }
-
- boolean sleepWeekends = mSettings.getBoolean("sleep_weekends", false);
- if ((mStatus != SLEEP_HOURS) && ((mStatus != AWAKE) || sleepWeekends)) {
- boolean wantSleep = false;
- calendar.setTimeInMillis(now);
- int day = calendar.get(GregorianCalendar.DAY_OF_WEEK);
- if (sleepWeekends &&
- (day == GregorianCalendar.SATURDAY ||
- day == GregorianCalendar.SUNDAY)) {
- wantSleep = true;
- }
- if ((mStatus == AWAKE) && wantSleep) {
- mStatus = SLEEP_WEEKEND;
- startSleep();
- broadcastSleep();
- } else if ((mStatus != AWAKE) && !wantSleep) {
- // Wake up!!
- mStatus = AWAKE;
- broadcastSleep();
- }
- }
-
- boolean offline = mSettings.getBoolean("offline", false);
- if (mStatus == AWAKE || mStatus == OFFLINE) {
- boolean wantSleep = offline;
- if ((mStatus == AWAKE) && wantSleep) {
- mStatus = OFFLINE;
- startSleep();
- broadcastSleep();
- } else if ((mStatus == OFFLINE) && !wantSleep) {
- // Wake up!!
- mStatus = AWAKE;
- broadcastSleep();
- }
- }
-
- if (!mStop && ((mStatus == AWAKE) || mToothpicks)) {
- // Start up threads that need it...
+ if (!mStop) {
+ mNextWaitReason = "Heartbeat";
+ long nextWait = checkMailboxes();
try {
- Cursor c = getContentResolver().query(Mailbox.CONTENT_URI,
- Mailbox.CONTENT_PROJECTION, null, null, null);
- while (c.moveToNext()) {
- // TODO Could be much faster - just get cursor of ones we're watching...
- long aid = c.getLong(Mailbox.CONTENT_ACCOUNT_KEY_COLUMN);
- // Only check mailboxes for EAS accounts
- if (!mAccountObserver.mAccountIds.contains(aid)) {
- continue;
+ synchronized (INSTANCE) {
+ if (nextWait < 0) {
+ log("Negative wait? Setting to 1s");
+ nextWait = 1*SECS;
}
- long mid = c.getLong(Mailbox.CONTENT_ID_COLUMN);
- ProtocolService service = serviceMap.get(mid);
- if (service == null) {
- long freq = c.getInt(Mailbox.CONTENT_SYNC_FREQUENCY_COLUMN);
- if (freq == Account.CHECK_INTERVAL_PUSH) {
- Mailbox m =
- EmailContent.getContent(c, Mailbox.class);
- // Either push, or 30 mins (default for idle timeout)
- if (((m.mFlags & Mailbox.FLAG_CANT_PUSH) == 0)
- || ((now - m.mSyncTime) > (1000 * 60 * 30L))) {
- startService(m);
- }
- } else if (freq == -19) {
- // See if we've got anything to do...
- int cnt = EmailContent.count(this,
- Message.CONTENT_URI, "mailboxKey=" +
- mid + " and syncServerId=0", null);
- if (cnt > 0) {
- Mailbox m = EmailContent.getContent(c, Mailbox.class);
- startService(new EasOutboxService(this, m), m);
- }
- } else if (freq > 0 && freq <= 1440) {
- long lastSync =
- c.getLong(Mailbox.CONTENT_SYNC_TIME_COLUMN);
- if (now - lastSync > (freq * 60000L)) {
- Mailbox m = EmailContent.getContent(c, Mailbox.class);
- startService(m);
- }
- }
- } else {
- Thread thread = service.mThread;
- if (!thread.isAlive()) {
- serviceMap.remove(mid);
- // Restart this if necessary
- if (nextWait > 3*SECS) {
- nextWait = 3*SECS;
- }
- } else {
- long requestTime = service.mRequestTime;
- if (requestTime > 0) {
- long timeToRequest = requestTime - now;
- if (service instanceof ProtocolService &&
- timeToRequest <= 0) {
- service.mRequestTime = 0;
- service.ping();
- } else if (requestTime > 0 && timeToRequest < nextWait) {
- if (timeToRequest < 11*MINS) {
- nextWait =
- timeToRequest < 250 ? 250 : timeToRequest;
- } else {
- log("Illegal timeToRequest: " + timeToRequest);
- }
- }
- }
- }
+ if (nextWait > (30*SECS)) {
+ runAsleep(-1, nextWait - 1000);
}
+ log("Next awake in " + (nextWait / 1000) + "s: " + mNextWaitReason);
+ INSTANCE.wait(nextWait);
}
- c.close();
-
- } catch (Exception e1) {
- log("Exception to follow...");
+ } catch (InterruptedException e) {
+ // Needs to be caught, but causes no problem
}
- }
-
- try {
- synchronized (INSTANCE) {
- if (nextWait < 0) {
- System.err.println("WTF?");
- nextWait = 1*SECS;
- }
- if (nextWait > 30*SECS) {
- runAsleep(-1, nextWait - 1000);
- }
-
- log("%%MailService sleeping for " + (nextWait / 1000) + " s");
- INSTANCE.wait(nextWait);
- }
- } catch (InterruptedException e) {
- log("IOException to follow...");
- }
-
- if (mStop) {
- startSleep();
- log("Shutdown requested.");
+ } else {
+ stopServices();
+ log("Shutdown requested");
return;
}
}
- } catch (Throwable e) {
- log("MailService crashed.");
} finally {
- log("Goodbye.");
+ log("Goodbye");
}
startService(new Intent(this, SyncManager.class));
throw new RuntimeException("MailService crash; please restart me...");
}
- static public void serviceRequest (Mailbox m) {
- serviceRequest(m.mId, 10*SECS);
- }
-
- static public void serviceRequest (long mailboxId) {
- serviceRequest(mailboxId, 10*SECS);
- }
-
- static public void serviceRequest (long mailboxId, long ms) {
+ long checkMailboxes () {
+ long nextWait = 10*MINS;
+ long now = System.currentTimeMillis();
+ // Start up threads that need it...
+ Cursor c = getContentResolver().query(Mailbox.CONTENT_URI,
+ Mailbox.CONTENT_PROJECTION, null, null, null);
try {
- if (INSTANCE == null)
+ while (c.moveToNext()) {
+ // TODO Could be much faster - just get cursor of
+ // ones we're watching...
+ long aid = c.getLong(Mailbox.CONTENT_ACCOUNT_KEY_COLUMN);
+ // Only check mailboxes for EAS accounts
+ if (!mAccountObserver.mAccountIds.contains(aid)) {
+ continue;
+ }
+ long mid = c.getLong(Mailbox.CONTENT_ID_COLUMN);
+ AbstractSyncService service = mServiceMap.get(mid);
+ 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.holdEndTime < (now + nextWait)) {
+ nextWait = syncError.holdEndTime - now;
+ mNextWaitReason = "Release hold";
+ }
+ }
+ continue;
+ }
+ long freq = c.getInt(Mailbox.CONTENT_SYNC_FREQUENCY_COLUMN);
+ if (freq == Account.CHECK_INTERVAL_PUSH) {
+ Mailbox m = EmailContent.getContent(c, Mailbox.class);
+ startService(m);
+ } else if (c.getInt(Mailbox.CONTENT_TYPE_COLUMN) == Mailbox.TYPE_OUTBOX) {
+ int cnt = EmailContent.count(this, Message.CONTENT_URI,
+ "mailboxKey=" + mid + " and syncServerId=0", null);
+ if (cnt > 0) {
+ Mailbox m = EmailContent.getContent(c, Mailbox.class);
+ startService(new EasOutboxService(this, m), m);
+ }
+ } else if (freq > 0 && freq <= 1440) {
+ long lastSync = c.getLong(Mailbox.CONTENT_SYNC_TIME_COLUMN);
+ if (now - lastSync > (freq*MINS)) {
+ Mailbox m = EmailContent.getContent(c, Mailbox.class);
+ startService(m);
+ }
+ }
+ } else {
+ Thread thread = service.mThread;
+ if (!thread.isAlive()) {
+ mServiceMap.remove(mid);
+ // Restart this if necessary
+ if (nextWait > 3*SECS) {
+ nextWait = 3*SECS;
+ mNextWaitReason = "Clean up dead thread(s)";
+ }
+ } else {
+ long requestTime = service.mRequestTime;
+ if (requestTime > 0) {
+ long timeToRequest = requestTime - now;
+ if (service instanceof AbstractSyncService && timeToRequest <= 0) {
+ service.mRequestTime = 0;
+ service.ping();
+ } else if (requestTime > 0 && timeToRequest < nextWait) {
+ if (timeToRequest < 11*MINS) {
+ nextWait = timeToRequest < 250 ? 250 : timeToRequest;
+ mNextWaitReason = "Sync data change";
+ } else {
+ log("Illegal timeToRequest: " + timeToRequest);
+ }
+ }
+ }
+ }
+ }
+ }
+ } finally {
+ c.close();
+ }
+ return nextWait;
+ }
+
+ static public void serviceRequest(Mailbox m) {
+ serviceRequest(m.mId, 5*SECS);
+ }
+
+ static public void serviceRequest(long mailboxId) {
+ serviceRequest(mailboxId, 5*SECS);
+ }
+
+ static public void serviceRequest(long mailboxId, long ms) {
+ try {
+ if (INSTANCE == null) {
return;
- ProtocolService service = INSTANCE.serviceMap.get(mailboxId);
+ }
+ AbstractSyncService service = INSTANCE.mServiceMap.get(mailboxId);
if (service != null) {
service.mRequestTime = System.currentTimeMillis() + ms;
kick();
@@ -728,10 +741,10 @@
}
}
- static public void serviceRequestImmediate (long mailboxId) {
- ProtocolService service = INSTANCE.serviceMap.get(mailboxId);
+ static public void serviceRequestImmediate(long mailboxId) {
+ AbstractSyncService service = INSTANCE.mServiceMap.get(mailboxId);
if (service != null) {
- service.mRequestTime = System.currentTimeMillis() ;
+ service.mRequestTime = System.currentTimeMillis();
Mailbox m = Mailbox.restoreMailboxWithId(INSTANCE, mailboxId);
service.mAccount = Account.restoreAccountWithId(INSTANCE, m.mAccountKey);
service.mMailbox = m;
@@ -739,16 +752,17 @@
}
}
- static public void partRequest (PartRequest req) {
+ static public void partRequest(PartRequest req) {
Message msg = Message.restoreMessageWithId(INSTANCE, req.emailId);
if (msg == null) {
return;
}
long mailboxId = msg.mMailboxKey;
- ProtocolService service = INSTANCE.serviceMap.get(mailboxId);
+ AbstractSyncService service = INSTANCE.mServiceMap.get(mailboxId);
- if (service == null)
+ if (service == null) {
service = startManualSync(mailboxId);
+ }
if (service != null) {
service.mRequestTime = System.currentTimeMillis();
@@ -763,7 +777,7 @@
return null;
}
long mailboxId = msg.mMailboxKey;
- ProtocolService service = INSTANCE.serviceMap.get(mailboxId);
+ AbstractSyncService service = INSTANCE.mServiceMap.get(mailboxId);
if (service != null) {
service.mRequestTime = System.currentTimeMillis();
return service.hasPartRequest(emailId, part);
@@ -777,26 +791,38 @@
return;
}
long mailboxId = msg.mMailboxKey;
- ProtocolService service = INSTANCE.serviceMap.get(mailboxId);
+ AbstractSyncService service = INSTANCE.mServiceMap.get(mailboxId);
if (service != null) {
service.mRequestTime = System.currentTimeMillis();
service.cancelPartRequest(emailId, part);
}
}
- public class SyncStatus {
- static public final int NOT_RUNNING = 0;
- static public final int DIED = 1;
- static public final int SYNC = 2;
- static public final int IDLE = 3;
+ /**
+ * Determine whether a given Mailbox can be synced, i.e. is not already syncing and is not in
+ * an error state
+ *
+ * @param mailboxId
+ * @return whether or not the Mailbox is available for syncing (i.e. is a valid push target)
+ */
+ static public boolean canSync(long mailboxId) {
+ // Already syncing...
+ if (INSTANCE.mServiceMap.get(mailboxId) != null) {
+ return false;
+ }
+ // Blocked from syncing (transient or permanent)
+ if (INSTANCE.mSyncErrorMap.get(mailboxId) != null) {
+ return false;
+ }
+ return true;
}
-
- static public int getSyncStatus (long mid) {
+
+ static public int getSyncStatus(long mailboxId) {
synchronized (mSyncToken) {
- if (INSTANCE == null || INSTANCE.serviceMap == null) {
+ if (INSTANCE == null || INSTANCE.mServiceMap == null) {
return SyncStatus.NOT_RUNNING;
}
- ProtocolService svc = INSTANCE.serviceMap.get(mid);
+ AbstractSyncService svc = INSTANCE.mServiceMap.get(mailboxId);
if (svc == null) {
return SyncStatus.NOT_RUNNING;
} else {
@@ -809,27 +835,29 @@
}
}
- static public ProtocolService startManualSync (long mid) {
- if (INSTANCE == null || INSTANCE.serviceMap == null)
+ static public AbstractSyncService startManualSync(long mailboxId) {
+ if (INSTANCE == null || INSTANCE.mServiceMap == null) {
return null;
+ }
INSTANCE.log("startManualSync");
synchronized (mSyncToken) {
- if (INSTANCE.serviceMap.get(mid) == null) {
- Mailbox m = Mailbox.restoreMailboxWithId(INSTANCE, mid);
+ if (INSTANCE.mServiceMap.get(mailboxId) == null) {
+ INSTANCE.mSyncErrorMap.remove(mailboxId);
+ Mailbox m = Mailbox.restoreMailboxWithId(INSTANCE, mailboxId);
INSTANCE.log("Starting sync for " + m.mDisplayName);
INSTANCE.startService(m);
}
}
- return INSTANCE.serviceMap.get(mid);
+ return INSTANCE.mServiceMap.get(mailboxId);
}
// DO NOT CALL THIS IN A LOOP ON THE SERVICEMAP
- static public void stopManualSync (long mid) {
- if (INSTANCE == null || INSTANCE.serviceMap == null) {
+ static public void stopManualSync(long mailboxId) {
+ if (INSTANCE == null || INSTANCE.mServiceMap == null) {
return;
}
synchronized (mSyncToken) {
- ProtocolService svc = INSTANCE.serviceMap.get(mid);
+ AbstractSyncService svc = INSTANCE.mServiceMap.get(mailboxId);
if (svc != null) {
INSTANCE.log("Stopping sync for " + svc.mMailboxName);
svc.stop();
@@ -838,7 +866,7 @@
}
}
- static public void kick () {
+ static public void kick() {
if (INSTANCE == null) {
return;
}
@@ -848,20 +876,19 @@
}
}
- static public void kick (long mid) {
- Mailbox m = Mailbox.restoreMailboxWithId(INSTANCE, mid);
+ static public void kick(long mailboxId) {
+ Mailbox m = Mailbox.restoreMailboxWithId(INSTANCE, mailboxId);
int syncType = m.mSyncFrequency;
if (syncType == Account.CHECK_INTERVAL_PUSH) {
- SyncManager.serviceRequestImmediate(mid);
+ SyncManager.serviceRequestImmediate(mailboxId);
} else {
- SyncManager.startManualSync(mid);
+ SyncManager.startManualSync(mailboxId);
}
}
-
- static public void accountUpdated (long acctId) {
+ static public void accountUpdated(long acctId) {
synchronized (mSyncToken) {
- for (ProtocolService svc : INSTANCE.serviceMap.values()) {
+ for (AbstractSyncService svc : INSTANCE.mServiceMap.values()) {
if (svc.mAccount.mId == acctId) {
svc.mAccount = Account.restoreAccountWithId(INSTANCE, acctId);
}
@@ -869,38 +896,42 @@
}
}
- static public int status () {
- return mStatus;
+ static public void done(AbstractSyncService svc) {
+ long mailboxId = svc.mMailboxId;
+ HashMap<Long, SyncError> errorMap = INSTANCE.mSyncErrorMap;
+ SyncError syncError = errorMap.get(mailboxId);
+ INSTANCE.mServiceMap.remove(mailboxId);
+ int exitStatus = svc.mExitStatus;
+ switch (exitStatus) {
+ case AbstractSyncService.EXIT_DONE:
+ errorMap.remove(mailboxId);
+ break;
+ case AbstractSyncService.EXIT_IO_ERROR:
+ if (syncError != null) {
+ syncError.escalate();
+ } else {
+ errorMap.put(mailboxId, INSTANCE.new SyncError(exitStatus, false));
+ }
+ kick();
+ break;
+ case AbstractSyncService.EXIT_LOGIN_FAILURE:
+ case AbstractSyncService.EXIT_EXCEPTION:
+ errorMap.put(mailboxId, INSTANCE.new SyncError(exitStatus, true));
+ break;
+ }
}
- static public boolean isSleeping () {
- return (mStatus == SLEEP_HOURS || mStatus == SLEEP_WEEKEND);
- }
-
- static public void forceAwake (boolean wake) {
- mToothpicks = wake;
- kick();
- }
-
- static public boolean isForceAwake () {
- return mToothpicks;
- }
-
- static public void done (ProtocolService svc) {
- INSTANCE.serviceMap.remove(svc.mMailboxId);
- }
-
- public static void shutdown () {
+ public static void shutdown() {
INSTANCE.mStop = true;
kick();
INSTANCE.stopSelf();
}
- static public String serviceName (long id) {
+ static public String serviceName(long id) {
if (id < 0) {
- return "MailService";
+ return "SyncManager";
} else {
- ProtocolService service = INSTANCE.serviceMap.get(id);
+ AbstractSyncService service = INSTANCE.mServiceMap.get(id);
if (service != null) {
return service.mThread.getName();
} else {
@@ -909,7 +940,7 @@
}
}
- static public Context getContext () {
+ static public Context getContext() {
if (INSTANCE == null) {
return null;
}
diff --git a/src/com/android/exchange/UserSyncAlarmReceiver.java b/src/com/android/exchange/UserSyncAlarmReceiver.java
new file mode 100644
index 0000000..c93e713
--- /dev/null
+++ b/src/com/android/exchange/UserSyncAlarmReceiver.java
@@ -0,0 +1,98 @@
+/*
+ * 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;
+
+import java.util.ArrayList;
+
+import com.android.exchange.EmailContent.Message;
+import com.android.exchange.EmailContent.MessageColumns;
+import com.android.exchange.EmailContent.SyncColumns;
+
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.util.Log;
+
+/**
+ * UserSyncAlarmReceiver (USAR) is used by the SyncManager to start up-syncs of user-modified data
+ * back to the Exchange server.
+ *
+ * Here's how this works for Email, for example:
+ *
+ * 1) User modifies or deletes an email from the UI.
+ * 2) SyncManager, which has a ContentObserver watching the Message class, is alerted to a change
+ * 3) SyncManager sets an alarm (to be received by USAR) for a few seconds in the
+ * future (currently 15), the delay preventing excess syncing (think of it as a debounce mechanism).
+ * 4) USAR Receiver's onReceive method is called
+ * 5) USAR goes through all change and deletion records and compiles a list of mailboxes which have
+ * changes to be uploaded.
+ * 6) USAR calls SyncManager to start syncs of those mailboxes
+ *
+ */
+public class UserSyncAlarmReceiver extends BroadcastReceiver {
+ final String[] MAILBOX_DATA_PROJECTION = {MessageColumns.MAILBOX_KEY, SyncColumns.DATA};
+ private static String TAG = "UserSyncAlarm";
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Log.v(TAG, "onReceive");
+ ArrayList<Long> mailboxesToNotify = new ArrayList<Long>();
+ ContentResolver cr = context.getContentResolver();
+ int messageCount = 0;
+ // Find all of the deletions
+ Cursor c = cr.query(Message.DELETED_CONTENT_URI, MAILBOX_DATA_PROJECTION,
+ null, null, null);
+ try {
+ // Keep track of which mailboxes to notify; we'll only notify each one once
+ while (c.moveToNext()) {
+ messageCount++;
+ long mailboxId = c.getLong(0);
+ if (!mailboxesToNotify.contains(mailboxId)) {
+ mailboxesToNotify.add(mailboxId);
+ }
+ }
+ } finally {
+ c.close();
+ }
+
+ // Now, find changed messages
+ c = cr.query(Message.UPDATED_CONTENT_URI, MAILBOX_DATA_PROJECTION,
+ null, null, null);
+ try {
+ // Keep track of which mailboxes to notify; we'll only notify each one once
+ while (c.moveToNext()) {
+ messageCount++;
+ long mailboxId = c.getLong(0);
+ if (!mailboxesToNotify.contains(mailboxId)) {
+ mailboxesToNotify.add(mailboxId);
+ }
+ }
+ } finally {
+ c.close();
+ }
+
+ // Request service from the mailbox
+ for (Long mailboxId: mailboxesToNotify) {
+ SyncManager.serviceRequest(mailboxId);
+ }
+ Log.v(TAG, "Changed/Deleted messages: " + messageCount + ", mailboxes: " +
+ mailboxesToNotify.size());
+ }
+}
diff --git a/src/com/android/exchange/adapter/EasCalendarSyncAdapter.java b/src/com/android/exchange/adapter/EasCalendarSyncAdapter.java
new file mode 100644
index 0000000..f400a0c
--- /dev/null
+++ b/src/com/android/exchange/adapter/EasCalendarSyncAdapter.java
@@ -0,0 +1,52 @@
+/*
+ * 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.adapter;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+
+import com.android.exchange.EasSyncService;
+import com.android.exchange.EmailContent.Mailbox;
+
+/**
+ * Sync adapter class for EAS calendars
+ *
+ */
+public class EasCalendarSyncAdapter extends EasSyncAdapter {
+
+ public EasCalendarSyncAdapter(Mailbox mailbox) {
+ super(mailbox);
+ }
+
+ @Override
+ public boolean parse(ByteArrayInputStream is, EasSyncService service) throws IOException {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ @Override
+ public String getCollectionName() {
+ return "Calendar";
+ }
+
+ @Override
+ public boolean sendLocalChanges(EasSerializer s, EasSyncService service) throws IOException {
+ // TODO Auto-generated method stub
+ return false;
+ }
+}
diff --git a/src/com/android/exchange/adapter/EasContactsSyncAdapter.java b/src/com/android/exchange/adapter/EasContactsSyncAdapter.java
new file mode 100644
index 0000000..1fb4876
--- /dev/null
+++ b/src/com/android/exchange/adapter/EasContactsSyncAdapter.java
@@ -0,0 +1,385 @@
+/*
+ * 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.adapter;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+
+import android.content.ContentProviderOperation;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.Contacts;
+import android.provider.Contacts.People;
+
+import com.android.exchange.EasSyncService;
+import com.android.exchange.EmailContent.Mailbox;
+
+/**
+ * Sync adapter for EAS Contacts
+ *
+ */
+public class EasContactsSyncAdapter extends EasSyncAdapter {
+
+ private static final String WHERE_SERVER_ID_AND_ACCOUNT = "_sync_id=?";
+
+ ArrayList<Long> mDeletedIdList = new ArrayList<Long>();
+
+ ArrayList<Long> mUpdatedIdList = new ArrayList<Long>();
+
+ public EasContactsSyncAdapter(Mailbox mailbox) {
+ super(mailbox);
+ }
+
+ @Override
+ public boolean parse(ByteArrayInputStream is, EasSyncService service) throws IOException {
+ EasContactsSyncParser p = new EasContactsSyncParser(is, service);
+ return p.parse();
+ }
+
+ class EasContactsSyncParser extends EasContentParser {
+
+ String[] mBindArgument = new String[1];
+
+ String mMailboxIdAsString;
+
+ StringBuilder mExtraData = new StringBuilder(1024);
+
+ public EasContactsSyncParser(InputStream in, EasSyncService service) throws IOException {
+ super(in, service);
+ //setDebug(true); // DON'T CHECK IN WITH THIS UNCOMMENTED
+ }
+
+ class ContactMethod {
+ ContentValues values = new ContentValues();
+
+ ContactMethod(int kind, int type, String value) {
+ values.put(Contacts.ContactMethods.KIND, kind);
+ values.put(Contacts.ContactMethods.TYPE, type);
+ values.put(Contacts.ContactMethods.DATA, value);
+ }
+ }
+
+ class Phone {
+ ContentValues values = new ContentValues();
+
+ Phone(int type, String value) {
+ values.put(Contacts.Phones.TYPE, type);
+ values.put(Contacts.Phones.NUMBER, value);
+ }
+ }
+
+ @Override
+ public void wipe() {
+ // TODO Auto-generated method stub
+ }
+
+ void saveExtraData (int tag) throws IOException {
+ mExtraData.append(name);
+ mExtraData.append("~");
+ mExtraData.append(getValue());
+ mExtraData.append('~');
+ }
+
+ public void addData(String serverId, ArrayList<ContentProviderOperation> ops)
+ throws IOException {
+ String firstName = null;
+ String lastName = null;
+ String companyName = null;
+ ArrayList<ContactMethod> contactMethods = new ArrayList<ContactMethod>();
+ ArrayList<Phone> phones = new ArrayList<Phone>();
+ while (nextTag(EasTags.SYNC_APPLICATION_DATA) != END) {
+ switch (tag) {
+ case EasTags.CONTACTS_FIRST_NAME:
+ firstName = getValue();
+ break;
+ case EasTags.CONTACTS_LAST_NAME:
+ lastName = getValue();
+ break;
+ case EasTags.CONTACTS_COMPANY_NAME:
+ companyName = getValue();
+ break;
+ case EasTags.CONTACTS_EMAIL1_ADDRESS:
+ case EasTags.CONTACTS_EMAIL2_ADDRESS:
+ case EasTags.CONTACTS_EMAIL3_ADDRESS:
+ contactMethods.add(new ContactMethod(Contacts.KIND_EMAIL,
+ Contacts.ContactMethods.TYPE_OTHER, getValue()));
+ break;
+ case EasTags.CONTACTS_BUSINESS2_TELEPHONE_NUMBER:
+ case EasTags.CONTACTS_BUSINESS_TELEPHONE_NUMBER:
+ phones.add(new Phone(Contacts.Phones.TYPE_WORK, getValue()));
+ break;
+ case EasTags.CONTACTS_BUSINESS_FAX_NUMBER:
+ phones.add(new Phone(Contacts.Phones.TYPE_FAX_WORK, getValue()));
+ break;
+ case EasTags.CONTACTS_HOME_FAX_NUMBER:
+ phones.add(new Phone(Contacts.Phones.TYPE_FAX_HOME, getValue()));
+ break;
+ case EasTags.CONTACTS_HOME_TELEPHONE_NUMBER:
+ case EasTags.CONTACTS_HOME2_TELEPHONE_NUMBER:
+ phones.add(new Phone(Contacts.Phones.TYPE_HOME, getValue()));
+ break;
+ case EasTags.CONTACTS_MOBILE_TELEPHONE_NUMBER:
+ case EasTags.CONTACTS_CAR_TELEPHONE_NUMBER:
+ phones.add(new Phone(Contacts.Phones.TYPE_MOBILE, getValue()));
+ break;
+ case EasTags.CONTACTS_PAGER_NUMBER:
+ phones.add(new Phone(Contacts.Phones.TYPE_PAGER, getValue()));
+ break;
+ // All tags that we don't use (except for a few like picture and body) need to
+ // be saved, even if we're not using them. Otherwise, when we upload changes,
+ // those items will be deleted back on the server.
+ case EasTags.CONTACTS_ANNIVERSARY:
+ case EasTags.CONTACTS_ASSISTANT_NAME:
+ case EasTags.CONTACTS_ASSISTANT_TELEPHONE_NUMBER:
+ case EasTags.CONTACTS_BIRTHDAY:
+ case EasTags.CONTACTS_BUSINESS_ADDRESS_CITY:
+ case EasTags.CONTACTS_BUSINESS_ADDRESS_COUNTRY:
+ case EasTags.CONTACTS_BUSINESS_ADDRESS_POSTAL_CODE:
+ case EasTags.CONTACTS_BUSINESS_ADDRESS_STATE:
+ case EasTags.CONTACTS_BUSINESS_ADDRESS_STREET:
+ case EasTags.CONTACTS_CATEGORIES:
+ case EasTags.CONTACTS_CATEGORY:
+ case EasTags.CONTACTS_CHILDREN:
+ case EasTags.CONTACTS_CHILD:
+ case EasTags.CONTACTS_DEPARTMENT:
+ case EasTags.CONTACTS_FILE_AS:
+ case EasTags.CONTACTS_HOME_ADDRESS_CITY:
+ case EasTags.CONTACTS_HOME_ADDRESS_COUNTRY:
+ case EasTags.CONTACTS_HOME_ADDRESS_POSTAL_CODE:
+ case EasTags.CONTACTS_HOME_ADDRESS_STATE:
+ case EasTags.CONTACTS_HOME_ADDRESS_STREET:
+ case EasTags.CONTACTS_JOB_TITLE:
+ case EasTags.CONTACTS_MIDDLE_NAME:
+ case EasTags.CONTACTS_OFFICE_LOCATION:
+ case EasTags.CONTACTS_OTHER_ADDRESS_CITY:
+ case EasTags.CONTACTS_OTHER_ADDRESS_COUNTRY:
+ case EasTags.CONTACTS_OTHER_ADDRESS_POSTAL_CODE:
+ case EasTags.CONTACTS_OTHER_ADDRESS_STATE:
+ case EasTags.CONTACTS_OTHER_ADDRESS_STREET:
+ case EasTags.CONTACTS_RADIO_TELEPHONE_NUMBER:
+ case EasTags.CONTACTS_SPOUSE:
+ case EasTags.CONTACTS_SUFFIX:
+ case EasTags.CONTACTS_TITLE:
+ case EasTags.CONTACTS_WEBPAGE:
+ case EasTags.CONTACTS_YOMI_COMPANY_NAME:
+ case EasTags.CONTACTS_YOMI_FIRST_NAME:
+ case EasTags.CONTACTS_YOMI_LAST_NAME:
+ case EasTags.CONTACTS_COMPRESSED_RTF:
+ //case EasTags.CONTACTS_PICTURE:
+ case EasTags.CONTACTS2_CUSTOMER_ID:
+ case EasTags.CONTACTS2_GOVERNMENT_ID:
+ case EasTags.CONTACTS2_IM_ADDRESS:
+ case EasTags.CONTACTS2_IM_ADDRESS_2:
+ case EasTags.CONTACTS2_IM_ADDRESS_3:
+ case EasTags.CONTACTS2_MANAGER_NAME:
+ case EasTags.CONTACTS2_COMPANY_MAIN_PHONE:
+ case EasTags.CONTACTS2_ACCOUNT_NAME:
+ case EasTags.CONTACTS2_NICKNAME:
+ case EasTags.CONTACTS2_MMS:
+ saveExtraData(tag);
+ break;
+ default:
+ skipTag();
+ }
+ }
+
+ // Ok, ready to create our contact...
+ // First pass, no batch... Eventually, move to changesParser
+ ContentValues values = new ContentValues();
+
+ // TODO Do something with the extras (i.e. find a home for them)
+ String extraData = mExtraData.toString();
+ mService.userLog(extraData);
+
+ // We must have first name, last name, or company name
+ String name;
+ if (firstName != null || lastName != null) {
+ if (firstName == null) {
+ name = lastName;
+ } else if (lastName == null) {
+ name = firstName;
+ } else {
+ name = firstName + ' ' + lastName;
+ }
+ } else if (companyName != null) {
+ name = companyName;
+ } else {
+ return;
+ }
+
+ values.put(Contacts.People.NAME, name);
+ values.put("_sync_id", serverId);
+ // TODO Use proper value here; need to ask jham
+ //values.put("_sync_account", "EAS");
+ Uri contactUri =
+ Contacts.People.createPersonInMyContactsGroup(mContentResolver, values);
+
+ Uri contactMethodsUri = Uri.withAppendedPath(contactUri,
+ Contacts.People.ContactMethods.CONTENT_DIRECTORY);
+ for (ContactMethod cm: contactMethods) {
+ mContentResolver.insert(contactMethodsUri, cm.values);
+ //ops.add(ContentProviderOperation
+ // .newInsert(contactMethodsUri).withValues(cm.values).build());
+ }
+
+ Uri phoneUri = Uri.withAppendedPath(contactUri, People.Phones.CONTENT_DIRECTORY);
+ for (Phone phone: phones) {
+ mContentResolver.insert(phoneUri, phone.values);
+ //ops.add(ContentProviderOperation
+ // .newInsert(phoneUri).withValues(phone.values).build());
+ }
+ }
+
+ public void addParser(ArrayList<ContentProviderOperation> ops) throws IOException {
+ String serverId = null;
+ while (nextTag(EasTags.SYNC_ADD) != END) {
+ switch (tag) {
+ case EasTags.SYNC_SERVER_ID: // same as
+ serverId = getValue();
+ break;
+ case EasTags.SYNC_APPLICATION_DATA:
+ addData(serverId, ops);
+ break;
+ default:
+ skipTag();
+ }
+ }
+ }
+
+ private Cursor getServerIdCursor(String serverId) {
+ mBindArgument[0] = serverId;
+ //bindArguments[1] = "EAS";
+ // TODO Find proper constant for _id
+ return mContentResolver.query(Contacts.People.CONTENT_URI, new String[] {"_id"},
+ WHERE_SERVER_ID_AND_ACCOUNT, mBindArgument, null);
+ }
+
+ public void deleteParser(ArrayList<ContentProviderOperation> ops) throws IOException {
+ while (nextTag(EasTags.SYNC_DELETE) != END) {
+ switch (tag) {
+ case EasTags.SYNC_SERVER_ID:
+ String serverId = getValue();
+ // Find the message in this mailbox with the given serverId
+ Cursor c = getServerIdCursor(serverId);
+ try {
+ if (c.moveToFirst()) {
+ mService.userLog("Deleting " + serverId);
+ mContentResolver.delete(ContentUris
+ .withAppendedId(Contacts.People.CONTENT_URI, c.getLong(0)),
+ null, null);
+ //ops.add(ContentProviderOperation.newDelete(
+ // ContentUris.withAppendedId(Contacts.People.CONTENT_URI,
+ // c.getLong(0))).build());
+ }
+ } finally {
+ c.close();
+ }
+ break;
+ default:
+ skipTag();
+ }
+ }
+ }
+
+ class ServerChange {
+ long id;
+ boolean read;
+
+ ServerChange(long _id, boolean _read) {
+ id = _id;
+ read = _read;
+ }
+ }
+
+ /**
+ * A change operation on a contact is implemented as a delete followed by an add, since the
+ * change data is always a full contact.
+ *
+ * @param ops the array of pending ContactProviderOperations.
+ * @throws IOException
+ */
+ public void changeParser(ArrayList<ContentProviderOperation> ops) throws IOException {
+ String serverId = null;
+ while (nextTag(EasTags.SYNC_CHANGE) != END) {
+ switch (tag) {
+ case EasTags.SYNC_SERVER_ID:
+ serverId = getValue();
+ Cursor c = getServerIdCursor(serverId);
+ try {
+ if (c.moveToFirst()) {
+ mContentResolver.delete(ContentUris
+ .withAppendedId(Contacts.People.CONTENT_URI, c.getLong(0)),
+ null, null);
+ //ops.add(ContentProviderOperation.newDelete(
+ // ContentUris.withAppendedId(Contacts.People.CONTENT_URI,
+ // c.getLong(0))).build());
+ mService.userLog("Changing " + serverId);
+ }
+ } finally {
+ c.close();
+ }
+ break;
+ case EasTags.SYNC_APPLICATION_DATA:
+ addData(serverId, ops);
+ default:
+ skipTag();
+ }
+ }
+ }
+
+ public void commandsParser() throws IOException {
+ ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
+ while (nextTag(EasTags.SYNC_COMMANDS) != END) {
+ if (tag == EasTags.SYNC_ADD) {
+ addParser(ops);
+ } else if (tag == EasTags.SYNC_DELETE) {
+ deleteParser(ops);
+ } else if (tag == EasTags.SYNC_CHANGE) {
+ changeParser(ops);
+ } else
+ skipTag();
+ }
+
+ // Batch provider operations here
+// try {
+// mService.mContext.getContentResolver()
+// .applyBatch(ContactsProvider.EMAIL_AUTHORITY, ops);
+// } catch (RemoteException e) {
+// // There is nothing to be done here; fail by returning null
+// } catch (OperationApplicationException e) {
+// // There is nothing to be done here; fail by returning null
+// }
+
+ mService.userLog("SyncKey confirmed as: " + mMailbox.mSyncKey);
+ }
+ }
+
+ @Override
+ public String getCollectionName() {
+ return "Contacts";
+ }
+
+ @Override
+ public boolean sendLocalChanges(EasSerializer s, EasSyncService service) throws IOException {
+ return false;
+ }
+}
diff --git a/src/com/android/exchange/adapter/EasContentParser.java b/src/com/android/exchange/adapter/EasContentParser.java
new file mode 100644
index 0000000..88385bc
--- /dev/null
+++ b/src/com/android/exchange/adapter/EasContentParser.java
@@ -0,0 +1,141 @@
+/*
+ * 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.adapter;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import android.content.ContentResolver;
+import android.content.Context;
+
+import com.android.exchange.EasSyncService;
+import com.android.exchange.EmailContent.Account;
+import com.android.exchange.EmailContent.Mailbox;
+
+/**
+ * Base class for the Email and PIM sync parsers
+ * Handles the basic flow of syncKeys, looping to get more data, handling errors, etc.
+ * Each subclass must implement a handful of methods that relate specifically to the data type
+ *
+ */
+public abstract class EasContentParser extends EasParser {
+
+ EasSyncService mService;
+
+ Mailbox mMailbox;
+
+ Account mAccount;
+
+ Context mContext;
+
+ ContentResolver mContentResolver;
+
+ public EasContentParser(InputStream in, EasSyncService _service) throws IOException {
+ super(in);
+ mService = _service;
+ mContext = mService.mContext;
+ mContentResolver = mContext.getContentResolver();
+ mMailbox = mService.mMailbox;
+ mAccount = mService.mAccount;
+ }
+
+ /**
+ * Read, parse, and act on incoming commands from the Exchange server
+ * @throws IOException if the connection is broken
+ */
+ public abstract void commandsParser() throws IOException;
+
+ /**
+ * Read, parse, and act on server responses
+ * Email doesn't have any, so this isn't yet implemented anywhere. It will become abstract,
+ * in the near future, however.
+ * @throws IOException
+ */
+ public void responsesParser() throws IOException {
+ // Placeholder until needed; will become an abstract method
+ }
+
+ /**
+ * Delete all records of this class in this account
+ */
+ public abstract void wipe();
+
+ /**
+ * Loop through the top-level structure coming from the Exchange server
+ * Sync keys and the more available flag are handled here, whereas specific data parsing
+ * is handled by abstract methods implemented for each data class (e.g. Email, Contacts, etc.)
+ */
+ public boolean parse() throws IOException {
+ int status;
+ boolean moreAvailable = false;
+ // If we're not at the top of the xml tree, throw an exception
+ if (nextTag(START_DOCUMENT) != EasTags.SYNC_SYNC) {
+ throw new IOException();
+ }
+ // Loop here through the remaining xml
+ while (nextTag(START_DOCUMENT) != END_DOCUMENT) {
+ if (tag == EasTags.SYNC_COLLECTION || tag == EasTags.SYNC_COLLECTIONS) {
+ // Ignore these tags, since we've only got one collection syncing in this loop
+ } else if (tag == EasTags.SYNC_STATUS) {
+ // Status = 1 is success; everything else is a failure
+ status = getValueInt();
+ if (status != 1) {
+ mService.errorLog("Sync failed: " + status);
+ // Status = 3 means invalid sync key
+ if (status == 3) {
+ // Must delete all of the data and start over with syncKey of "0"
+ mMailbox.mSyncKey = "0";
+ // Make this a push box through the first sync
+ // TODO Make frequency conditional on user settings!
+ mMailbox.mSyncFrequency = Account.CHECK_INTERVAL_PUSH;
+ mService.errorLog("Bad sync key; RESET and delete contacts");
+ wipe();
+ // Indicate there's more so that we'll start syncing again
+ moreAvailable = true;
+ }
+ }
+ } else if (tag == EasTags.SYNC_COMMANDS) {
+ commandsParser();
+ } else if (tag == EasTags.SYNC_RESPONSES) {
+ responsesParser();
+ } else if (tag == EasTags.SYNC_MORE_AVAILABLE) {
+ moreAvailable = true;
+ } else if (tag == EasTags.SYNC_SYNC_KEY) {
+ if (mMailbox.mSyncKey.equals("0"))
+ moreAvailable = true;
+ String newKey = getValue();
+ mService.userLog("New sync key: " + newKey);
+ mMailbox.mSyncKey = newKey;
+ // If we were pushing (i.e. auto-start), now we'll become ping-triggered
+ if (mMailbox.mSyncFrequency == Account.CHECK_INTERVAL_PUSH) {
+ mMailbox.mSyncFrequency = Account.CHECK_INTERVAL_PING;
+ }
+ } else {
+ skipTag();
+ }
+ }
+
+ // Make sure we save away the new syncKey, syncFrequency, etc.
+ mMailbox.saveOrUpdate(mContext);
+
+ // Let the caller know that there's more to do
+ return moreAvailable;
+ }
+
+
+}
diff --git a/src/com/android/exchange/adapter/EasEmailSyncAdapter.java b/src/com/android/exchange/adapter/EasEmailSyncAdapter.java
new file mode 100644
index 0000000..dd3bde2
--- /dev/null
+++ b/src/com/android/exchange/adapter/EasEmailSyncAdapter.java
@@ -0,0 +1,436 @@
+/*
+ * 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.adapter;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.GregorianCalendar;
+import java.util.TimeZone;
+
+import android.content.ContentProviderOperation;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.OperationApplicationException;
+import android.database.Cursor;
+import android.os.RemoteException;
+
+import com.android.email.provider.EmailProvider;
+import com.android.exchange.EasSyncService;
+import com.android.exchange.EmailContent.Attachment;
+import com.android.exchange.EmailContent.Mailbox;
+import com.android.exchange.EmailContent.Message;
+import com.android.exchange.EmailContent.MessageColumns;
+import com.android.exchange.EmailContent.SyncColumns;
+
+/**
+ * Sync adapter for EAS email
+ *
+ */
+public class EasEmailSyncAdapter extends EasSyncAdapter {
+
+ private static final String[] UPDATES_PROJECTION = {MessageColumns.FLAG_READ};
+
+ ArrayList<Long> mDeletedIdList = new ArrayList<Long>();
+
+ ArrayList<Long> mUpdatedIdList = new ArrayList<Long>();
+
+ public EasEmailSyncAdapter(Mailbox mailbox) {
+ super(mailbox);
+ }
+
+ @Override
+ public boolean parse(ByteArrayInputStream is, EasSyncService service) throws IOException {
+ EasEmailSyncParser p = new EasEmailSyncParser(is, service);
+ return p.parse();
+ }
+
+ class EasEmailSyncParser extends EasContentParser {
+
+ private static final String WHERE_SERVER_ID_AND_MAILBOX_KEY =
+ SyncColumns.SERVER_ID + "=? and " + MessageColumns.MAILBOX_KEY + "=?";
+
+ private String mMailboxIdAsString;
+
+ String[] bindArguments = new String[2];
+
+ public EasEmailSyncParser(InputStream in, EasSyncService service) throws IOException {
+ super(in, service);
+ mMailboxIdAsString = Long.toString(mMailbox.mId);
+ //setDebug(true); // DON'T CHECK IN WITH THIS
+ }
+
+ public void wipe() {
+ mContentResolver.delete(Message.CONTENT_URI,
+ Message.MAILBOX_KEY + "=" + mMailbox.mId, null);
+ mContentResolver.delete(Message.DELETED_CONTENT_URI,
+ Message.MAILBOX_KEY + "=" + mMailbox.mId, null);
+ mContentResolver.delete(Message.UPDATED_CONTENT_URI,
+ Message.MAILBOX_KEY + "=" + mMailbox.mId, null);
+ }
+
+ public void addData (Message msg) throws IOException {
+ String to = "";
+ String from = "";
+ String cc = "";
+ String replyTo = "";
+ int size = 0;
+
+ ArrayList<Attachment> atts = new ArrayList<Attachment>();
+
+ while (nextTag(EasTags.SYNC_APPLICATION_DATA) != END) {
+ switch (tag) {
+ case EasTags.EMAIL_ATTACHMENTS:
+ break;
+ case EasTags.EMAIL_ATTACHMENT:
+ attachmentParser(atts, msg);
+ break;
+ case EasTags.EMAIL_TO:
+ to = getValue();
+ break;
+ case EasTags.EMAIL_FROM:
+ from = getValue();
+ String sender = from;
+ int q = from.indexOf('\"');
+ if (q >= 0) {
+ int qq = from.indexOf('\"', q + 1);
+ if (qq > 0) {
+ sender = from.substring(q + 1, qq);
+ }
+ }
+ msg.mDisplayName = sender;
+ break;
+ case EasTags.EMAIL_CC:
+ cc = getValue();
+ break;
+ case EasTags.EMAIL_REPLY_TO:
+ replyTo = getValue();
+ break;
+ case EasTags.EMAIL_DATE_RECEIVED:
+ String date = getValue();
+ // 2009-02-11T18:03:03.627Z
+ GregorianCalendar cal = new GregorianCalendar();
+ cal.set(Integer.parseInt(date.substring(0, 4)), Integer.parseInt(date
+ .substring(5, 7)) - 1, Integer.parseInt(date.substring(8, 10)),
+ Integer.parseInt(date.substring(11, 13)), Integer.parseInt(date
+ .substring(14, 16)), Integer.parseInt(date
+ .substring(17, 19)));
+ cal.setTimeZone(TimeZone.getTimeZone("GMT"));
+ msg.mTimeStamp = cal.getTimeInMillis();
+ break;
+ case EasTags.EMAIL_SUBJECT:
+ msg.mSubject = getValue();
+ break;
+ case EasTags.EMAIL_READ:
+ msg.mFlagRead = getValueInt() == 1;
+ break;
+ case EasTags.EMAIL_BODY:
+ msg.mTextInfo = "X;X;8;" + size; // location;encoding;charset;size
+ msg.mText = getValue();
+ // For now...
+ msg.mPreview = "Fake preview"; // Messages.previewFromText(body);
+ break;
+ default:
+ skipTag();
+ }
+ }
+
+ msg.mTo = to;
+ msg.mFrom = from;
+ msg.mCc = cc;
+ msg.mReplyTo = replyTo;
+ if (atts.size() > 0) {
+ msg.mAttachments = atts;
+ }
+
+ }
+
+ public void addParser(ArrayList<Message> emails) throws IOException {
+ Message msg = new Message();
+ msg.mAccountKey = mAccount.mId;
+ msg.mMailboxKey = mMailbox.mId;
+ msg.mFlagLoaded = Message.LOADED;
+
+ while (nextTag(EasTags.SYNC_ADD) != END) {
+ switch (tag) {
+ case EasTags.SYNC_SERVER_ID:
+ msg.mServerId = getValue();
+ break;
+ case EasTags.SYNC_APPLICATION_DATA:
+ addData(msg);
+ break;
+ default:
+ skipTag();
+ }
+ }
+
+ // Tell the provider that this is synced back
+ msg.mServerVersion = mMailbox.mSyncKey;
+ emails.add(msg);
+ }
+
+ public void attachmentParser(ArrayList<Attachment> atts, Message msg) throws IOException {
+ String fileName = null;
+ String length = null;
+ String lvl = null;
+
+ while (nextTag(EasTags.EMAIL_ATTACHMENT) != END) {
+ switch (tag) {
+ case EasTags.EMAIL_DISPLAY_NAME:
+ fileName = getValue();
+ break;
+ case EasTags.EMAIL_ATT_NAME:
+ lvl = getValue();
+ break;
+ case EasTags.EMAIL_ATT_SIZE:
+ length = getValue();
+ break;
+ default:
+ skipTag();
+ }
+ }
+
+ if (fileName != null && length != null && lvl != null) {
+ Attachment att = new Attachment();
+ att.mEncoding = "base64";
+ att.mSize = Long.parseLong(length);
+ att.mFileName = fileName;
+ atts.add(att);
+ msg.mFlagAttachment = true;
+ }
+ }
+
+ private Cursor getServerIdCursor(String serverId, String[] projection) {
+ bindArguments[0] = serverId;
+ bindArguments[1] = mMailboxIdAsString;
+ return mContentResolver.query(Message.CONTENT_URI, projection,
+ WHERE_SERVER_ID_AND_MAILBOX_KEY, bindArguments, null);
+ }
+
+ public void deleteParser(ArrayList<Long> deletes) throws IOException {
+ while (nextTag(EasTags.SYNC_DELETE) != END) {
+ switch (tag) {
+ case EasTags.SYNC_SERVER_ID:
+ String serverId = getValue();
+ // Find the message in this mailbox with the given serverId
+ Cursor c = getServerIdCursor(serverId, Message.ID_COLUMN_PROJECTION);
+ try {
+ if (c.moveToFirst()) {
+ mService.userLog("Deleting " + serverId);
+ deletes.add(c.getLong(Message.ID_COLUMNS_ID_COLUMN));
+ }
+ } finally {
+ c.close();
+ }
+ break;
+ default:
+ skipTag();
+ }
+ }
+ }
+
+ class ServerChange {
+ long id;
+ boolean read;
+
+ ServerChange(long _id, boolean _read) {
+ id = _id;
+ read = _read;
+ }
+ }
+
+ public void changeParser(ArrayList<ServerChange> changes) throws IOException {
+ String serverId = null;
+ boolean oldRead = false;
+ boolean read = true;
+ long id = 0;
+ while (nextTag(EasTags.SYNC_CHANGE) != END) {
+ switch (tag) {
+ case EasTags.SYNC_SERVER_ID:
+ serverId = getValue();
+ Cursor c = getServerIdCursor(serverId, Message.LIST_PROJECTION);
+ try {
+ if (c.moveToFirst()) {
+ mService.userLog("Changing " + serverId);
+ oldRead = c.getInt(Message.LIST_READ_COLUMN) == Message.READ;
+ id = c.getLong(Message.LIST_ID_COLUMN);
+ }
+ } finally {
+ c.close();
+ }
+ break;
+ case EasTags.EMAIL_READ:
+ read = getValueInt() == 1;
+ break;
+ case EasTags.SYNC_APPLICATION_DATA:
+ break;
+ default:
+ skipTag();
+ }
+ }
+ if (oldRead != read) {
+ changes.add(new ServerChange(id, read));
+ }
+ }
+
+ public void commandsParser() throws IOException {
+ ArrayList<Message> newEmails = new ArrayList<Message>();
+ ArrayList<Long> deletedEmails = new ArrayList<Long>();
+ ArrayList<ServerChange> changedEmails = new ArrayList<ServerChange>();
+
+ while (nextTag(EasTags.SYNC_COMMANDS) != END) {
+ if (tag == EasTags.SYNC_ADD) {
+ addParser(newEmails);
+ } else if (tag == EasTags.SYNC_DELETE) {
+ deleteParser(deletedEmails);
+ } else if (tag == EasTags.SYNC_CHANGE) {
+ changeParser(changedEmails);
+ } else
+ skipTag();
+ }
+
+ // Use a batch operation to handle the changes
+ // TODO New mail notifications? Who looks for these?
+ ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
+ for (Message content : newEmails) {
+ content.addSaveOps(ops);
+ }
+ for (Long id : deletedEmails) {
+ ops.add(ContentProviderOperation.newDelete(
+ ContentUris.withAppendedId(Message.CONTENT_URI, id)).build());
+ }
+ if (!changedEmails.isEmpty()) {
+ // Server wins in a conflict...
+ for (ServerChange change : changedEmails) {
+ // For now, don't handle read->unread
+ ContentValues cv = new ContentValues();
+ cv.put(MessageColumns.FLAG_READ, change.read);
+ ops.add(ContentProviderOperation.newUpdate(
+ ContentUris.withAppendedId(Message.CONTENT_URI, change.id))
+ .withValues(cv)
+ .build());
+ }
+ }
+ ops.add(ContentProviderOperation.newUpdate(
+ ContentUris.withAppendedId(Mailbox.CONTENT_URI, mMailbox.mId)).withValues(
+ mMailbox.toContentValues()).build());
+
+ // If we've sent local deletions, clear out the deleted table
+ for (Long id: mDeletedIdList) {
+ ops.add(ContentProviderOperation.newDelete(
+ ContentUris.withAppendedId(Message.DELETED_CONTENT_URI, id)).build());
+ }
+
+ try {
+ mService.mContext.getContentResolver()
+ .applyBatch(EmailProvider.EMAIL_AUTHORITY, ops);
+ } catch (RemoteException e) {
+ // There is nothing to be done here; fail by returning null
+ } catch (OperationApplicationException e) {
+ // There is nothing to be done here; fail by returning null
+ }
+
+ mService.userLog("SyncKey confirmed as: " + mMailbox.mSyncKey);
+ }
+ }
+
+ @Override
+ public String getCollectionName() {
+ return "Email";
+ }
+
+ @Override
+ public boolean sendLocalChanges(EasSerializer s, EasSyncService service) throws IOException {
+ ContentResolver cr = service.mContext.getContentResolver();
+
+ // Find any of our deleted items
+ Cursor c = cr.query(Message.DELETED_CONTENT_URI, Message.LIST_PROJECTION,
+ MessageColumns.MAILBOX_KEY + '=' + mMailbox.mId, null, null);
+ boolean first = true;
+ // We keep track of the list of deleted item id's so that we can remove them from the
+ // deleted table after the server receives our command
+ mDeletedIdList.clear();
+ try {
+ while (c.moveToNext()) {
+ if (first) {
+ s.start("Commands");
+ first = false;
+ }
+ // Send the command to delete this message
+ s.start("Delete")
+ .data("ServerId", c.getString(Message.LIST_SERVER_ID_COLUMN))
+ .end("Delete");
+ mDeletedIdList.add(c.getLong(Message.LIST_ID_COLUMN));
+ }
+ } finally {
+ c.close();
+ }
+
+ // Do the same now for updated items
+ c = cr.query(Message.UPDATED_CONTENT_URI, Message.LIST_PROJECTION,
+ MessageColumns.MAILBOX_KEY + '=' + mMailbox.mId, null, null);
+ // We keep track of the list of updated item id's so that we can remove them from the
+ // deleted table after the server receives our command
+ mUpdatedIdList.clear();
+ try {
+ while (c.moveToNext()) {
+ long id = c.getLong(Message.LIST_ID_COLUMN);
+ // Say we've handled this update
+ mUpdatedIdList.add(id);
+ // We have the id of the changed item. But first, we have to find out its current
+ // state, since the updated table saves the opriginal state
+ Cursor currentCursor = cr.query(ContentUris.withAppendedId(Message.CONTENT_URI, id),
+ UPDATES_PROJECTION, null, null, null);
+ try {
+ // If this item no longer exists (shouldn't be possible), just move along
+ if (!currentCursor.moveToFirst()) {
+ continue;
+ }
+ int read = currentCursor.getInt(0);
+ if (read == c.getInt(Message.LIST_READ_COLUMN)) {
+ // The read state hasn't really changed, so move on...
+ continue;
+ }
+ if (first) {
+ s.start("Commands");
+ first = false;
+ }
+ // Send the change to "read". We'll do "flagged" here eventually as well
+ // TODO Add support for flags here (EAS 12.0 and above)
+ s.start("Change")
+ .data("ServerId", c.getString(Message.LIST_SERVER_ID_COLUMN))
+ .start("ApplicationData")
+ .data("Read", Integer.toString(read))
+ .end("ApplicationData")
+ .end("Change");
+ } finally {
+ currentCursor.close();
+ }
+ }
+ } finally {
+ c.close();
+ }
+
+ if (!first) {
+ s.end("Commands");
+ }
+ return false;
+ }
+}
diff --git a/src/com/android/exchange/adapter/EasFolderSyncParser.java b/src/com/android/exchange/adapter/EasFolderSyncParser.java
new file mode 100644
index 0000000..0e0b07f
--- /dev/null
+++ b/src/com/android/exchange/adapter/EasFolderSyncParser.java
@@ -0,0 +1,333 @@
+/*
+ * 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.adapter;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import com.android.email.provider.EmailProvider;
+import com.android.exchange.Eas;
+import com.android.exchange.EasSyncService;
+import com.android.exchange.MockParserStream;
+import com.android.exchange.SyncManager;
+import com.android.exchange.EmailContent.Account;
+import com.android.exchange.EmailContent.Mailbox;
+import com.android.exchange.EmailContent.MailboxColumns;
+
+import android.content.ContentProviderOperation;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.Context;
+import android.content.OperationApplicationException;
+import android.database.Cursor;
+import android.os.RemoteException;
+import android.util.Log;
+
+/**
+ * Parse the result of a FolderSync command
+ *
+ * Handles the addition, deletion, and changes to folders in the user's Exchange account.
+ **/
+
+public class EasFolderSyncParser extends EasParser {
+
+ public static final String TAG = "FolderSyncParser";
+
+ // These are defined by the EAS protocol
+ public static final int USER_FOLDER_TYPE = 1;
+ public static final int INBOX_TYPE = 2;
+ public static final int DRAFTS_TYPE = 3;
+ public static final int DELETED_TYPE = 4;
+ public static final int SENT_TYPE = 5;
+ public static final int OUTBOX_TYPE = 6;
+ public static final int TASKS_TYPE = 7;
+ public static final int CALENDAR_TYPE = 8;
+ public static final int CONTACTS_TYPE = 9;
+ public static final int NOTES_TYPE = 10;
+ public static final int JOURNAL_TYPE = 11;
+ public static final int USER_MAILBOX_TYPE = 12;
+
+ public static final List<Integer> mValidFolderTypes = Arrays.asList(INBOX_TYPE, DRAFTS_TYPE,
+ DELETED_TYPE, SENT_TYPE, OUTBOX_TYPE, USER_MAILBOX_TYPE, CALENDAR_TYPE, CONTACTS_TYPE);
+
+ private static final String WHERE_SERVER_ID_AND_ACCOUNT = MailboxColumns.SERVER_ID + "=? and " +
+ MailboxColumns.ACCOUNT_KEY + "=?";
+
+ private static final String WHERE_DISPLAY_NAME_AND_ACCOUNT = MailboxColumns.DISPLAY_NAME +
+ "=? and " + MailboxColumns.ACCOUNT_KEY + "=?";
+
+ private static final String WHERE_PARENT_SERVER_ID_AND_ACCOUNT =
+ MailboxColumns.PARENT_SERVER_ID +"=? and " + MailboxColumns.ACCOUNT_KEY + "=?";
+
+ private static final String[] MAILBOX_ID_COLUMNS_PROJECTION =
+ new String[] {MailboxColumns.ID, MailboxColumns.SERVER_ID};
+
+ private Account mAccount;
+
+ private long mAccountId;
+
+ private String mAccountIdAsString;
+
+ private EasSyncService mService;
+
+ private Context mContext;
+
+ private ContentResolver mContentResolver;
+
+ private MockParserStream mMock = null;
+
+ private String[] mBindArguments = new String[2];
+
+ public EasFolderSyncParser(InputStream in, EasSyncService service) throws IOException {
+ super(in);
+ mService = service;
+ mAccount = service.mAccount;
+ mAccountId = mAccount.mId;
+ mAccountIdAsString = Long.toString(mAccountId);
+ mContext = service.mContext;
+ mContentResolver = mContext.getContentResolver();
+ if (in instanceof MockParserStream) {
+ mMock = (MockParserStream)in;
+ }
+ setDebug(true);
+ }
+
+ public boolean parse() throws IOException {
+ int status;
+ boolean res = false;
+ if (nextTag(START_DOCUMENT) != EasTags.FOLDER_FOLDER_SYNC)
+ throw new IOException();
+ while (nextTag(START_DOCUMENT) != END_DOCUMENT) {
+ if (tag == EasTags.FOLDER_STATUS) {
+ status = getValueInt();
+ if (status != Eas.FOLDER_STATUS_OK) {
+ mService.errorLog("FolderSync failed: " + status);
+ if (status == Eas.FOLDER_STATUS_INVALID_KEY) {
+ mAccount.mSyncKey = "0";
+ mService.errorLog("Bad sync key; RESET and delete all folders");
+ mContentResolver.delete(Mailbox.CONTENT_URI,
+ MailboxColumns.ACCOUNT_KEY + '=' + mAccountId, null);
+ // Stop existing syncs and reconstruct _main
+ SyncManager.folderListReloaded(mAccountId);
+ res = 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
+ mService.errorLog("Throwing IOException; will retry later");
+ throw new IOException();
+ }
+ }
+ } else if (tag == EasTags.FOLDER_SYNC_KEY) {
+ mAccount.mSyncKey = getValue();
+ mService.userLog("New Account SyncKey: " + mAccount.mSyncKey);
+ } else if (tag == EasTags.FOLDER_CHANGES) {
+ changesParser();
+ } else
+ skipTag();
+ }
+
+ mAccount.saveOrUpdate(mContext);
+ return res;
+ }
+
+ private Cursor getServerIdCursor(String serverId) {
+ mBindArguments[0] = serverId;
+ mBindArguments[1] = mAccountIdAsString;
+ return mContentResolver.query(Mailbox.CONTENT_URI, new String[] {MailboxColumns.ID},
+ WHERE_SERVER_ID_AND_ACCOUNT, mBindArguments, null);
+ }
+
+ public void deleteParser(ArrayList<ContentProviderOperation> ops) throws IOException {
+ while (nextTag(EasTags.SYNC_DELETE) != END) {
+ switch (tag) {
+ case EasTags.FOLDER_SERVER_ID:
+ String serverId = getValue();
+ // Find the mailbox in this account with the given serverId
+ Cursor c = getServerIdCursor(serverId);
+ try {
+ if (c.moveToFirst()) {
+ mService.userLog("Deleting " + serverId);
+ ops.add(ContentProviderOperation.newDelete(
+ ContentUris.withAppendedId(Mailbox.CONTENT_URI,
+ c.getLong(0))).build());
+ }
+ } finally {
+ c.close();
+ }
+ break;
+ default:
+ skipTag();
+ }
+ }
+ }
+
+ public void addParser(ArrayList<ContentProviderOperation> ops) throws IOException {
+ String name = null;
+ String serverId = null;
+ String parentId = null;
+ int type = 0;
+
+ while (nextTag(EasTags.FOLDER_ADD) != END) {
+ switch (tag) {
+ case EasTags.FOLDER_DISPLAY_NAME: {
+ name = getValue();
+ break;
+ }
+ case EasTags.FOLDER_TYPE: {
+ type = getValueInt();
+ break;
+ }
+ case EasTags.FOLDER_PARENT_ID: {
+ parentId = getValue();
+ break;
+ }
+ case EasTags.FOLDER_SERVER_ID: {
+ serverId = getValue();
+ break;
+ }
+ default:
+ skipTag();
+ }
+ }
+ if (mValidFolderTypes.contains(type)) {
+ Mailbox m = new Mailbox();
+ m.mDisplayName = name;
+ m.mServerId = serverId;
+ m.mAccountKey = mAccountId;
+ m.mSyncFrequency = Account.CHECK_INTERVAL_NEVER;
+ switch (type) {
+ case INBOX_TYPE:
+ m.mSyncFrequency = Account.CHECK_INTERVAL_PUSH;
+ m.mType = Mailbox.TYPE_INBOX;
+ break;
+ case OUTBOX_TYPE:
+ m.mSyncFrequency = Account.CHECK_INTERVAL_NEVER;
+ // TYPE_OUTBOX mailboxes are known by SyncManager to sync whenever they aren't
+ // empty. The value of mSyncFrequency is ignored for this kind of mailbox.
+ m.mType = Mailbox.TYPE_OUTBOX;
+ break;
+ case SENT_TYPE:
+ m.mType = Mailbox.TYPE_SENT;
+ break;
+ case DRAFTS_TYPE:
+ m.mType = Mailbox.TYPE_DRAFTS;
+ break;
+ case DELETED_TYPE:
+ m.mType = Mailbox.TYPE_TRASH;
+ break;
+ case CALENDAR_TYPE:
+ m.mType = Mailbox.TYPE_CALENDAR;
+ // TODO This could be push, depending on settings
+ // For now, no sync, since it's not yet implemented
+ break;
+ case CONTACTS_TYPE:
+ m.mType = Mailbox.TYPE_CONTACTS;
+ // TODO Frequency below should depend on settings
+ m.mSyncFrequency = Account.CHECK_INTERVAL_PUSH;
+ break;
+ }
+
+ // Make boxes like Contacts and Calendar invisible in the folder list
+ m.mFlagVisible = (m.mType < Mailbox.TYPE_NOT_EMAIL);
+
+ if (!parentId.equals("0")) {
+ m.mParentServerId = parentId;
+ }
+
+ Log.v(TAG, "Adding mailbox: " + m.mDisplayName);
+ ops.add(ContentProviderOperation
+ .newInsert(Mailbox.CONTENT_URI).withValues(m.toContentValues()).build());
+ }
+
+ return;
+ }
+
+ public void changesParser() throws IOException {
+ // Keep track of new boxes, deleted boxes, updated boxes
+ ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
+
+ while (nextTag(EasTags.FOLDER_CHANGES) != END) {
+ // TODO Handle FOLDER_CHANGE and FOLDER_DELETE
+ if (tag == EasTags.FOLDER_ADD) {
+ addParser(ops);
+ } else if (tag == EasTags.FOLDER_DELETE) {
+ deleteParser(ops);
+ } else if (tag == EasTags.FOLDER_COUNT) {
+ getValueInt();
+ } else
+ skipTag();
+ }
+
+ // The mock stream is used for junit tests, so that the parsing code can be tested
+ // separately from the provider code.
+ // TODO Change tests to not require this; remove references to the mock stream
+ if (mMock != null) {
+ mMock.setResult(null);
+ return;
+ }
+
+ // Create the new mailboxes in a single batch operation
+ if (!ops.isEmpty()) {
+ mService.userLog("Applying " + ops.size() + " mailbox operations.");
+
+ // Then, we create an update for the account (most importantly, updating the syncKey)
+ ops.add(ContentProviderOperation.newUpdate(
+ ContentUris.withAppendedId(Account.CONTENT_URI, mAccountId)).withValues(
+ mAccount.toContentValues()).build());
+
+ // Finally, we execute the batch
+ try {
+ mService.mContext.getContentResolver()
+ .applyBatch(EmailProvider.EMAIL_AUTHORITY, ops);
+ mService.userLog("New syncKey: " + mAccount.mSyncKey);
+ } catch (RemoteException e) {
+ // There is nothing to be done here; fail by returning null
+ } catch (OperationApplicationException e) {
+ // There is nothing to be done here; fail by returning null
+ }
+
+ // Look for sync issues and its children and delete them
+ // I'm not aware of any other way to deal with this properly
+ mBindArguments[0] = "Sync Issues";
+ mBindArguments[1] = mAccountIdAsString;
+ Cursor c = mContentResolver.query(Mailbox.CONTENT_URI, MAILBOX_ID_COLUMNS_PROJECTION,
+ WHERE_DISPLAY_NAME_AND_ACCOUNT, mBindArguments, null);
+ String parentServerId = null;
+ long id = 0;
+ try {
+ if (c.moveToFirst()) {
+ id = c.getLong(0);
+ parentServerId = c.getString(1);
+ }
+ } finally {
+ c.close();
+ }
+ if (parentServerId != null) {
+ mContentResolver.delete(ContentUris.withAppendedId(Mailbox.CONTENT_URI, id),
+ null, null);
+ mBindArguments[0] = parentServerId;
+ mContentResolver.delete(Mailbox.CONTENT_URI, WHERE_PARENT_SERVER_ID_AND_ACCOUNT,
+ mBindArguments);
+ }
+ }
+ }
+
+}
diff --git a/src/com/android/exchange/EasMoveParser.java b/src/com/android/exchange/adapter/EasMoveParser.java
similarity index 72%
rename from src/com/android/exchange/EasMoveParser.java
rename to src/com/android/exchange/adapter/EasMoveParser.java
index 3ec8c22..a3a23fa 100644
--- a/src/com/android/exchange/EasMoveParser.java
+++ b/src/com/android/exchange/adapter/EasMoveParser.java
@@ -15,32 +15,39 @@
* limitations under the License.
*/
-package com.android.exchange;
+package com.android.exchange.adapter;
import java.io.IOException;
import java.io.InputStream;
import android.util.Log;
+import com.android.exchange.EasSyncService;
import com.android.exchange.EmailContent.Mailbox;
+/**
+ * Parse the result of a Move command
+ *
+ * This is currently unused, as "move to folder" is not implemented in the application.
+ **/
public class EasMoveParser extends EasParser {
private static final String TAG = "EasMoveParser";
- private EasService mService;
+ private EasSyncService mService;
private Mailbox mMailbox;
protected boolean mMoreAvailable = false;
- public EasMoveParser(InputStream in, EasService service) throws IOException {
+ public EasMoveParser(InputStream in, EasSyncService service) throws IOException {
super(in);
mService = service;
mMailbox = service.mMailbox;
- setDebug(true);
+ //setDebug(true);
}
- public void parse() throws IOException {
+ public boolean parse() throws IOException {
int status;
- if (nextTag(START_DOCUMENT) != EasTags.MOVE_MOVE_ITEMS)
+ if (nextTag(START_DOCUMENT) != EasTags.MOVE_MOVE_ITEMS) {
throw new IOException();
+ }
while (nextTag(START_DOCUMENT) != END_DOCUMENT) {
if (tag == EasTags.MOVE_RESPONSE) {
// Ignore
@@ -50,10 +57,13 @@
Log.e(TAG, "Sync failed (3 is success): " + status);
}
} else if (tag == EasTags.SYNC_RESPONSES) {
+ // TODO See if any of these cases need to be handled
skipTag();
- } else
+ } else {
skipTag();
+ }
}
mMailbox.save(mService.mContext);
+ return false;
}
}
diff --git a/src/com/android/exchange/EasOutboxService.java b/src/com/android/exchange/adapter/EasOutboxService.java
similarity index 71%
rename from src/com/android/exchange/EasOutboxService.java
rename to src/com/android/exchange/adapter/EasOutboxService.java
index 21e1302..461654b 100644
--- a/src/com/android/exchange/EasOutboxService.java
+++ b/src/com/android/exchange/adapter/EasOutboxService.java
@@ -15,20 +15,25 @@
* limitations under the License.
*/
-package com.android.exchange;
+package com.android.exchange.adapter;
+import java.io.IOException;
import java.net.HttpURLConnection;
+import com.android.exchange.EasSyncService;
+import com.android.exchange.SyncManager;
import com.android.exchange.EmailContent.HostAuth;
import com.android.exchange.EmailContent.Mailbox;
import com.android.exchange.EmailContent.Message;
+import com.android.exchange.EmailContent.MessageColumns;
+import com.android.exchange.utility.Rfc822Formatter;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
-public class EasOutboxService extends EasService {
+public class EasOutboxService extends EasSyncService {
public EasOutboxService(Context _context, Mailbox _mailbox) {
super(_context, _mailbox);
@@ -39,26 +44,24 @@
mPassword = ha.mPassword;
}
- public void run () {
+ public void run() {
mThread = Thread.currentThread();
String uniqueId = android.provider.Settings.System.getString(mContext.getContentResolver(),
android.provider.Settings.System.ANDROID_ID);
try {
Cursor c = mContext.getContentResolver().query(Message.CONTENT_URI,
- Message.CONTENT_PROJECTION, "mMailbox=" + mMailbox, null, null);
+ Message.CONTENT_PROJECTION, MessageColumns.MAILBOX_KEY + '=' + mMailbox,
+ null, null);
try {
- if (c.moveToFirst()) {
+ while (c.moveToNext()) {
Message msg = new Message().restore(c);
if (msg != null) {
String data = Rfc822Formatter
- .writeEmailAsRfc822String(mContext, mAccount, msg, uniqueId);
+ .writeEmailAsRfc822String(mContext, mAccount, msg, uniqueId);
HttpURLConnection uc = sendEASPostCommand("SendMail&SaveInSent=T", data);
int code = uc.getResponseCode();
- //Intent intent = new Intent(MessageListView.MAIL_UPDATE);
- //intent.putExtra("type", "toast");
if (code == HttpURLConnection.HTTP_OK) {
- //intent.putExtra("text", "Your message with subject \"" + msg.mSubject + "\" has been sent.");
- log("Deleting message...");
+ userLog("Deleting message...");
mContext.getContentResolver().delete(ContentUris.withAppendedId(
Message.CONTENT_URI, msg.mId), null, null);
} else {
@@ -66,19 +69,21 @@
cv.put("uid", 1);
Message.update(mContext,
Message.CONTENT_URI, msg.mId, cv);
- //intent.putExtra("text", "WHOA! Your message with subject \"" + msg.mSubject + "\" failed to send.");
}
- //mContext.sendBroadcast(intent);
- updateUI();
+ // TODO How will the user know that the message sent or not?
}
}
- } catch (Exception e) {
- e.printStackTrace();
} finally {
c.close();
}
- } catch (RuntimeException e1) {
- e1.printStackTrace();
+ } catch (IOException e) {
+ userLog("Caught IOException");
+ mExitStatus = EXIT_IO_ERROR;
+ } catch (Exception e) {
+ mExitStatus = EXIT_EXCEPTION;
+ } finally {
+ userLog(mMailbox.mDisplayName + ": sync finished");
+ SyncManager.done(this);
}
}
}
\ No newline at end of file
diff --git a/src/com/android/exchange/adapter/EasParser.java b/src/com/android/exchange/adapter/EasParser.java
new file mode 100644
index 0000000..190ed03
--- /dev/null
+++ b/src/com/android/exchange/adapter/EasParser.java
@@ -0,0 +1,470 @@
+/*
+ * 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.adapter;
+
+import java.io.*;
+import java.util.ArrayList;
+
+import com.android.exchange.EasException;
+
+import android.content.Context;
+import android.util.Log;
+
+/**
+ * Extremely fast and lightweight WBXML parser, implementing only the subset of WBXML that
+ * EAS uses (as defined in the EAS specification)
+ *
+ */
+public abstract class EasParser {
+
+ private static final String TAG = "EasParser";
+
+ // The following constants are Wbxml standard
+ public static final int START_DOCUMENT = 0;
+ public static final int DONE = 1;
+ public static final int START = 2;
+ public static final int END = 3;
+ public static final int TEXT = 4;
+ public static final int END_DOCUMENT = 3;
+ private static final int NOT_FETCHED = Integer.MIN_VALUE;
+ private static final int NOT_ENDED = Integer.MIN_VALUE;
+ private static final int EOF_BYTE = -1;
+ private boolean debug = false;
+ private boolean capture = false;
+
+ private ArrayList<Integer> captureArray;
+
+ // The input stream for this parser
+ private InputStream in;
+
+ // The current tag depth
+ private int depth;
+
+ // The upcoming (saved) id from the stream
+ private int nextId = NOT_FETCHED;
+
+ // The current tag table (i.e. the tag table for the current page)
+ private String[] tagTable;
+
+ // An array of tag tables, as defined in EasTags
+ static private String[][] tagTables = new String[24][];
+
+ // The stack of names of tags being processed; used when debug = true
+ private String[] nameArray = new String[32];
+
+ // The stack of tags being processed
+ private int[] startTagArray = new int[32];
+
+ // The following vars are available to all to avoid method calls that represent the state of
+ // the parser at any given time
+ public int endTag = NOT_ENDED;
+
+ public int startTag;
+
+ // The type of the last token read
+ public int type;
+
+ // The current page
+ public int page;
+
+ // The current tag
+ public int tag;
+
+ // The name of the current tag
+ public String name;
+
+ // Whether the current tag is associated with content (a value)
+ private boolean noContent;
+
+ // The value read, as a String. Only one of text or num will be valid, depending on whether the
+ // value was requested as a String or an int (to avoid wasted effort in parsing)
+ public String text;
+
+ // The value read, as an int
+ public int num;
+
+ public class EofException extends IOException {
+ private static final long serialVersionUID = 1L;
+ }
+
+ public class EodException extends IOException {
+ private static final long serialVersionUID = 1L;
+ }
+
+ public class EasParserException extends IOException {
+ private static final long serialVersionUID = 1L;
+ }
+
+ public boolean parse() throws IOException, EasException {
+ return false;
+ }
+
+ /**
+ * Initialize the tag tables; they are constant
+ *
+ */
+ {
+ String[][] pages = EasTags.pages;
+ for (int i = 0; i < pages.length; i++) {
+ String[] page = pages[i];
+ if (page.length > 0) {
+ tagTables[i] = page;
+ }
+ }
+ }
+
+ public EasParser(InputStream in) throws IOException {
+ setInput(in);
+ }
+
+ /**
+ * Set the debug state of the parser. When debugging is on, every token is logged (Log.v) to
+ * the console.
+ *
+ * @param val the desired state for debug output
+ */
+ public void setDebug(boolean val) {
+ debug = val;
+ }
+
+ /**
+ * Turns on data capture; this is used to create test streams that represent "live" data and
+ * can be used against the various parsers.
+ */
+ public void captureOn() {
+ capture = true;
+ captureArray = new ArrayList<Integer>();
+ }
+
+ /**
+ * Turns off data capture; writes the captured data to a specified file.
+ */
+ public void captureOff(Context context, String file) {
+ try {
+ FileOutputStream out = context.openFileOutput(file, Context.MODE_WORLD_WRITEABLE);
+ out.write(captureArray.toString().getBytes());
+ out.close();
+ } catch (FileNotFoundException e) {
+ // This is debug code; exceptions aren't interesting.
+ } catch (IOException e) {
+ // This is debug code; exceptions aren't interesting.
+ }
+ }
+
+ /**
+ * Return the value of the current tag, as a String
+ *
+ * @return the String value of the current tag
+ * @throws IOException
+ */
+ public String getValue() throws IOException {
+ // The false argument tells getNext to return the value as a String
+ getNext(false);
+ // Save the value
+ String val = text;
+ // Read the next token; it had better be the end of the current tag
+ getNext(false);
+ // If not, throw an exception
+ if (type != END) {
+ throw new IOException("No END found!");
+ }
+ endTag = startTag;
+ return val;
+ }
+
+ /**
+ * Return the value of the current tag, as an integer
+ *
+ * @return the integer value of the current tag
+ * @throws IOException
+ */
+ public int getValueInt() throws IOException {
+ // The true argument to getNext indicates the desire for an integer return value
+ getNext(true);
+ // Save the value
+ int val = num;
+ // Read the next token; it had better be the end of the current tag
+ getNext(false);
+ // If not, throw an exception
+ if (type != END) {
+ throw new IOException("No END found!");
+ }
+ endTag = startTag;
+ return val;
+ }
+
+ /**
+ * Return the next tag found in the stream; special tags END and END_DOCUMENT are used to
+ * mark the end of the current tag and end of document. If we hit end of document without
+ * looking for it, generate an EodException. The tag returned consists of the page number
+ * shifted PAGE_SHIFT bits OR'd with the tag retrieved from the stream. Thus, all tags returned
+ * are unique.
+ *
+ * @param endingTag the tag that would represent the end of the tag we're processing
+ * @return the next tag found
+ * @throws IOException
+ */
+ public int nextTag(int endingTag) throws IOException {
+ // Lose the page information
+ endTag = endingTag &= EasTags.PAGE_MASK;
+ while (getNext(false) != DONE) {
+ // If we're a start, set tag to include the page and return it
+ if (type == START) {
+ tag = page | startTag;
+ return tag;
+ // If we're at the ending tag we're looking for, return the END signal
+ } else if (type == END && startTag == endTag) {
+ return END;
+ }
+ }
+ // We're at end of document here. If we're looking for it, return END_DOCUMENT
+ if (endTag == START_DOCUMENT) {
+ return END_DOCUMENT;
+ }
+ // Otherwise, we've prematurely hit end of document, so exception out
+ // EodException is a subclass of IOException; this will be treated as an IO error by
+ // SyncManager.
+ throw new EodException();
+ }
+
+ /**
+ * Skip anything found in the stream until the end of the current tag is reached. This can be
+ * used to ignore stretches of xml that aren't needed by the parser.
+ *
+ * @throws IOException
+ */
+ public void skipTag() throws IOException {
+ int thisTag = startTag;
+ // Just loop until we hit the end of the current tag
+ while (getNext(false) != DONE) {
+ if (type == END && startTag == thisTag) {
+ return;
+ }
+ }
+
+ // If we're at end of document, that's bad
+ throw new EofException();
+ }
+
+ /**
+ * Retrieve the next token from the input stream
+ *
+ * @return the token found
+ * @throws IOException
+ */
+ public int nextToken() throws IOException {
+ getNext(false);
+ return type;
+ }
+
+ /**
+ * Initializes the parser with an input stream; reads the first 4 bytes (which are always the
+ * same in EAS, and then sets the tag table to point to page 0 (by definition, the starting
+ * page).
+ *
+ * @param in the InputStream associated with this parser
+ * @throws IOException
+ */
+ public void setInput(InputStream in) throws IOException {
+ this.in = in;
+ readByte(); // version
+ readInt(); // ?
+ readInt(); // 106 (UTF-8)
+ readInt(); // string table length
+ tagTable = tagTables[0];
+ }
+
+ /**
+ * Return the next piece of data from the stream. The return value indicates the type of data
+ * that has been retrieved - START (start of tag), END (end of tag), DONE (end of stream), or
+ * TEXT (the value of a tag)
+ *
+ * @param asInt whether a TEXT value should be parsed as a String or an int.
+ * @return the type of data retrieved
+ * @throws IOException
+ */
+ private final int getNext(boolean asInt) throws IOException {
+ if (type == END) {
+ depth--;
+ } else {
+ endTag = NOT_ENDED;
+ }
+
+ if (noContent) {
+ type = END;
+ noContent = false;
+ return type;
+ }
+
+ text = null;
+ name = null;
+
+ int id = nextId ();
+ while (id == Wbxml.SWITCH_PAGE) {
+ nextId = NOT_FETCHED;
+ // Get the new page number
+ int pg = readByte();
+ // Save the shifted page to add into the startTag in nextTag
+ page = pg << EasTags.PAGE_SHIFT;
+ // Retrieve the current tag table
+ tagTable = tagTables[pg];
+ id = nextId();
+ }
+ nextId = NOT_FETCHED;
+
+ switch (id) {
+ case EOF_BYTE:
+ // End of document
+ type = DONE;
+ break;
+
+ case Wbxml.END:
+ // End of tag
+ type = END;
+ if (debug) {
+ name = nameArray[depth];
+ Log.v(TAG, "</" + name + '>');
+ }
+ // Retrieve the now-current startTag from our stack
+ startTag = endTag = startTagArray[depth];
+ break;
+
+ case Wbxml.STR_I:
+ // Inline string
+ type = TEXT;
+ if (asInt) {
+ num = readInlineInt();
+ } else {
+ text = readInlineString();
+ }
+ if (debug) {
+ Log.v(TAG, asInt ? Integer.toString(num) : text);
+ }
+ break;
+
+ default:
+ // Start of tag
+ type = START;
+ // The tag is in the low 6 bits
+ startTag = id & 0x3F;
+ // If the high bit is set, there is content (a value) to be read
+ noContent = (id & 0x40) == 0;
+ depth++;
+ if (debug) {
+ name = tagTable[startTag - 5];
+ Log.v(TAG, '<' + name + '>');
+ nameArray[depth] = name;
+ }
+ // Save the startTag to our stack
+ startTagArray[depth] = startTag;
+ }
+
+ // Return the type of data we're dealing with
+ return type;
+ }
+
+ /**
+ * Read an int from the input stream, and capture it if necessary for debugging. Seems a small
+ * price to pay...
+ *
+ * @return the int read
+ * @throws IOException
+ */
+ private int read() throws IOException {
+ int i;
+ i = in.read();
+ if (capture) {
+ captureArray.add(i);
+ }
+ return i;
+ }
+
+ private int nextId() throws IOException {
+ if (nextId == NOT_FETCHED) {
+ nextId = read();
+ }
+ return nextId;
+ }
+
+ private int readByte() throws IOException {
+ int i = read();
+ if (i == EOF_BYTE) {
+ throw new EofException();
+ }
+ return i;
+ }
+
+ /**
+ * Read an integer from the stream; this is called when the parser knows that what follows is
+ * an inline string representing an integer (e.g. the Read tag in Email has a value known to
+ * be either "0" or "1")
+ *
+ * @return the integer as parsed from the stream
+ * @throws IOException
+ */
+ private int readInlineInt() throws IOException {
+ int result = 0;
+
+ while (true) {
+ int i = readByte();
+ // Inline strings are always terminated with a zero byte
+ if (i == 0) {
+ return result;
+ }
+ if (i >= '0' && i <= '9') {
+ result = (result * 10) + (i - '0');
+ } else {
+ throw new IOException("Non integer");
+ }
+ }
+ }
+
+ private int readInt() throws IOException {
+ int result = 0;
+ int i;
+
+ do {
+ i = readByte();
+ result = (result << 7) | (i & 0x7f);
+ } while ((i & 0x80) != 0);
+
+ return result;
+ }
+
+ /**
+ * Read an inline string from the stream
+ *
+ * @return the String as parsed from the stream
+ * @throws IOException
+ */
+ private String readInlineString() throws IOException {
+ StringBuilder sb = new StringBuilder(256);
+
+ while (true) {
+ int i = read();
+ if (i == 0) {
+ break;
+ } else if (i == EOF_BYTE) {
+ throw new EofException();
+ }
+ sb.append((char)i);
+ }
+ String res = sb.toString();
+ return res;
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/exchange/adapter/EasPingParser.java b/src/com/android/exchange/adapter/EasPingParser.java
new file mode 100644
index 0000000..d6b0007
--- /dev/null
+++ b/src/com/android/exchange/adapter/EasPingParser.java
@@ -0,0 +1,85 @@
+/* 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.adapter;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+
+import com.android.exchange.EasSyncService;
+import com.android.exchange.StaleFolderListException;
+
+/**
+ * Parse the result of a Ping command.
+ *
+ * If there are folders with changes, add the serverId of those folders to the syncList array.
+ * If the folder list needs to be reloaded, throw a StaleFolderListException, which will be caught
+ * by the sync server, which will sync the updated folder list.
+ */
+public class EasPingParser extends EasParser {
+ ArrayList<String> syncList = new ArrayList<String>();
+
+ EasSyncService mService;
+
+ public ArrayList<String> getSyncList() {
+ return syncList;
+ }
+
+ public EasPingParser(InputStream in, EasSyncService _service) throws IOException {
+ super(in);
+ mService = _service;
+ //setDebug(true);
+ }
+
+ public void parsePingFolders(ArrayList<String> syncList) throws IOException {
+ while (nextTag(EasTags.PING_FOLDERS) != END) {
+ if (tag == EasTags.PING_FOLDER) {
+ // Here we'll keep track of which mailboxes need syncing
+ syncList.add(getValue());
+ } else {
+ skipTag();
+ }
+ }
+ }
+
+ @Override
+ public boolean parse() throws IOException, StaleFolderListException {
+ boolean res = false;
+ if (nextTag(START_DOCUMENT) != EasTags.PING_PING) {
+ throw new IOException();
+ }
+ while (nextTag(START_DOCUMENT) != END_DOCUMENT) {
+ if (tag == EasTags.PING_STATUS) {
+ int status = getValueInt();
+ mService.userLog("Ping completed, status = " + status);
+ if (status == 2) {
+ // Status = 2 indicates changes in one folder or other
+ res = true;
+ } else if (status == 7 || status == 4) {
+ // Status of 7 or 4 indicate a stale folder list
+ throw new StaleFolderListException();
+ }
+ } else if (tag == EasTags.PING_FOLDERS) {
+ parsePingFolders(syncList);
+ } else {
+ skipTag();
+ }
+ }
+ return res;
+ }
+}
+
diff --git a/src/com/android/exchange/adapter/EasSerializer.java b/src/com/android/exchange/adapter/EasSerializer.java
new file mode 100644
index 0000000..8895e93
--- /dev/null
+++ b/src/com/android/exchange/adapter/EasSerializer.java
@@ -0,0 +1,103 @@
+/*
+ * 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.adapter;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.Hashtable;
+
+
+/**
+ * This is a convenience class that simplifies the creation of Wbxml commands and allows
+ * multiple commands to be chained together.
+ *
+ * Each start command must pair with an end command; the values of all data fields are Strings. The
+ * methods here should be self-explanatory.
+ *
+ * Use toString() to obtain the output for the EAS server
+ */
+public class EasSerializer extends WbxmlSerializer {
+ ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
+
+ static Hashtable<String, Object> tagTable = null;
+
+ public EasSerializer() {
+ super();
+ try {
+ setOutput(byteStream, null);
+ // Lazy initialization of our tag tables, as created from EasTags
+ if (tagTable == null) {
+ String[][] pages = EasTags.pages;
+ for (int i = 0; i < pages.length; i++) {
+ String[] page = pages[i];
+ if (page.length > 0) {
+ setTagTable(i, page);
+ }
+ }
+ tagTable = getTagTable();
+ } else {
+ setTagTable(tagTable);
+ }
+ startDocument("UTF-8", false);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public EasSerializer start(String tag) throws IOException {
+ startTag(null, tag);
+ return this;
+ }
+
+ public EasSerializer end(String tag) throws IOException {
+ endTag(null, tag);
+ return this;
+ }
+
+ public EasSerializer end() throws IOException {
+ endDocument();
+ return this;
+ }
+
+ public EasSerializer data(String tag, String value) throws IOException {
+ startTag(null, tag);
+ text(value);
+ endTag(null, tag);
+ return this;
+ }
+
+ public EasSerializer tag(String tag) throws IOException {
+ startTag(null, tag);
+ endTag(null, tag);
+ return this;
+ }
+
+ public EasSerializer text(String str) throws IOException {
+ super.text(str);
+ return this;
+ }
+
+ public ByteArrayOutputStream getByteStream() {
+ return byteStream;
+ }
+
+ public String toString() {
+ return byteStream.toString();
+ }
+}
+
diff --git a/src/com/android/exchange/adapter/EasSyncAdapter.java b/src/com/android/exchange/adapter/EasSyncAdapter.java
new file mode 100644
index 0000000..136c54b
--- /dev/null
+++ b/src/com/android/exchange/adapter/EasSyncAdapter.java
@@ -0,0 +1,47 @@
+/*
+ * 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.adapter;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+
+import com.android.exchange.EasSyncService;
+import com.android.exchange.EmailContent.Mailbox;
+
+/**
+ * Parent class of all sync adapters (EasMailbox, EasCalendar, and EasContacts)
+ *
+ */
+public abstract class EasSyncAdapter {
+ public Mailbox mMailbox;
+
+ // Create the data for local changes that need to be sent up to the server
+ public abstract boolean sendLocalChanges(EasSerializer s, EasSyncService service)
+ throws IOException;
+ // Parse incoming data from the EAS server, creating, modifying, and deleting objects as
+ // required through the EmailProvider
+ public abstract boolean parse(ByteArrayInputStream is, EasSyncService service)
+ throws IOException;
+ // The name used to specify the collection type of the target (Email, Calendar, or Contacts)
+ public abstract String getCollectionName();
+
+ public EasSyncAdapter(Mailbox mailbox) {
+ mMailbox = mailbox;
+ }
+}
+
diff --git a/src/com/android/exchange/adapter/EasTags.java b/src/com/android/exchange/adapter/EasTags.java
new file mode 100644
index 0000000..4a832e1
--- /dev/null
+++ b/src/com/android/exchange/adapter/EasTags.java
@@ -0,0 +1,459 @@
+/*
+ * 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.adapter;
+
+/**
+ * The wbxml tags for EAS are all defined here.
+ *
+ * The static final int's, of the form <page>_<tag> = <constant> are used in parsing incoming
+ * responses from the server (i.e. EasParser and its subclasses).
+ *
+ * The array of String arrays is used to construct server requests with EasSerializer. One thing
+ * we might do eventually is to "precompile" these requests, in part, although they should be
+ * fairly fast to begin with (each tag requires one HashMap lookup, and there aren't all that many
+ * of them in a given command).
+ *
+ */
+public class EasTags {
+
+ // Wbxml page definitions for EAS
+ static final int AIRSYNC = 0x00;
+ static final int CONTACTS = 0x01;
+ static final int EMAIL = 0x02;
+ static final int CALENDAR = 0x04;
+ static final int MOVE = 0x05;
+ static final int FOLDER = 0x07;
+ static final int CONTACTS2 = 0x0C;
+ static final int PING = 0x0D;
+ static final int GAL = 0x10;
+ static final int BASE = 0x11;
+
+ // Shift applied to page numbers to generate tag
+ static final int PAGE_SHIFT = 6;
+ static final int PAGE_MASK = 0x3F; // 6 bits
+
+ static final int SYNC_PAGE = 0 << PAGE_SHIFT;
+ static final int SYNC_SYNC = SYNC_PAGE + 5;
+ static final int SYNC_RESPONSES = SYNC_PAGE + 6;
+ static final int SYNC_ADD = SYNC_PAGE + 7;
+ static final int SYNC_CHANGE = SYNC_PAGE + 8;
+ static final int SYNC_DELETE = SYNC_PAGE + 9;
+ static final int SYNC_FETCH = SYNC_PAGE + 0xA;
+ static final int SYNC_SYNC_KEY = SYNC_PAGE + 0xB;
+ static final int SYNC_CLIENT_ID = SYNC_PAGE + 0xC;
+ static final int SYNC_SERVER_ID = SYNC_PAGE + 0xD;
+ static final int SYNC_STATUS = SYNC_PAGE + 0xE;
+ static final int SYNC_COLLECTION = SYNC_PAGE + 0xF;
+ static final int SYNC_CLASS = SYNC_PAGE + 0x10;
+ static final int SYNC_VERSION = SYNC_PAGE + 0x11;
+ static final int SYNC_COLLECTION_ID = SYNC_PAGE + 0x12;
+ static final int SYNC_GET_CHANGES = SYNC_PAGE + 0x13;
+ static final int SYNC_MORE_AVAILABLE = SYNC_PAGE + 0x14;
+ static final int SYNC_WINDOW_SIZE = SYNC_PAGE + 0x15;
+ static final int SYNC_COMMANDS = SYNC_PAGE + 0x16;
+ static final int SYNC_OPTIONS = SYNC_PAGE + 0x17;
+ static final int SYNC_FILTER_TYPE = SYNC_PAGE + 0x18;
+ static final int SYNC_TRUNCATION = SYNC_PAGE + 0x19;
+ static final int SYNC_RTF_TRUNCATION = SYNC_PAGE + 0x1A;
+ static final int SYNC_CONFLICT = SYNC_PAGE + 0x1B;
+ static final int SYNC_COLLECTIONS = SYNC_PAGE + 0x1C;
+ static final int SYNC_APPLICATION_DATA = SYNC_PAGE + 0x1D;
+ static final int SYNC_DELETES_AS_MOVES = SYNC_PAGE + 0x1E;
+ static final int SYNC_NOTIFY_GUID = SYNC_PAGE + 0x1F;
+ static final int SYNC_SUPPORTED = SYNC_PAGE + 0x20;
+ static final int SYNC_SOFT_DELETE = SYNC_PAGE + 0x21;
+ static final int SYNC_MIME_SUPPORT = SYNC_PAGE + 0x22;
+ static final int SYNC_MIME_TRUNCATION = SYNC_PAGE + 0x23;
+ static final int SYNC_WAIT = SYNC_PAGE + 0x24;
+ static final int SYNC_LIMIT = SYNC_PAGE + 0x25;
+ static final int SYNC_PARTIAL = SYNC_PAGE + 0x26;
+
+ static final int CONTACTS_PAGE = CONTACTS << PAGE_SHIFT;
+ static final int CONTACTS_ANNIVERSARY = CONTACTS_PAGE + 5;
+ static final int CONTACTS_ASSISTANT_NAME = CONTACTS_PAGE + 6;
+ static final int CONTACTS_ASSISTANT_TELEPHONE_NUMBER = CONTACTS_PAGE + 7;
+ static final int CONTACTS_BIRTHDAY = CONTACTS_PAGE + 8;
+ static final int CONTACTS_BODY = CONTACTS_PAGE + 9;
+ static final int CONTACTS_BODY_SIZE = CONTACTS_PAGE + 0xA;
+ static final int CONTACTS_BODY_TRUNCATED = CONTACTS_PAGE + 0xB;
+ static final int CONTACTS_BUSINESS2_TELEPHONE_NUMBER = CONTACTS_PAGE + 0xC;
+ static final int CONTACTS_BUSINESS_ADDRESS_CITY = CONTACTS_PAGE + 0xD;
+ static final int CONTACTS_BUSINESS_ADDRESS_COUNTRY = CONTACTS_PAGE + 0xE;
+ static final int CONTACTS_BUSINESS_ADDRESS_POSTAL_CODE = CONTACTS_PAGE + 0xF;
+ static final int CONTACTS_BUSINESS_ADDRESS_STATE = CONTACTS_PAGE + 0x10;
+ static final int CONTACTS_BUSINESS_ADDRESS_STREET = CONTACTS_PAGE + 0x11;
+ static final int CONTACTS_BUSINESS_FAX_NUMBER = CONTACTS_PAGE + 0x12;
+ static final int CONTACTS_BUSINESS_TELEPHONE_NUMBER = CONTACTS_PAGE + 0x13;
+ static final int CONTACTS_CAR_TELEPHONE_NUMBER = CONTACTS_PAGE + 0x14;
+ static final int CONTACTS_CATEGORIES = CONTACTS_PAGE + 0x15;
+ static final int CONTACTS_CATEGORY = CONTACTS_PAGE + 0x16;
+ static final int CONTACTS_CHILDREN = CONTACTS_PAGE + 0x17;
+ static final int CONTACTS_CHILD = CONTACTS_PAGE + 0x18;
+ static final int CONTACTS_COMPANY_NAME = CONTACTS_PAGE + 0x19;
+ static final int CONTACTS_DEPARTMENT = 0x1A;
+ static final int CONTACTS_EMAIL1_ADDRESS = CONTACTS_PAGE + 0x1B;
+ static final int CONTACTS_EMAIL2_ADDRESS = CONTACTS_PAGE + 0x1C;
+ static final int CONTACTS_EMAIL3_ADDRESS = CONTACTS_PAGE + 0x1D;
+ static final int CONTACTS_FILE_AS = CONTACTS_PAGE + 0x1E;
+ static final int CONTACTS_FIRST_NAME = CONTACTS_PAGE + 0x1F;
+ static final int CONTACTS_HOME2_TELEPHONE_NUMBER = CONTACTS_PAGE + 0x20;
+ static final int CONTACTS_HOME_ADDRESS_CITY = CONTACTS_PAGE + 0x21;
+ static final int CONTACTS_HOME_ADDRESS_COUNTRY = CONTACTS_PAGE + 0x22;
+ static final int CONTACTS_HOME_ADDRESS_POSTAL_CODE = CONTACTS_PAGE + 0x23;
+ static final int CONTACTS_HOME_ADDRESS_STATE = CONTACTS_PAGE + 0x24;
+ static final int CONTACTS_HOME_ADDRESS_STREET = CONTACTS_PAGE + 0x25;
+ static final int CONTACTS_HOME_FAX_NUMBER = CONTACTS_PAGE + 0x26;
+ static final int CONTACTS_HOME_TELEPHONE_NUMBER = CONTACTS_PAGE + 0x27;
+ static final int CONTACTS_JOB_TITLE = CONTACTS_PAGE + 0x28;
+ static final int CONTACTS_LAST_NAME = CONTACTS_PAGE + 0x29;
+ static final int CONTACTS_MIDDLE_NAME = CONTACTS_PAGE + 0x2A;
+ static final int CONTACTS_MOBILE_TELEPHONE_NUMBER = CONTACTS_PAGE + 0x2B;
+ static final int CONTACTS_OFFICE_LOCATION = CONTACTS_PAGE + 0x2C;
+ static final int CONTACTS_OTHER_ADDRESS_CITY = CONTACTS_PAGE + 0x2D;
+ static final int CONTACTS_OTHER_ADDRESS_COUNTRY = CONTACTS_PAGE + 0x2E;
+ static final int CONTACTS_OTHER_ADDRESS_POSTAL_CODE = CONTACTS_PAGE + 0x2F;
+ static final int CONTACTS_OTHER_ADDRESS_STATE = CONTACTS_PAGE + 0x30;
+ static final int CONTACTS_OTHER_ADDRESS_STREET = CONTACTS_PAGE + 0x31;
+ static final int CONTACTS_PAGER_NUMBER = CONTACTS_PAGE + 0x32;
+ static final int CONTACTS_RADIO_TELEPHONE_NUMBER = CONTACTS_PAGE + 0x33;
+ static final int CONTACTS_SPOUSE = CONTACTS_PAGE + 0x34;
+ static final int CONTACTS_SUFFIX = CONTACTS_PAGE + 0x35;
+ static final int CONTACTS_TITLE = CONTACTS_PAGE + 0x36;
+ static final int CONTACTS_WEBPAGE = CONTACTS_PAGE + 0x37;
+ static final int CONTACTS_YOMI_COMPANY_NAME = CONTACTS_PAGE + 0x38;
+ static final int CONTACTS_YOMI_FIRST_NAME = CONTACTS_PAGE + 0x39;
+ static final int CONTACTS_YOMI_LAST_NAME = CONTACTS_PAGE + 0x3A;
+ static final int CONTACTS_COMPRESSED_RTF = CONTACTS_PAGE + 0x3B;
+ static final int CONTACTS_PICTURE = CONTACTS_PAGE + 0x3C;
+
+ static final int CALENDAR_PAGE = CALENDAR << PAGE_SHIFT;
+ static final int CALENDAR_TIME_ZONE = CALENDAR_PAGE + 5;
+ static final int CALENDAR_ALL_DAY_EVENT = CALENDAR_PAGE + 6;
+ static final int CALENDAR_ATTENDEES = CALENDAR_PAGE + 7;
+ static final int CALENDAR_ATTENDEE = CALENDAR_PAGE + 8;
+ static final int CALENDAR_ATTENDEE_EMAIL = CALENDAR_PAGE + 9;
+ static final int CALENDAR_ATTENDEE_NAME = CALENDAR_PAGE + 0xA;
+ static final int CALENDAR_BODY = CALENDAR_PAGE + 0xB;
+ static final int CALENDAR_BODY_TRUNCATED = CALENDAR_PAGE + 0xC;
+ static final int CALENDAR_BUSY_STATUS = CALENDAR_PAGE + 0xD;
+ static final int CALENDAR_CATEGORIES = CALENDAR_PAGE + 0xE;
+ static final int CALENDAR_CATEGORY = CALENDAR_PAGE + 0xF;
+ static final int CALENDAR_COMPRESSED_RTF = CALENDAR_PAGE + 0x10;
+ static final int CALENDAR_DTSTAMP = CALENDAR_PAGE + 0x11;
+ static final int CALENDAR_END_TIME = CALENDAR_PAGE + 0x12;
+ static final int CALENDAR_EXCEPTION = CALENDAR_PAGE + 0x13;
+ static final int CALENDAR_EXCEPTIONS = CALENDAR_PAGE + 0x14;
+ static final int CALENDAR_EXCEPTION_IS_DELETED = CALENDAR_PAGE + 0x15;
+ static final int CALENDAR_EXCEPTION_START_TIME = CALENDAR_PAGE + 0x16;
+ static final int CALENDAR_LOCATION = CALENDAR_PAGE + 0x17;
+ static final int CALENDAR_MEETING_STATUS = CALENDAR_PAGE + 0x18;
+ static final int CALENDAR_ORGANIZER_EMAIL = CALENDAR_PAGE + 0x19;
+ static final int CALENDAR_ORGANIZER_NAME = CALENDAR_PAGE + 0x1A;
+ static final int CALENDAR_RECURRENCE = CALENDAR_PAGE + 0x1B;
+ static final int CALENDAR_RECURRENCE_TYPE = CALENDAR_PAGE + 0x1C;
+ static final int CALENDAR_RECURRENCE_UNTIL = CALENDAR_PAGE + 0x1D;
+ static final int CALENDAR_RECURRENCE_OCCURRENCES = CALENDAR_PAGE + 0x1E;
+ static final int CALENDAR_RECURRENCE_INTERVAL = CALENDAR_PAGE + 0x1F;
+ static final int CALENDAR_RECURRENCE_DAYOFWEEK = CALENDAR_PAGE + 0x20;
+ static final int CALENDAR_RECURRENCE_DAYOFMONTH = CALENDAR_PAGE + 0x21;
+ static final int CALENDAR_RECURRENCE_WEEKOFMONTH = CALENDAR_PAGE + 0x22;
+ static final int CALENDAR_RECURRENCE_MONTHOFYEAR = CALENDAR_PAGE + 0x23;
+ static final int CALENDAR_REMINDER_MINS_BEFORE = CALENDAR_PAGE + 0x24;
+ static final int CALENDAR_SENSITIVITY = CALENDAR_PAGE + 0x25;
+ static final int CALENDAR_SUBJECT = CALENDAR_PAGE + 0x26;
+ static final int CALENDAR_START_TIME = CALENDAR_PAGE + 0x27;
+ static final int CALENDAR_UID = CALENDAR_PAGE + 0x28;
+ static final int CALENDAR_ATTENDEE_STATUS = CALENDAR_PAGE + 0x29;
+ static final int CALENDAR_ATTENDEE_TYPE = CALENDAR_PAGE + 0x2A;
+
+ static final int FOLDER_PAGE = FOLDER << PAGE_SHIFT;
+ static final int FOLDER_FOLDERS = FOLDER_PAGE + 5;
+ static final int FOLDER_FOLDER = FOLDER_PAGE + 6;
+ static final int FOLDER_DISPLAY_NAME = FOLDER_PAGE + 7;
+ static final int FOLDER_SERVER_ID = FOLDER_PAGE + 8;
+ static final int FOLDER_PARENT_ID = FOLDER_PAGE + 9;
+ static final int FOLDER_TYPE = FOLDER_PAGE + 0xA;
+ static final int FOLDER_RESPONSE = FOLDER_PAGE + 0xB;
+ static final int FOLDER_STATUS = FOLDER_PAGE + 0xC;
+ static final int FOLDER_CONTENT_CLASS = FOLDER_PAGE + 0xD;
+ static final int FOLDER_CHANGES = FOLDER_PAGE + 0xE;
+ static final int FOLDER_ADD = FOLDER_PAGE + 0xF;
+ static final int FOLDER_DELETE = FOLDER_PAGE + 0x10;
+ static final int FOLDER_UPDATE = FOLDER_PAGE + 0x11;
+ static final int FOLDER_SYNC_KEY = FOLDER_PAGE + 0x12;
+ static final int FOLDER_FOLDER_CREATE = FOLDER_PAGE + 0x13;
+ static final int FOLDER_FOLDER_DELETE= FOLDER_PAGE + 0x14;
+ static final int FOLDER_FOLDER_UPDATE = FOLDER_PAGE + 0x15;
+ static final int FOLDER_FOLDER_SYNC = FOLDER_PAGE + 0x16;
+ static final int FOLDER_COUNT = FOLDER_PAGE + 0x17;
+ static final int FOLDER_VERSION = FOLDER_PAGE + 0x18;
+
+ static final int EMAIL_PAGE = EMAIL << PAGE_SHIFT;
+ static final int EMAIL_ATTACHMENT = EMAIL_PAGE + 5;
+ static final int EMAIL_ATTACHMENTS = EMAIL_PAGE + 6;
+ static final int EMAIL_ATT_NAME = EMAIL_PAGE + 7;
+ static final int EMAIL_ATT_SIZE = EMAIL_PAGE + 8;
+ static final int EMAIL_ATT0ID = EMAIL_PAGE + 9;
+ static final int EMAIL_ATT_METHOD = EMAIL_PAGE + 0xA;
+ static final int EMAIL_ATT_REMOVED = EMAIL_PAGE + 0xB;
+ static final int EMAIL_BODY = EMAIL_PAGE + 0xC;
+ static final int EMAIL_BODY_SIZE = EMAIL_PAGE + 0xD;
+ static final int EMAIL_BODY_TRUNCATED = EMAIL_PAGE + 0xE;
+ static final int EMAIL_DATE_RECEIVED = EMAIL_PAGE + 0xF;
+ static final int EMAIL_DISPLAY_NAME = EMAIL_PAGE + 0x10;
+ static final int EMAIL_DISPLAY_TO = EMAIL_PAGE + 0x11;
+ static final int EMAIL_IMPORTANCE = EMAIL_PAGE + 0x12;
+ static final int EMAIL_MESSAGE_CLASS = EMAIL_PAGE + 0x13;
+ static final int EMAIL_SUBJECT = EMAIL_PAGE + 0x14;
+ static final int EMAIL_READ = EMAIL_PAGE + 0x15;
+ static final int EMAIL_TO = EMAIL_PAGE + 0x16;
+ static final int EMAIL_CC = EMAIL_PAGE + 0x17;
+ static final int EMAIL_FROM = EMAIL_PAGE + 0x18;
+ static final int EMAIL_REPLY_TO = EMAIL_PAGE + 0x19;
+ static final int EMAIL_ALL_DAY_EVENT = EMAIL_PAGE + 0x1A;
+ static final int EMAIL_CATEGORIES = EMAIL_PAGE + 0x1B;
+ static final int EMAIL_CATEGORY = EMAIL_PAGE + 0x1C;
+ static final int EMAIL_DTSTAMP = EMAIL_PAGE + 0x1D;
+ static final int EMAIL_END_TIME = EMAIL_PAGE + 0x1E;
+ static final int EMAIL_INSTANCE_TYPE = EMAIL_PAGE + 0x1F;
+ static final int EMAIL_INTD_BUSY_STATUS = EMAIL_PAGE + 0x20;
+ static final int EMAIL_LOCATION = EMAIL_PAGE + 0x21;
+ static final int EMAIL_MEETING_REQUEST = EMAIL_PAGE + 0x22;
+ static final int EMAIL_ORGANIZER = EMAIL_PAGE + 0x23;
+ static final int EMAIL_RECURRENCE_ID = EMAIL_PAGE + 0x24;
+ static final int EMAIL_REMINDER = EMAIL_PAGE + 0x25;
+ static final int EMAIL_RESPONSE_REQUESTED = EMAIL_PAGE + 0x26;
+ static final int EMAIL_RECURRENCES = EMAIL_PAGE + 0x27;
+ static final int EMAIL_RECURRENCE = EMAIL_PAGE + 0x28;
+ static final int EMAIL_RECURRENCE_TYPE = EMAIL_PAGE + 0x29;
+ static final int EMAIL_RECURRENCE_UNTIL = EMAIL_PAGE + 0x2A;
+ static final int EMAIL_RECURRENCE_OCCURRENCES = EMAIL_PAGE + 0x2B;
+ static final int EMAIL_RECURRENCE_INTERVAL = EMAIL_PAGE + 0x2C;
+ static final int EMAIL_RECURRENCE_DAYOFWEEK = EMAIL_PAGE + 0x2D;
+ static final int EMAIL_RECURRENCE_DAYOFMONTH = EMAIL_PAGE + 0x2E;
+ static final int EMAIL_RECURRENCE_WEEKOFMONTH = EMAIL_PAGE + 0x2F;
+ static final int EMAIL_RECURRENCE_MONTHOFYEAR = EMAIL_PAGE + 0x30;
+ static final int EMAIL_START_TIME = EMAIL_PAGE + 0x31;
+ static final int EMAIL_SENSITIVITY = EMAIL_PAGE + 0x32;
+ static final int EMAIL_TIME_ZONE = EMAIL_PAGE + 0x33;
+ static final int EMAIL_GLOBAL_OBJID = EMAIL_PAGE + 0x34;
+ static final int EMAIL_THREAD_TOPIC = EMAIL_PAGE + 0x35;
+ static final int EMAIL_MIME_DATA = EMAIL_PAGE + 0x36;
+ static final int EMAIL_MIME_TRUNCATED = EMAIL_PAGE + 0x37;
+ static final int EMAIL_MIME_SIZE = EMAIL_PAGE + 0x38;
+ static final int EMAIL_INTERNET_CPID = EMAIL_PAGE + 0x39;
+ static final int EMAIL_FLAG = EMAIL_PAGE + 0x3A;
+ static final int EMAIL_FLAG_STATUS = EMAIL_PAGE + 0x3B;
+ static final int EMAIL_CONTENT_CLASS = EMAIL_PAGE + 0x3C;
+ static final int EMAIL_FLAG_TYPE = EMAIL_PAGE + 0x3D;
+ static final int EMAIL_COMPLETE_TIME = EMAIL_PAGE + 0x3E;
+
+ static final int MOVE_PAGE = MOVE << PAGE_SHIFT;
+ static final int MOVE_MOVE_ITEMS = MOVE_PAGE + 5;
+ static final int MOVE_MOVE = MOVE_PAGE + 6;
+ static final int MOVE_SRCMSGID = MOVE_PAGE + 7;
+ static final int MOVE_SRCFLDID = MOVE_PAGE + 8;
+ static final int MOVE_DSTFLDID = MOVE_PAGE + 9;
+ static final int MOVE_RESPONSE = MOVE_PAGE + 0xA;
+ static final int MOVE_STATUS = MOVE_PAGE + 0xB;
+ static final int MOVE_DSTMSGID = MOVE_PAGE + 0xC;
+
+ static final int CONTACTS2_PAGE = CONTACTS2 << PAGE_SHIFT;
+ static final int CONTACTS2_CUSTOMER_ID = CONTACTS2_PAGE + 5;
+ static final int CONTACTS2_GOVERNMENT_ID = CONTACTS2_PAGE + 6;
+ static final int CONTACTS2_IM_ADDRESS = CONTACTS2_PAGE + 7;
+ static final int CONTACTS2_IM_ADDRESS_2 = CONTACTS2_PAGE + 8;
+ static final int CONTACTS2_IM_ADDRESS_3 = CONTACTS2_PAGE + 9;
+ static final int CONTACTS2_MANAGER_NAME = CONTACTS2_PAGE + 0xA;
+ static final int CONTACTS2_COMPANY_MAIN_PHONE = CONTACTS2_PAGE + 0xB;
+ static final int CONTACTS2_ACCOUNT_NAME = CONTACTS2_PAGE + 0xC;
+ static final int CONTACTS2_NICKNAME = CONTACTS2_PAGE + 0xD;
+ static final int CONTACTS2_MMS = CONTACTS2_PAGE + 0xE;
+
+ // The Ping constants are used by EasSyncService, and need to be public
+ static final int PING_PAGE = PING << PAGE_SHIFT;
+ public static final int PING_PING = PING_PAGE + 5;
+ public static final int PING_AUTD_STATE = PING_PAGE + 6;
+ public static final int PING_STATUS = PING_PAGE + 7;
+ public static final int PING_HEARTBEAT_INTERVAL = PING_PAGE + 8;
+ public static final int PING_FOLDERS = PING_PAGE + 9;
+ public static final int PING_FOLDER = PING_PAGE + 0xA;
+ public static final int PING_ID = PING_PAGE + 0xB;
+ public static final int PING_CLASS = PING_PAGE + 0xC;
+ public static final int PING_MAX_FOLDERS = PING_PAGE + 0xD;
+
+ static final int BASE_PAGE = BASE << PAGE_SHIFT;
+ static final int BASE_BODY_PREFERENCE = BASE_PAGE + 5;
+ static final int BASE_TYPE = BASE_PAGE + 6;
+ static final int BASE_TRUNCATION_SIZE = BASE_PAGE + 7;
+ static final int BASE_ALL_OR_NONE = BASE_PAGE + 8;
+ static final int BASE_RESERVED = BASE_PAGE + 9;
+ static final int BASE_BODY = BASE_PAGE + 0xA;
+ static final int BASE_DATA = BASE_PAGE + 0xB;
+ static final int BASE_ESTIMATED_DATA_SIZE = BASE_PAGE + 0xC;
+ static final int BASE_TRUNCATED = BASE_PAGE + 0xD;
+ static final int BASE_ATTACHMENTS = BASE_PAGE + 0xE;
+ static final int BASE_ATTACHMENT = BASE_PAGE + 0xF;
+ static final int BASE_DISPLAY_NAME = BASE_PAGE + 0x10;
+ static final int BASE_FILE_REFERENCE = BASE_PAGE + 0x11;
+ static final int BASE_METHOD = BASE_PAGE + 0x12;
+ static final int BASE_CONTENT_ID = BASE_PAGE + 0x13;
+ static final int BASE_CONTENT_LOCATION = BASE_PAGE + 0x14;
+ static final int BASE_IS_INLINE = BASE_PAGE + 0x15;
+ static final int BASE_NATIVE_BODY_TYPE = BASE_PAGE + 0x16;
+ static final int BASE_CONTENT_TYPE = BASE_PAGE + 0x17;
+
+ static public String[][] pages = {
+ { // 0x00 AirSync
+ "Sync", "Responses", "Add", "Change", "Delete", "Fetch", "SyncKey", "ClientId",
+ "ServerId", "Status", "Collection", "Class", "Version", "CollectionId", "GetChanges",
+ "MoreAvailable", "WindowSize", "Commands", "Options", "FilterType", "Truncation",
+ "RTFTruncation", "Conflict", "Collections", "ApplicationData", "DeletesAsMoves",
+ "NotifyGUID", "Supported", "SoftDelete", "MIMESupport", "MIMETruncation", "Wait",
+ "Limit", "Partial"
+ },
+ {
+ // 0x01 Contacts
+ "Anniversary", "AssistantName", "AssistantTelephoneNumber", "Birthday", "Body",
+ "BodySize", "BodyTruncated", "Business2TelephoneNumber", "BusinessAddressCity",
+ "BusinessAddressCountry", "BusinessAddressPostalCode", "BusinessAddressState",
+ "BusinessAddressStreet", "BusinessFaxNumber", "BusinessTelephoneNumber",
+ "CarTelephoneNumber", "ContactsCategories", "ContactsCategory", "Children", "Child",
+ "CompanyName", "Department", "Email1Address", "Email2Address", "Email3Address",
+ "FileAs", "FirstName", "Home2TelephoneNumber", "HomeAddressCity", "HomeAddressCountry",
+ "HomeAddressPostalCode", "HomeAddressState", "HomeAddressStreet", "HomeFaxNumber",
+ "HomeTelephoneNumber", "JobTitle", "LastName", "MiddleName", "MobileTelephoneNumber",
+ "OfficeLocation", "OfficeAddressCity", "OfficeAddressCountry",
+ "OfficeAddressPostalCode", "OfficeAddressState", "OfficeAddressStreet", "PagerNumber",
+ "RadioTelephoneNumber", "Spouse", "Suffix", "Title", "Webpage", "YomiCompanyName",
+ "YomiFirstName", "YomiLastName", "CompressedRTF", "Picture"
+ },
+ {
+ // 0x02 Email
+ "Attachment", "Attachments", "AttName", "AttSize", "Add0Id", "AttMethod", "AttRemoved",
+ "Body", "BodySize", "BodyTruncated", "DateReceived", "DisplayName", "DisplayTo",
+ "Importance", "MessageClass", "Subject", "Read", "To", "CC", "From", "ReplyTo",
+ "AllDayEvent", "Categories", "Category", "DTStamp", "EndTime", "InstanceType",
+ "IntDBusyStatus", "Location", "MeetingRequest", "Organizer", "RecurrenceId", "Reminder",
+ "ResponseRequested", "Recurrences", "Recurence", "Recurrence_Type", "Recurrence_Until",
+ "Recurrence_Occurrences", "Recurrence_Interval", "Recurrence_DayOfWeek",
+ "Recurrence_DayOfMonth", "Recurrence_WeekOfMonth", "Recurrence_MonthOfYear",
+ "StartTime", "Sensitivity", "TimeZone", "GlobalObjId", "ThreadTopic", "MIMEData",
+ "MIMETruncated", "MIMESize", "InternetCPID", "Flag", "FlagStatus", "ContentClass",
+ "FlagType", "CompleteTime"
+ },
+ {
+ // 0x03 AirNotify
+ },
+ {
+ // 0x04 Calendar
+ "CalTimeZone", "CalAllDayEvent", "CalAttendees", "CalAttendee", "CalAttendee_Email",
+ "CalAttendee_Name", "CalBody", "CalBodyTruncated", "CalBusyStatus", "CalCategories",
+ "CalCategory", "CalCompressed_RTF", "CalDTStamp", "CalEndTime", "CalExeption",
+ "CalExceptions", "CalException_IsDeleted", "CalException_StartTime", "CalLocation",
+ "CalMeetingStatus", "CalOrganizer_Email", "CalOrganizer_Name", "CalRecurrence",
+ "CalRecurrence_Type", "CalRecurrence_Until", "CalRecurrence_Occurrences",
+ "CalRecurrence_Interval", "CalRecurrence_DayOfWeek", "CalRecurrence_DayOfMonth",
+ "CalRecurrence_WeekOfMonth", "CalRecurrence_MonthOfYear", "CalReminder_MinsBefore",
+ "CalSensitivity", "CalSubject", "CalStartTime", "CalUID", "CalAttendee_Status",
+ "CalAttendee_Type"
+ },
+ {
+ // 0x05 Move
+ "MoveItems", "Move", "SrcMsgId", "SrcFldId", "DstFldId", "Response", "Status",
+ "DstMsgId"
+ },
+ {
+ // 0x06 ItemEstimate
+ },
+ {
+ // 0x07 FolderHierarchy
+ "Folders", "Folder", "FolderDisplayName", "FolderServerId", "FolderParentId", "Type",
+ "Response", "Status", "ContentClass", "Changes", "FolderAdd", "FolderDelete",
+ "FolderUpdate", "FolderSyncKey", "FolderCreate", "FolderDelete", "FolderUpdate",
+ "FolderSync", "Count", "Version"
+ },
+ {
+ // 0x08 MeetingResponse
+ },
+ {
+ // 0x09 Tasks
+ },
+ {
+ // 0x0A ResolveRecipients
+ },
+ {
+ // 0x0B ValidateCert
+ },
+ {
+ // 0x0C Contacts2
+ "CustomerId", "GovernmentId", "IMAddress", "IMAddress2", "IMAddress3", "ManagerName",
+ "CompanyMainPhone", "AccountName", "NickName", "MMS"
+ },
+ {
+ // 0x0D Ping
+ "Ping", "AutdState", "Status", "HeartbeatInterval", "PingFolders", "PingFolder",
+ "PingId", "PingClass", "MaxFolders"
+ },
+ {
+ // 0x0E Provision
+ "Provision", "Policies", "Policy", "PolicyType", "PolicyKey", "Data", "Status",
+ "RemoteWipe", "EASProvidionDoc", "DevicePasswordEnabled",
+ "AlphanumericDevicePasswordRequired",
+ "DeviceEncryptionEnabled", "-unused-", "AttachmentsEnabled", "MinDevicePasswordLength",
+ "MaxInactivityTimeDeviceLock", "MaxDevicePasswordFailedAttempts", "MaxAttachmentSize",
+ "AllowSimpleDevicePassword", "DevicePasswordExpiration", "DevicePasswordHistory",
+ "AllowStorageCard", "AllowCamera", "RequireDeviceEncryption",
+ "AllowUnsignedApplications", "AllowUnsignedInstallationPackages",
+ "MinDevicePasswordComplexCharacters", "AllowWiFi", "AllowTextMessaging",
+ "AllowPOPIMAPEmail", "AllowBluetooth", "AllowIrDA", "RequireManualSyncWhenRoaming",
+ "AllowDesktopSync",
+ "MaxCalendarAgeFilder", "AllowHTMLEmail", "MaxEmailAgeFilder",
+ "MaxEmailBodyTruncationSize", "MaxEmailHTMLBodyTruncationSize",
+ "RequireSignedSMIMEMessages", "RequireEncryptedSMIMEMessages",
+ "RequireSignedSMIMEAlgorithm", "RequireEncryptionSMIMEAlgorithm",
+ "AllowSMIMEEncryptionAlgorithmNegotiation", "AllowSMIMESoftCerts", "AllowBrowser",
+ "AllowConsumerEmail", "AllowRemoteDesktop", "AllowInternetSharing",
+ "UnapprovedInROMApplicationList", "ApplicationName", "ApprovedApplicationList", "Hash"
+ },
+ {
+ // 0x0F Search
+ },
+ {
+ // 0x10 Gal
+ "DisplayName", "Phone", "Office", "Title", "Company", "Alias", "FirstName", "LastName",
+ "HomePhone", "MobilePhone", "EmailAddress"
+ },
+ {
+ // 0x11 AirSyncBase
+ "BodyPreference", "BodyPreferenceType", "BodyPreferenceTruncationSize", "AllOrNone",
+ "Body", "Data", "EstimatedDataSize", "Truncated", "Attachments", "Attachment",
+ "DisplayName", "FileReference", "Method", "ContentId", "ContentLocation", "IsInline",
+ "NativeBodyType", "ContentType"
+ },
+ {
+ // 0x12 Settings
+ },
+ {
+ // 0x13 DocumentLibrary
+ },
+ {
+ // 0x14 ItemOperations
+ }
+ };
+}
diff --git a/src/com/android/exchange/Wbxml.java b/src/com/android/exchange/adapter/Wbxml.java
similarity index 97%
rename from src/com/android/exchange/Wbxml.java
rename to src/com/android/exchange/adapter/Wbxml.java
index 07b4d9a..55d8735 100644
--- a/src/com/android/exchange/Wbxml.java
+++ b/src/com/android/exchange/adapter/Wbxml.java
@@ -18,7 +18,7 @@
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE. */
-package com.android.exchange;
+package com.android.exchange.adapter;
/** contains the WBXML constants */
diff --git a/src/com/android/exchange/WbxmlSerializer.java b/src/com/android/exchange/adapter/WbxmlSerializer.java
similarity index 99%
rename from src/com/android/exchange/WbxmlSerializer.java
rename to src/com/android/exchange/adapter/WbxmlSerializer.java
index af3a4f1..1363d85 100644
--- a/src/com/android/exchange/WbxmlSerializer.java
+++ b/src/com/android/exchange/adapter/WbxmlSerializer.java
@@ -1,4 +1,3 @@
-
/* Copyright (c) 2002,2003, Stefan Haustein, Oberhausen, Rhld., Germany
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -22,7 +21,7 @@
//Contributors: Jonathan Cox, Bogdan Onoiu, Jerry Tian
//Simplified for Google, Inc. by Marc Blank
-package com.android.exchange;
+package com.android.exchange.adapter;
import java.io.*;
import java.util.*;
@@ -31,6 +30,7 @@
+
/**
* A class for writing WBXML.
*
diff --git a/src/com/android/exchange/Base64.java b/src/com/android/exchange/utility/Base64.java
similarity index 99%
rename from src/com/android/exchange/Base64.java
rename to src/com/android/exchange/utility/Base64.java
index 71cb7a0..2ca76cc 100644
--- a/src/com/android/exchange/Base64.java
+++ b/src/com/android/exchange/utility/Base64.java
@@ -11,7 +11,7 @@
* @author rob@iharder.net
* @version 2.2.2
*/
-package com.android.exchange;
+package com.android.exchange.utility;
import java.io.Writer;
diff --git a/src/com/android/exchange/QuotedPrintable.java b/src/com/android/exchange/utility/QuotedPrintable.java
similarity index 89%
rename from src/com/android/exchange/QuotedPrintable.java
rename to src/com/android/exchange/utility/QuotedPrintable.java
index 08ebdd5..a0b0928 100644
--- a/src/com/android/exchange/QuotedPrintable.java
+++ b/src/com/android/exchange/utility/QuotedPrintable.java
@@ -15,8 +15,13 @@
* limitations under the License.
*/
-package com.android.exchange;
+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();
@@ -34,7 +39,9 @@
if (n == '\n') {
continue;
} else {
- System.err.println("Not valid QP");
+ // 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?
diff --git a/src/com/android/exchange/Rfc822Formatter.java b/src/com/android/exchange/utility/Rfc822Formatter.java
similarity index 94%
rename from src/com/android/exchange/Rfc822Formatter.java
rename to src/com/android/exchange/utility/Rfc822Formatter.java
index fdd2178..5508807 100644
--- a/src/com/android/exchange/Rfc822Formatter.java
+++ b/src/com/android/exchange/utility/Rfc822Formatter.java
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-package com.android.exchange;
+package com.android.exchange.utility;
import java.io.File;
import java.io.FileInputStream;
@@ -38,6 +38,12 @@
import android.text.SpannedString;
import android.util.Log;
+/**
+ * Generates RFC822 formatted message data from a Message object. This functionality is also needed
+ * by the SMTP code, so we should use a single piece of code for this purpose. stadler is currently
+ * planning on rewriting SMTP code to handle this task, and we will use that code when it is ready.
+ *
+ */
public class Rfc822Formatter {
static final SimpleDateFormat rfc822DateFormat = new SimpleDateFormat("dd MMM yy HH:mm:ss Z");
@@ -49,14 +55,14 @@
static final String CRLF = "\r\n";
- static public String writeEmailAsRfc822String (Context context, Account acct,
+ static public String writeEmailAsRfc822String(Context context, Account acct,
Message msg, String uniqueId) throws IOException {
StringWriter w = new StringWriter();
writeEmailAsRfc822(context, acct, msg, w, uniqueId);
return w.toString();
}
- static public boolean writeEmailAsRfc822 (Context context, Account acct,
+ static public boolean writeEmailAsRfc822(Context context, Account acct,
Message msg, Writer writer, String uniqueId) throws IOException {
// For now, multi-part alternative means an HTML reply...
boolean alternativeParts = false;