Switch contacts sync to an EasSyncBase.

Also includes some changes to EasSyncBase and friends to
accomodate this conversion.

Change-Id: I6b58bbdd9c62120371a92d1a2deddbab302d1b13
diff --git a/src/com/android/exchange/adapter/ContactsSyncParser.java b/src/com/android/exchange/adapter/ContactsSyncParser.java
index c5b6068..3504bd9 100644
--- a/src/com/android/exchange/adapter/ContactsSyncParser.java
+++ b/src/com/android/exchange/adapter/ContactsSyncParser.java
@@ -40,7 +40,7 @@
 import com.android.emailcommon.provider.Mailbox;
 import com.android.emailcommon.utility.Utility;
 import com.android.exchange.Eas;
-import com.android.exchange.service.EasContactsSyncHandler;
+import com.android.exchange.eas.EasSyncContacts;
 import com.android.exchange.service.EasSyncHandler;
 import com.android.exchange.utility.CalendarUtilities;
 import com.android.mail.utils.LogUtils;
@@ -1308,7 +1308,7 @@
     @Override
     protected void wipe() {
         LogUtils.w(TAG, "Wiping contacts for account %d", mAccount.mId);
-        EasContactsSyncHandler.wipeAccountFromContentProvider(mContext,
+        EasSyncContacts.wipeAccountFromContentProvider(mContext,
                 mAccount.mEmailAddress);
     }
 }
diff --git a/src/com/android/exchange/adapter/FolderSyncParser.java b/src/com/android/exchange/adapter/FolderSyncParser.java
index 4805cdc..de06658 100644
--- a/src/com/android/exchange/adapter/FolderSyncParser.java
+++ b/src/com/android/exchange/adapter/FolderSyncParser.java
@@ -40,8 +40,8 @@
 import com.android.exchange.CommandStatusException;
 import com.android.exchange.CommandStatusException.CommandStatus;
 import com.android.exchange.Eas;
+import com.android.exchange.eas.EasSyncContacts;
 import com.android.exchange.service.EasCalendarSyncHandler;
-import com.android.exchange.service.EasContactsSyncHandler;
 import com.android.mail.utils.LogUtils;
 import com.google.common.annotations.VisibleForTesting;
 
@@ -761,7 +761,7 @@
     protected void wipe() {
         EasCalendarSyncHandler.wipeAccountFromContentProvider(mContext,
                 mAccount.mEmailAddress);
-        EasContactsSyncHandler.wipeAccountFromContentProvider(mContext,
+        EasSyncContacts.wipeAccountFromContentProvider(mContext,
                 mAccount.mEmailAddress);
 
         // Save away any mailbox sync information that is NOT default
diff --git a/src/com/android/exchange/eas/EasSyncBase.java b/src/com/android/exchange/eas/EasSyncBase.java
index ede73b5..871000e 100644
--- a/src/com/android/exchange/eas/EasSyncBase.java
+++ b/src/com/android/exchange/eas/EasSyncBase.java
@@ -1,8 +1,10 @@
 package com.android.exchange.eas;
 
 import android.content.Context;
+import android.net.TrafficStats;
 import android.text.format.DateUtils;
 
+import com.android.emailcommon.TrafficFlags;
 import com.android.emailcommon.provider.Account;
 import com.android.emailcommon.provider.EmailContent;
 import com.android.emailcommon.provider.Mailbox;
@@ -70,6 +72,9 @@
             if (mCollectionTypeHandler == null) {
                 return false;
             }
+            // Set up traffic stats bookkeeping.
+            final int trafficFlags = TrafficFlags.getSyncFlags(mContext, mAccount);
+            TrafficStats.setThreadStatsTag(trafficFlags | mCollectionTypeHandler.getTrafficFlag());
         }
         return result;
     }
