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;