@@ -121,6 +126,9 @@
         final String key = getSyncKey();
         while (result == RESULT_MORE_AVAILABLE) {
             result = super.performOperation();
+            if (result == RESULT_MORE_AVAILABLE || result == RESULT_DONE) {
+                mCollectionTypeHandler.cleanup(mContext, mAccount);
+            }
             // TODO: Clear pending request queue.
             final String newKey = getSyncKey();
             if (result == RESULT_MORE_AVAILABLE && key.equals(newKey)) {
@@ -155,16 +163,14 @@
             case Mailbox.TYPE_INBOX:
             case Mailbox.TYPE_DRAFTS:
             case Mailbox.TYPE_SENT:
-//            case Mailbox.TYPE_TRASH:
-            case Mailbox.TYPE_JUNK: {
+            case Mailbox.TYPE_TRASH:
+            //case Mailbox.TYPE_JUNK:
                 return new EasSyncMail();
-            }
             case Mailbox.TYPE_CALENDAR:
-                // TODO: fill this in when we have EasSyncContacts;
+                // TODO: fill this in when we have EasSyncCalendar;
                 return null;
             case Mailbox.TYPE_CONTACTS:
-                // TODO: fill this in when we have EasSyncContacts;
-                return null;
+                return new EasSyncContacts(mAccount.mEmailAddress);
             default:
                 LogUtils.e(LOG_TAG, "unexpected collectiontype %d", type);
                 return null;
diff --git a/src/com/android/exchange/eas/EasSyncCollectionTypeBase.java b/src/com/android/exchange/eas/EasSyncCollectionTypeBase.java
index d8e578c..555e535 100644
--- a/src/com/android/exchange/eas/EasSyncCollectionTypeBase.java
+++ b/src/com/android/exchange/eas/EasSyncCollectionTypeBase.java
@@ -4,8 +4,10 @@
 
 import com.android.emailcommon.provider.Account;
 import com.android.emailcommon.provider.Mailbox;
+import com.android.exchange.Eas;
 import com.android.exchange.adapter.AbstractSyncParser;
 import com.android.exchange.adapter.Serializer;
+import com.android.exchange.adapter.Tags;
 
 import java.io.IOException;
 import java.io.InputStream;
@@ -21,6 +23,13 @@
     public static final int MAX_WINDOW_SIZE = 512;
 
     /**
+     * Get the flag for traffic bookkeeping for this sync type.
+     * @return The appropriate value from {@link com.android.emailcommon.TrafficFlags} for this
+     *         sync.
+     */
+    public abstract int getTrafficFlag();
+
+    /**
      * Write the contents of a Collection node in an EAS sync request appropriate for our mailbox.
      * See http://msdn.microsoft.com/en-us/library/gg650891(v=exchg.80).aspx for documentation on
      * the contents of this sync request element.
@@ -49,4 +58,44 @@
      */
     public abstract AbstractSyncParser getParser(final Context context, final Account account,
             final Mailbox mailbox, final InputStream is) throws IOException;
+
+    /**
+     * After every successful sync iteration, this function gets called to cleanup any state to
+     * match the sync result (e.g., to clean up an external ContentProvider for PIM data).
+     * @param context
+     * @param account
+     */
+    public void cleanup(final Context context, final Account account) {}
+
+    /**
+     * Shared non-initial sync options for PIM (contacts & calendar) objects.
+     *
+     * @param s The {@link com.android.exchange.adapter.Serializer} for this sync request.
+     * @param filter The lookback to use, or null if no lookback is desired.
+     * @param protocolVersion The EAS protocol version for this request, as a double.
+     * @param windowSize
+     * @throws IOException
+     */
+    protected static void setPimSyncOptions(final Serializer s, final String filter,
+            final double protocolVersion, int windowSize) throws IOException {
+        s.tag(Tags.SYNC_DELETES_AS_MOVES);
+        s.tag(Tags.SYNC_GET_CHANGES);
+        s.data(Tags.SYNC_WINDOW_SIZE, String.valueOf(windowSize));
+        s.start(Tags.SYNC_OPTIONS);
+        // Set the filter (lookback), if provided
+        if (filter != null) {
+            s.data(Tags.SYNC_FILTER_TYPE, filter);
+        }
+        // Set the truncation amount and body type
+        if (protocolVersion >= Eas.SUPPORTED_PROTOCOL_EX2007_DOUBLE) {
+            s.start(Tags.BASE_BODY_PREFERENCE);
+            // Plain text
+            s.data(Tags.BASE_TYPE, Eas.BODY_PREFERENCE_TEXT);
+            s.data(Tags.BASE_TRUNCATION_SIZE, Eas.EAS12_TRUNCATION_SIZE);
+            s.end();
+        } else {
+            s.data(Tags.SYNC_TRUNCATION, Eas.EAS2_5_TRUNCATION_SIZE);
+        }
+        s.end();
+    }
 }
diff --git a/src/com/android/exchange/service/EasContactsSyncHandler.java b/src/com/android/exchange/eas/EasSyncContacts.java
similarity index 90%
rename from src/com/android/exchange/service/EasContactsSyncHandler.java
rename to src/com/android/exchange/eas/EasSyncContacts.java
index 8757b92..3a6ffc0 100644
--- a/src/com/android/exchange/service/EasContactsSyncHandler.java
+++ b/src/com/android/exchange/eas/EasSyncContacts.java
@@ -1,4 +1,4 @@
-package com.android.exchange.service;
+package com.android.exchange.eas;
 
 import android.content.ContentProviderOperation;
 import android.content.ContentResolver;
@@ -7,10 +7,8 @@
 import android.content.Context;
 import android.content.Entity;
 import android.content.EntityIterator;
-import android.content.SyncResult;
 import android.database.Cursor;
 import android.net.Uri;
-import android.os.Bundle;
 import android.provider.ContactsContract;
 import android.provider.ContactsContract.CommonDataKinds.Email;
 import android.provider.ContactsContract.CommonDataKinds.Event;
@@ -54,9 +52,11 @@
  * Contact state is in the contacts provider, not in our DB (and therefore not in e.g. mMailbox).
  * The Mailbox in the Email DB is only useful for serverId and syncInterval.
  */
-public class EasContactsSyncHandler extends EasSyncHandler {
+public class EasSyncContacts extends EasSyncCollectionTypeBase {
     private static final String TAG = Eas.LOG_TAG;
 
+    public static final int PIM_WINDOW_SIZE_CONTACTS = 10;
+
     private static final String MIMETYPE_GROUP_MEMBERSHIP_AND_ID_EQUALS =
             ContactsContract.Data.MIMETYPE + "='" + GroupMembership.CONTENT_ITEM_TYPE + "' AND " +
                     GroupMembership.GROUP_ROW_ID + "=?";
@@ -152,34 +152,44 @@
         public static final String ACCOUNT_NAME = "data8";
     }
 
-    public EasContactsSyncHandler(final Context context, final ContentResolver contentResolver,
-            final android.accounts.Account accountManagerAccount, final Account account,
-            final Mailbox mailbox, final Bundle syncExtras, final SyncResult syncResult) {
-        super(context, contentResolver, account, mailbox, syncExtras, syncResult);
-        mAccountManagerAccount = accountManagerAccount;
+    public EasSyncContacts(final String emailAddress) {
+        mAccountManagerAccount = new android.accounts.Account(emailAddress,
+                Eas.EXCHANGE_ACCOUNT_MANAGER_TYPE);
     }
 
     @Override
-    protected int getTrafficFlag() {
+    public int getTrafficFlag() {
         return TrafficFlags.DATA_CONTACTS;
     }
 
     @Override
-    protected String getFolderClassName() {
-        return "Contacts";
+    public void setSyncOptions(final Context context, final Serializer s,
+            final double protocolVersion, final Account account, final Mailbox mailbox,
+            final boolean isInitialSync, final int numWindows) throws IOException {
+        if (isInitialSync) {
+            setInitialSyncOptions(s);
+            return;
+        }
+
+        final int windowSize = numWindows * PIM_WINDOW_SIZE_CONTACTS;
+        if (windowSize > MAX_WINDOW_SIZE  + PIM_WINDOW_SIZE_CONTACTS) {
+            throw new IOException("Max window size reached and still no data");
+        }
+        setPimSyncOptions(s, null, protocolVersion,
+                windowSize < MAX_WINDOW_SIZE ? windowSize : MAX_WINDOW_SIZE);
+
+        setUpsyncCommands(s, context.getContentResolver(), account, mailbox, protocolVersion);
     }
 
     @Override
-    protected AbstractSyncParser getParser(final InputStream is) throws IOException {
-        // Store the parser because we'll want to ask it about whether groups are used later.
-        // TODO: It'd be nice to find a cleaner way to get this result back from the parser.
-        mParser = new ContactsSyncParser(mContext, mContentResolver, is,
-                mMailbox, mAccount, mAccountManagerAccount);
+    public AbstractSyncParser getParser(final Context context, final Account account,
+            final Mailbox mailbox, final InputStream is) throws IOException {
+        mParser = new ContactsSyncParser(context, context.getContentResolver(), is, mailbox,
+                account, mAccountManagerAccount);
         return mParser;
     }
 
-    @Override
-    protected void setInitialSyncOptions(final Serializer s) throws IOException {
+    private void setInitialSyncOptions(final Serializer s) throws IOException {
         // These are the tags we support for upload; whenever we add/remove support
         // (in addData), we need to update this list
         s.start(Tags.SYNC_SUPPORTED);
@@ -243,15 +253,6 @@
         s.end(); // SYNC_SUPPORTED
     }
 
-    @Override
-    protected void setNonInitialSyncOptions(final Serializer s, int numWindows) throws IOException {
-        final int windowSize = numWindows * PIM_WINDOW_SIZE_CONTACTS;
-        if (windowSize > MAX_WINDOW_SIZE  + PIM_WINDOW_SIZE_CONTACTS) {
-            throw new IOException("Max window size reached and still no data");
-        }
-        setPimSyncOptions(s, null, windowSize < MAX_WINDOW_SIZE ? windowSize : MAX_WINDOW_SIZE);
-    }
-
     /**
      * Add account info and the "caller is syncadapter" param to a URI.
      * @param uri The {@link Uri} to add to.
@@ -281,10 +282,9 @@
     /**
      * Mark contacts in dirty groups as dirty.
      */
-    private void dirtyContactsWithinDirtyGroups() {
-        final String emailAddress = mAccount.mEmailAddress;
-        final Cursor c = mContentResolver.query(
-                uriWithAccountAndIsSyncAdapter(Groups.CONTENT_URI, emailAddress),
+    private void dirtyContactsWithinDirtyGroups(final ContentResolver cr, final Account account) {
+        final String emailAddress = account.mEmailAddress;
+        final Cursor c = cr.query( uriWithAccountAndIsSyncAdapter(Groups.CONTENT_URI, emailAddress),
                 GROUPS_ID_PROJECTION, Groups.DIRTY + "=1", null, null);
         if (c == null) {
             return;
@@ -300,19 +300,17 @@
                     final long id = c.getLong(0);
                     updateValues.put(GroupMembership.GROUP_ROW_ID, id);
                     updateArgs[0] = Long.toString(id);
-                    mContentResolver.update(ContactsContract.Data.CONTENT_URI, updateValues,
+                    cr.update(ContactsContract.Data.CONTENT_URI, updateValues,
                             MIMETYPE_GROUP_MEMBERSHIP_AND_ID_EQUALS, updateArgs);
                 }
                 // Really delete groups that are marked deleted
-                mContentResolver.delete(uriWithAccountAndIsSyncAdapter(
-                        Groups.CONTENT_URI, emailAddress),
+                cr.delete(uriWithAccountAndIsSyncAdapter(Groups.CONTENT_URI, emailAddress),
                         Groups.DELETED + "=1", null);
                 // Clear the dirty flag for all of our groups
                 updateValues.clear();
                 updateValues.put(Groups.DIRTY, 0);
-                mContentResolver.update(uriWithAccountAndIsSyncAdapter(
-                        Groups.CONTENT_URI, emailAddress), updateValues, null,
-                        null);
+                cr.update(uriWithAccountAndIsSyncAdapter(Groups.CONTENT_URI, emailAddress),
+                        updateValues, null, null);
             }
         } finally {
             c.close();
@@ -638,9 +636,11 @@
      * Add a note to the upsync.
      * @param s The {@link Serializer} for this sync request.
      * @param cv The {@link ContentValues} with the data for this note.
+     * @param protocolVersion
      * @throws IOException
      */
-    private void sendNote(final Serializer s, final ContentValues cv) throws IOException {
+    private void sendNote(final Serializer s, final ContentValues cv, final double protocolVersion)
+            throws IOException {
         // Even when there is no local note, we must explicitly upsync an empty note,
         // which is the only way to force the server to delete any pre-existing note.
         String note = "";
@@ -649,7 +649,7 @@
             note = cv.getAsString(Note.NOTE).replaceAll("\n", "\r\n");
         }
         // Format of upsync data depends on protocol version
-        if (getProtocolVersion() >= Eas.SUPPORTED_PROTOCOL_EX2007_DOUBLE) {
+        if (protocolVersion >= Eas.SUPPORTED_PROTOCOL_EX2007_DOUBLE) {
             s.start(Tags.BASE_BODY);
             s.data(Tags.BASE_TYPE, Eas.BODY_PREFERENCE_TEXT).data(Tags.BASE_DATA, note);
             s.end();
@@ -681,10 +681,11 @@
      * @param cv The {@link ContentValues} with the data for this email address.
      * @param count The number of email addresses that have already been added.
      * @param displayName The display name for this contact.
+     * @param protocolVersion
      * @throws IOException
      */
     private void sendEmail(final Serializer s, final ContentValues cv, final int count,
-            final String displayName) throws IOException {
+            final String displayName, final double protocolVersion) throws IOException {
         // Get both parts of the email address (a newly created one in the UI won't have a name)
         final String addr = cv.getAsString(Email.DATA);
         String name = cv.getAsString(Email.DISPLAY_NAME);
@@ -700,7 +701,7 @@
             final String value;
             // Only send the raw email address for EAS 2.5 (Hotmail, in particular, chokes on
             // an RFC822 address)
-            if (getProtocolVersion() < Eas.SUPPORTED_PROTOCOL_EX2007_DOUBLE) {
+            if (protocolVersion < Eas.SUPPORTED_PROTOCOL_EX2007_DOUBLE) {
                 value = addr;
             } else {
                 value = '\"' + name + "\" <" + addr + '>';
@@ -711,19 +712,19 @@
         }
     }
 
-    @Override
-    protected void setUpsyncCommands(final Serializer s) throws IOException {
+    private void setUpsyncCommands(final Serializer s, final ContentResolver cr,
+            final Account account, final Mailbox mailbox, final double protocolVersion)
+            throws IOException {
         // Find any groups of ours that are dirty and dirty those groups' members
-        dirtyContactsWithinDirtyGroups();
+        dirtyContactsWithinDirtyGroups(cr, account);
 
         // First, let's find Contacts that have changed.
         final Uri uri = uriWithAccountAndIsSyncAdapter(
-                ContactsContract.RawContactsEntity.CONTENT_URI, mAccount.mEmailAddress);
+                ContactsContract.RawContactsEntity.CONTENT_URI, account.mEmailAddress);
 
         // Get them all atomically
         final EntityIterator ei = ContactsContract.RawContacts.newEntityIterator(
-                mContentResolver.query(uri, null, ContactsContract.RawContacts.DIRTY + "=1", null,
-                        null));
+                cr.query(uri, null, ContactsContract.RawContacts.DIRTY + "=1", null, null));
         final ContentValues cidValues = new ContentValues();
         try {
             boolean first = true;
@@ -744,12 +745,12 @@
                 if (serverId == null) {
                     // This is a new contact; create a clientId
                     final String clientId =
-                            "new_" + mMailbox.mId + '_' + System.currentTimeMillis();
+                            "new_" + mailbox.mId + '_' + System.currentTimeMillis();
                     LogUtils.d(TAG, "Creating new contact with clientId: %s", clientId);
                     s.start(Tags.SYNC_ADD).data(Tags.SYNC_CLIENT_ID, clientId);
                     // And save it in the raw contact
                     cidValues.put(ContactsContract.RawContacts.SYNC1, clientId);
-                    mContentResolver.update(ContentUris.withAppendedId(rawContactUri,
+                    cr.update(ContentUris.withAppendedId(rawContactUri,
                             entityValues.getAsLong(ContactsContract.RawContacts._ID)),
                             cidValues, null, null);
                 } else {
@@ -811,7 +812,7 @@
                         // We must gather these, and send them together (below)
                         groupIds.add(cv.getAsInteger(GroupMembership.GROUP_ROW_ID));
                     } else if (mimeType.equals(Note.CONTENT_ITEM_TYPE)) {
-                        sendNote(s, cv);
+                        sendNote(s, cv, protocolVersion);
                     } else if (mimeType.equals(Photo.CONTENT_ITEM_TYPE)) {
                         sendPhoto(s, cv);
                     } else {
@@ -822,7 +823,7 @@
                 // We do the email rows last, because we need to make sure we've found the
                 // displayName (if one exists); this would be in a StructuredName rnow
                 for (final ContentValues cv: emailValues) {
-                    sendEmail(s, cv, emailCount++, displayName);
+                    sendEmail(s, cv, emailCount++, displayName, protocolVersion);
                 }
 
                 // Now, we'll send up groups, if any
@@ -830,9 +831,8 @@
                     boolean groupFirst = true;
                     for (final int id: groupIds) {
                         // Since we get id's from the provider, we need to find their names
-                        final Cursor c = mContentResolver.query(ContentUris.withAppendedId(
-                                Groups.CONTENT_URI, id),
-                                GROUP_TITLE_PROJECTION, null, null, null);
+                        final Cursor c = cr.query(ContentUris.withAppendedId(Groups.CONTENT_URI,
+                                id), GROUP_TITLE_PROJECTION, null, null, null);
                         try {
                             // Presumably, this should always succeed, but ...
                             if (c.moveToFirst()) {
@@ -863,10 +863,8 @@
     }
 
     @Override
-    protected void cleanup(final int syncResult) {
-        if (syncResult == SYNC_RESULT_FAILED) {
-            return;
-        }
+    public void cleanup(final Context context, final Account account) {
+        final ContentResolver cr = context.getContentResolver();
 
         // Mark the changed contacts dirty = 0
         // Permanently delete the user deletions
@@ -885,16 +883,15 @@
                     .appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true").build())
                     .build());
         }
-        ops.execute(mContext);
+        ops.execute(context);
         if (mParser != null && mParser.isGroupsUsed()) {
             // Make sure the title column is set for all of our groups
             // And that all of our groups are visible
             // TODO Perhaps the visible part should only happen when the group is created, but
             // this is fine for now.
             final Uri groupsUri = uriWithAccountAndIsSyncAdapter(Groups.CONTENT_URI,
-                    mAccount.mEmailAddress);
-            final Cursor c = mContentResolver.query(groupsUri,
-                    new String[] {Groups.SOURCE_ID, Groups.TITLE},
+                    account.mEmailAddress);
+            final Cursor c = cr.query(groupsUri, new String[] {Groups.SOURCE_ID, Groups.TITLE},
                     Groups.TITLE + " IS NULL", null, null);
             final ContentValues values = new ContentValues();
             values.put(Groups.GROUP_VISIBLE, 1);
@@ -902,8 +899,8 @@
                 while (c.moveToNext()) {
                     final String sourceId = c.getString(0);
                     values.put(Groups.TITLE, sourceId);
-                    mContentResolver.update(uriWithAccountAndIsSyncAdapter(groupsUri,
-                            mAccount.mEmailAddress), values, Groups.SOURCE_ID + "=?",
+                    cr.update(uriWithAccountAndIsSyncAdapter(groupsUri,
+                            account.mEmailAddress), values, Groups.SOURCE_ID + "=?",
                             new String[] {sourceId});
                 }
             } finally {
diff --git a/src/com/android/exchange/eas/EasSyncMail.java b/src/com/android/exchange/eas/EasSyncMail.java
index eec5bdf..f5e0e15 100644
--- a/src/com/android/exchange/eas/EasSyncMail.java
+++ b/src/com/android/exchange/eas/EasSyncMail.java
@@ -3,6 +3,7 @@
 import android.content.Context;
 import android.database.Cursor;
 
+import com.android.emailcommon.TrafficFlags;
 import com.android.emailcommon.provider.Account;
 import com.android.emailcommon.provider.EmailContent.Message;
 import com.android.emailcommon.provider.EmailContent.MessageColumns;
@@ -32,6 +33,12 @@
 
     private static final int EMAIL_WINDOW_SIZE = 10;
 
+
+    @Override
+    public int getTrafficFlag() {
+        return TrafficFlags.DATA_EMAIL;
+    }
+
     @Override
     public void setSyncOptions(final Context context, final Serializer s,
             final double protocolVersion, final Account account, final Mailbox mailbox,
diff --git a/src/com/android/exchange/service/EasMailboxSyncHandler.java b/src/com/android/exchange/service/EasMailboxSyncHandler.java
deleted file mode 100644
index 0384674..0000000
--- a/src/com/android/exchange/service/EasMailboxSyncHandler.java
+++ /dev/null
@@ -1,196 +0,0 @@
-package com.android.exchange.service;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.SyncResult;
-import android.database.Cursor;
-import android.os.Bundle;
-
-import com.android.emailcommon.TrafficFlags;
-import com.android.emailcommon.provider.Account;
-import com.android.emailcommon.provider.EmailContent.Message;
-import com.android.emailcommon.provider.EmailContent.MessageColumns;
-import com.android.emailcommon.provider.EmailContent.SyncColumns;
-import com.android.emailcommon.provider.Mailbox;
-import com.android.emailcommon.service.SyncWindow;
-import com.android.exchange.Eas;
-import com.android.exchange.adapter.AbstractSyncParser;
-import com.android.exchange.adapter.EmailSyncParser;
-import com.android.exchange.adapter.Serializer;
-import com.android.exchange.adapter.Tags;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-
-/**
- * Performs an Exchange mailbox sync for "normal" mailboxes.
- */
-public class EasMailboxSyncHandler extends EasSyncHandler {
-    /**
-     * The projection used for building the fetch request list.
-     */
-    private static final String[] FETCH_REQUEST_PROJECTION = { SyncColumns.SERVER_ID };
-    private static final int FETCH_REQUEST_SERVER_ID = 0;
-
-    private static final int EMAIL_WINDOW_SIZE = 10;
-
-    /**
-     * List of server ids for messages to fetch from the server.
-     */
-    private final ArrayList<String> mMessagesToFetch = new ArrayList<String>();
-
-    public EasMailboxSyncHandler(final Context context, final ContentResolver contentResolver,
-            final Account account, final Mailbox mailbox, final Bundle syncExtras,
-            final SyncResult syncResult) {
-        super(context, contentResolver, account, mailbox, syncExtras, syncResult);
-    }
-
-    private String getEmailFilter() {
-        final int syncLookback = mMailbox.mSyncLookback == SyncWindow.SYNC_WINDOW_ACCOUNT
-                ? mAccount.mSyncLookback : mMailbox.mSyncLookback;
-        switch (syncLookback) {
-            case SyncWindow.SYNC_WINDOW_1_DAY:
-                return Eas.FILTER_1_DAY;
-            case SyncWindow.SYNC_WINDOW_3_DAYS:
-                return Eas.FILTER_3_DAYS;
-            case SyncWindow.SYNC_WINDOW_1_WEEK:
-                return Eas.FILTER_1_WEEK;
-            case SyncWindow.SYNC_WINDOW_2_WEEKS:
-                return Eas.FILTER_2_WEEKS;
-            case SyncWindow.SYNC_WINDOW_1_MONTH:
-                return Eas.FILTER_1_MONTH;
-            case SyncWindow.SYNC_WINDOW_ALL:
-                return Eas.FILTER_ALL;
-            default:
-                // Auto window is deprecated and will also use the default.
-                return Eas.FILTER_1_WEEK;
-        }
-    }
-
-    /**
-     * Find partially loaded messages and add their server ids to {@link #mMessagesToFetch}.
-     */
-    private void addToFetchRequestList() {
-        final Cursor c = mContentResolver.query(Message.CONTENT_URI, FETCH_REQUEST_PROJECTION,
-                MessageColumns.FLAG_LOADED + "=" + Message.FLAG_LOADED_PARTIAL + " AND " +
-                MessageColumns.MAILBOX_KEY + "=?", new String[] {Long.toString(mMailbox.mId)},
-                null);
-        if (c != null) {
-            try {
-                while (c.moveToNext()) {
-                    mMessagesToFetch.add(c.getString(FETCH_REQUEST_SERVER_ID));
-                }
-            } finally {
-                c.close();
-            }
-        }
-    }
-
-    @Override
-    protected int getTrafficFlag() {
-        return TrafficFlags.DATA_EMAIL;
-    }
-
-    @Override
-    protected String getFolderClassName() {
-        return "Email";
-    }
-
-    @Override
-    protected AbstractSyncParser getParser(final InputStream is) throws IOException {
-        return new EmailSyncParser(mContext, mContentResolver, is, mMailbox, mAccount);
-    }
-
-    @Override
-    protected void setInitialSyncOptions(final Serializer s) {
-        // No-op.
-    }
-
-    @Override
-    protected void setNonInitialSyncOptions(final Serializer s, int numWindows) throws IOException {
-        // Check for messages that aren't fully loaded.
-        addToFetchRequestList();
-        // The "empty" case is typical; we send a request for changes, and also specify a sync
-        // window, body preference type (HTML for EAS 12.0 and later; MIME for EAS 2.5), and
-        // truncation
-        // If there are fetch requests, we only want the fetches (i.e. no changes from the server)
-        // so we turn MIME support off.  Note that we are always using EAS 2.5 if there are fetch
-        // requests
-        if (mMessagesToFetch.isEmpty()) {
-            // Permanently delete if in trash mailbox
-            // In Exchange 2003, deletes-as-moves tag = true; no tag = false
-            // In Exchange 2007 and up, deletes-as-moves tag is "0" (false) or "1" (true)
-            final boolean isTrashMailbox = mMailbox.mType == Mailbox.TYPE_TRASH;
-            if (getProtocolVersion() < Eas.SUPPORTED_PROTOCOL_EX2007_DOUBLE) {
-                if (!isTrashMailbox) {
-                    s.tag(Tags.SYNC_DELETES_AS_MOVES);
-                }
-            } else {
-                s.data(Tags.SYNC_DELETES_AS_MOVES, isTrashMailbox ? "0" : "1");
-            }
-            s.tag(Tags.SYNC_GET_CHANGES);
-
-            final int windowSize = numWindows * EMAIL_WINDOW_SIZE;
-            if (windowSize > MAX_WINDOW_SIZE  + EMAIL_WINDOW_SIZE) {
-                throw new IOException("Max window size reached and still no data");
-            }
-            s.data(Tags.SYNC_WINDOW_SIZE,
-                    String.valueOf(windowSize < MAX_WINDOW_SIZE ? windowSize : MAX_WINDOW_SIZE));
-            s.start(Tags.SYNC_OPTIONS);
-            // Set the lookback appropriately (EAS calls this a "filter")
-            s.data(Tags.SYNC_FILTER_TYPE, getEmailFilter());
-            // Set the truncation amount for all classes
-            if (getProtocolVersion() >= Eas.SUPPORTED_PROTOCOL_EX2007_DOUBLE) {
-                s.start(Tags.BASE_BODY_PREFERENCE);
-                // HTML for email
-                s.data(Tags.BASE_TYPE, Eas.BODY_PREFERENCE_HTML);
-                s.data(Tags.BASE_TRUNCATION_SIZE, Eas.EAS12_TRUNCATION_SIZE);
-                s.end();
-            } else {
-                // Use MIME data for EAS 2.5
-                s.data(Tags.SYNC_MIME_SUPPORT, Eas.MIME_BODY_PREFERENCE_MIME);
-                s.data(Tags.SYNC_MIME_TRUNCATION, Eas.EAS2_5_TRUNCATION_SIZE);
-            }
-            s.end();
-        } else {
-            // If we have any messages that are not fully loaded, ask for plain text rather than
-            // MIME, to guarantee we'll get usable text body. This also means we should NOT ask for
-            // new messages -- we only want data for the message explicitly fetched.
-            s.start(Tags.SYNC_OPTIONS);
-            s.data(Tags.SYNC_MIME_SUPPORT, Eas.MIME_BODY_PREFERENCE_TEXT);
-            s.data(Tags.SYNC_TRUNCATION, Eas.EAS2_5_TRUNCATION_SIZE);
-            s.end();
-        }
-    }
-
-    /**
-     * Add FETCH commands for messages that need a body (i.e. we didn't find it during our earlier
-     * sync; this happens only in EAS 2.5 where the body couldn't be found after parsing the
-     * message's MIME data).
-     * @param s The {@link Serializer} for this sync request.
-     * @throws IOException
-     */
-    private void addFetchCommands(final Serializer s) throws IOException {
-        if (!mMessagesToFetch.isEmpty()) {
-            s.start(Tags.SYNC_COMMANDS);
-            for (final String serverId : mMessagesToFetch) {
-                s.start(Tags.SYNC_FETCH).data(Tags.SYNC_SERVER_ID, serverId).end();
-            }
-            s.end();
-        }
-    }
-
-    @Override
-    protected void setUpsyncCommands(final Serializer s) throws IOException {
-        addFetchCommands(s);
-    }
-
-    @Override
-    protected void cleanup(final int syncResult) {
-        if (syncResult == SYNC_RESULT_MORE_AVAILABLE) {
-            // Prepare our member variables for another sync request.
-            mMessagesToFetch.clear();
-        }
-    }
-}
diff --git a/src/com/android/exchange/service/EasSyncHandler.java b/src/com/android/exchange/service/EasSyncHandler.java
index 6614399..148da49 100644
--- a/src/com/android/exchange/service/EasSyncHandler.java
+++ b/src/com/android/exchange/service/EasSyncHandler.java
@@ -126,19 +126,9 @@
             final Bundle syncExtras, final SyncResult syncResult) {
         if (account != null && mailbox != null) {
             switch (mailbox.mType) {
-                case Mailbox.TYPE_INBOX:
-                case Mailbox.TYPE_MAIL:
-                case Mailbox.TYPE_DRAFTS:
-                case Mailbox.TYPE_SENT:
-                case Mailbox.TYPE_TRASH:
-                    return new EasMailboxSyncHandler(context, contentResolver, account, mailbox,
-                            syncExtras, syncResult);
                 case Mailbox.TYPE_CALENDAR:
                     return new EasCalendarSyncHandler(context, contentResolver,
                             accountManagerAccount, account, mailbox, syncExtras, syncResult);
-                case Mailbox.TYPE_CONTACTS:
-                    return new EasContactsSyncHandler(context, contentResolver,
-                            accountManagerAccount, account, mailbox, syncExtras, syncResult);
             }
         }
         // Unknown mailbox type.
diff --git a/src/com/android/exchange/service/EmailSyncAdapterService.java b/src/com/android/exchange/service/EmailSyncAdapterService.java
index 05e7a01..6a2ed42 100644
--- a/src/com/android/exchange/service/EmailSyncAdapterService.java
+++ b/src/com/android/exchange/service/EmailSyncAdapterService.java
@@ -63,6 +63,7 @@
 import com.android.exchange.R.drawable;
 import com.android.exchange.R.string;
 import com.android.exchange.adapter.PingParser;
+import com.android.exchange.eas.EasSyncContacts;
 import com.android.exchange.eas.EasFolderSync;
 import com.android.exchange.eas.EasLoadAttachment;
 import com.android.exchange.eas.EasMoveItems;
@@ -461,7 +462,7 @@
             LogUtils.d(TAG, "IEmailService.deleteAccountPIMData");
             if (emailAddress != null) {
                 final Context context = EmailSyncAdapterService.this;
-                EasContactsSyncHandler.wipeAccountFromContentProvider(context, emailAddress);
+                EasSyncContacts.wipeAccountFromContentProvider(context, emailAddress);
                 EasCalendarSyncHandler.wipeAccountFromContentProvider(context, emailAddress);
             }
             // TODO: Run account reconciler?
@@ -875,18 +876,17 @@
             // Non-mailbox syncs are whole account syncs initiated by the AccountManager and are
             // treated as background syncs.
             // TODO: Push will be treated as "user" syncs, and probably should be background.
-            final ContentValues cv = new ContentValues(2);
-            updateMailbox(context, mailbox, cv, isMailboxSync ?
-                    EmailContent.SYNC_STATUS_USER : EmailContent.SYNC_STATUS_BACKGROUND);
-            try {
-                if (mailbox.mType == Mailbox.TYPE_OUTBOX) {
-                    return syncOutbox(context, cr, account, mailbox);
-                }
+            if (mailbox.mType == Mailbox.TYPE_OUTBOX || mailbox.isSyncable()) {
+                final ContentValues cv = new ContentValues(2);
+                updateMailbox(context, mailbox, cv, isMailboxSync ?
+                        EmailContent.SYNC_STATUS_USER : EmailContent.SYNC_STATUS_BACKGROUND);
+                try {
+                    if (mailbox.mType == Mailbox.TYPE_OUTBOX) {
+                        return syncOutbox(context, cr, account, mailbox);
+                    }
 
-                if(mailbox.isSyncable()) {
                     // TODO: This conditional logic is temporary until EasSyncHandler is obsolete.
-                    if (mailbox.mType == Mailbox.TYPE_INBOX || mailbox.mType == Mailbox.TYPE_MAIL ||
-                            mailbox.mType == Mailbox.TYPE_SENT) {
+                    if (mailbox.mType != Mailbox.TYPE_CALENDAR) {
                         final EasSyncBase operation = new EasSyncBase(context, account, mailbox);
                         return operation.performOperation();
                     } else {
@@ -908,9 +908,9 @@
                             }
                         }
                     }
+                } finally {
+                    updateMailbox(context, mailbox, cv, EmailContent.SYNC_STATUS_NONE);
                 }
-            } finally {
-                updateMailbox(context, mailbox, cv, EmailContent.SYNC_STATUS_NONE);
             }
 
             return EasSyncBase.RESULT_DONE;