Merge "Move stub logging classes to org.apache.james.mime4j"
diff --git a/src/com/android/exchange/EasSyncService.java b/src/com/android/exchange/EasSyncService.java
index cd255ee..418b1ea 100644
--- a/src/com/android/exchange/EasSyncService.java
+++ b/src/com/android/exchange/EasSyncService.java
@@ -91,6 +91,9 @@
     static private final String PING_COMMAND = "Ping";
     static private final int COMMAND_TIMEOUT = 20*SECONDS;
 
+    // Define our default protocol version as 2.5 (Exchange 2003)
+    static private final String DEFAULT_PROTOCOL_VERSION = "2.5";
+
     /**
      * We start with an 8 minute timeout, and increase/decrease by 3 minutes at a time.  There's
      * no point having a timeout shorter than 5 minutes, I think; at that point, we can just let
@@ -119,7 +122,7 @@
     static private final int PING_FALLBACK_PIM = 25;
 
     // Reasonable default
-    String mProtocolVersion = "2.5";
+    public String mProtocolVersion = DEFAULT_PROTOCOL_VERSION;
     public Double mProtocolVersionDouble;
     protected String mDeviceId = null;
     private String mDeviceType = "Android";
@@ -557,7 +560,7 @@
                 SyncManager.kick("change ping boxes to push");
             }
 
-            // Determine our protocol version, if we haven't already
+            // Determine our protocol version, if we haven't already and save it in the Account
             if (mAccount.mProtocolVersion == null) {
                 userLog("Determine EAS protocol version");
                 HttpResponse resp = sendHttpClientOptions();
@@ -574,6 +577,10 @@
                         }
                         mProtocolVersionDouble = Double.parseDouble(mProtocolVersion);
                         mAccount.mProtocolVersion = mProtocolVersion;
+                        // Save the protocol version
+                        cv.clear();
+                        cv.put(Account.PROTOCOL_VERSION, mProtocolVersion);
+                        mAccount.update(mContext, cv);
                         userLog(versions);
                         userLog("Using version ", mProtocolVersion);
                     } else {
@@ -588,7 +595,7 @@
 
             // Change all pushable boxes to push when we start the account mailbox
             if (mAccount.mSyncInterval == Account.CHECK_INTERVAL_PUSH) {
-                cv = new ContentValues();
+                cv.clear();
                 cv.put(Mailbox.SYNC_INTERVAL, Mailbox.CHECK_INTERVAL_PUSH);
                 if (mContentResolver.update(Mailbox.CONTENT_URI, cv,
                         SyncManager.WHERE_IN_ACCOUNT_AND_PUSHABLE,
@@ -623,7 +630,7 @@
                 }
 
                 // Change all push/hold boxes to push
-                cv = new ContentValues();
+                cv.clear();
                 cv.put(Mailbox.SYNC_INTERVAL, Account.CHECK_INTERVAL_PUSH);
                 if (mContentResolver.update(Mailbox.CONTENT_URI, cv,
                         WHERE_PUSH_HOLD_NOT_ACCOUNT_MAILBOX,
@@ -1101,11 +1108,13 @@
         mUserName = ha.mLogin;
         mPassword = ha.mPassword;
 
-        // Set up our protocol version
+        // Set up our protocol version from the Account
         mProtocolVersion = mAccount.mProtocolVersion;
-        if (mProtocolVersion != null) {
-            mProtocolVersionDouble = Double.parseDouble(mProtocolVersion);
+        // If it hasn't been set up, start with default version
+        if (mProtocolVersion == null) {
+            mProtocolVersion = DEFAULT_PROTOCOL_VERSION;
         }
+        mProtocolVersionDouble = Double.parseDouble(mProtocolVersion);
         return true;
     }
 
diff --git a/src/com/android/exchange/adapter/EmailSyncAdapter.java b/src/com/android/exchange/adapter/EmailSyncAdapter.java
index a75b293..94c5ef0 100644
--- a/src/com/android/exchange/adapter/EmailSyncAdapter.java
+++ b/src/com/android/exchange/adapter/EmailSyncAdapter.java
@@ -320,7 +320,7 @@
                     WHERE_SERVER_ID_AND_MAILBOX_KEY, mBindArguments, null);
         }
 
-        private void deleteParser(ArrayList<Long> deletes, int entryTag) throws IOException {
+        /*package*/ void deleteParser(ArrayList<Long> deletes, int entryTag) throws IOException {
             while (nextTag(entryTag) != END) {
                 switch (tag) {
                     case Tags.SYNC_SERVER_ID:
@@ -357,7 +357,7 @@
             }
         }
 
-        private void changeParser(ArrayList<ServerChange> changes) throws IOException {
+        /*package*/ void changeParser(ArrayList<ServerChange> changes) throws IOException {
             String serverId = null;
             Boolean oldRead = false;
             Boolean oldFlag = false;
diff --git a/src/com/android/exchange/adapter/Parser.java b/src/com/android/exchange/adapter/Parser.java
index ca99a77..01051c0 100644
--- a/src/com/android/exchange/adapter/Parser.java
+++ b/src/com/android/exchange/adapter/Parser.java
@@ -304,6 +304,10 @@
         tagTable = tagTables[0];
     }
 
+    /*package*/ void resetInput(InputStream in) {
+        this.in = in;
+    }
+    
     void log(String str) {
         int cr = str.indexOf('\n');
         if (cr > 0) {
diff --git a/src/com/android/exchange/adapter/Serializer.java b/src/com/android/exchange/adapter/Serializer.java
index f314772..7c0a3cf 100644
--- a/src/com/android/exchange/adapter/Serializer.java
+++ b/src/com/android/exchange/adapter/Serializer.java
@@ -54,12 +54,20 @@
     private int tagPage;
 
     public Serializer() {
+        this(true);
+    }
+
+    public Serializer(boolean startDocument) {
         super();
-        try {
-            startDocument();
-            //logging = Eas.PARSER_LOG;
-        } catch (IOException e) {
-            // Nothing to be done
+        if (startDocument) {
+            try {
+                startDocument();
+                //logging = Eas.PARSER_LOG;
+            } catch (IOException e) {
+                // Nothing to be done
+            }
+        } else {
+            out.write(0);
         }
     }
 
diff --git a/tests/src/com/android/exchange/adapter/EmailSyncAdapterTests.java b/tests/src/com/android/exchange/adapter/EmailSyncAdapterTests.java
index a702428..e378b03 100644
--- a/tests/src/com/android/exchange/adapter/EmailSyncAdapterTests.java
+++ b/tests/src/com/android/exchange/adapter/EmailSyncAdapterTests.java
@@ -23,8 +23,10 @@
 import com.android.email.provider.EmailContent.Body;
 import com.android.email.provider.EmailContent.Mailbox;
 import com.android.email.provider.EmailContent.Message;
+import com.android.email.provider.EmailContent.SyncColumns;
 import com.android.exchange.EasSyncService;
 import com.android.exchange.adapter.EmailSyncAdapter.EasEmailSyncParser;
+import com.android.exchange.adapter.EmailSyncAdapter.EasEmailSyncParser.ServerChange;
 
 import android.content.ContentResolver;
 import android.content.ContentUris;
@@ -43,6 +45,11 @@
 
     EmailProvider mProvider;
     Context mMockContext;
+    ContentResolver mMockResolver;
+    Mailbox mMailbox;
+    Account mAccount;
+    EmailSyncAdapter mSyncAdapter;
+    EasEmailSyncParser mSyncParser;
 
     public EmailSyncAdapterTests() {
         super(EmailProvider.class, EmailProvider.EMAIL_AUTHORITY);
@@ -52,6 +59,7 @@
     public void setUp() throws Exception {
         super.setUp();
         mMockContext = getMockContext();
+        mMockResolver = mMockContext.getContentResolver();
     }
 
     @Override
@@ -74,7 +82,15 @@
         Mailbox mailbox = new Mailbox();
         mailbox.mId = -1;
         EasSyncService service = new EasSyncService();
-        service.mContext = getContext();
+        service.mContext = mMockContext;
+        service.mMailbox = mailbox;
+        service.mAccount = account;
+        return service;
+    }
+
+    EasSyncService getTestService(Account account, Mailbox mailbox) {
+        EasSyncService service = new EasSyncService();
+        service.mContext = mMockContext;
         service.mMailbox = mailbox;
         service.mAccount = account;
         return service;
@@ -136,36 +152,35 @@
         ArrayList<Long> ids = new ArrayList<Long>();
         ArrayList<Long> deletedIds = new ArrayList<Long>();
 
-        Context context = mMockContext;
-        adapter.mContext = context;
-        final ContentResolver resolver = context.getContentResolver();
+        adapter.mContext = mMockContext;
 
         // Create account and two mailboxes
-        Account acct = ProviderTestUtils.setupAccount("account", true, context);
+        Account acct = ProviderTestUtils.setupAccount("account", true, mMockContext);
         adapter.mAccount = acct;
-        Mailbox box1 = ProviderTestUtils.setupMailbox("box1", acct.mId, true, context);
+        Mailbox box1 = ProviderTestUtils.setupMailbox("box1", acct.mId, true, mMockContext);
         adapter.mMailbox = box1;
 
         // Create 3 messages
-        Message msg1 =
-            ProviderTestUtils.setupMessage("message1", acct.mId, box1.mId, true, true, context);
+        Message msg1 = ProviderTestUtils.setupMessage("message1", acct.mId, box1.mId,
+                true, true, mMockContext);
         ids.add(msg1.mId);
-        Message msg2 =
-            ProviderTestUtils.setupMessage("message2", acct.mId, box1.mId, true, true, context);
+        Message msg2 = ProviderTestUtils.setupMessage("message2", acct.mId, box1.mId,
+                true, true, mMockContext);
         ids.add(msg2.mId);
-        Message msg3 =
-            ProviderTestUtils.setupMessage("message3", acct.mId, box1.mId, true, true, context);
+        Message msg3 = ProviderTestUtils.setupMessage("message3", acct.mId, box1.mId,
+                true, true, mMockContext);
         ids.add(msg3.mId);
-        assertEquals(3, EmailContent.count(context, Message.CONTENT_URI, null, null));
+        assertEquals(3, EmailContent.count(mMockContext, Message.CONTENT_URI, null, null));
 
         // Delete them
         for (long id: ids) {
-            resolver.delete(ContentUris.withAppendedId(Message.SYNCED_CONTENT_URI, id), null, null);
+            mMockResolver.delete(ContentUris.withAppendedId(Message.SYNCED_CONTENT_URI, id),
+                    null, null);
         }
 
         // Confirm that the messages are in the proper table
-        assertEquals(0, EmailContent.count(context, Message.CONTENT_URI, null, null));
-        assertEquals(3, EmailContent.count(context, Message.DELETED_CONTENT_URI, null, null));
+        assertEquals(0, EmailContent.count(mMockContext, Message.CONTENT_URI, null, null));
+        assertEquals(3, EmailContent.count(mMockContext, Message.DELETED_CONTENT_URI, null, null));
 
         // Call code to send deletions; the id's of the ones actually deleted will be in the
         // deletedIds list
@@ -176,15 +191,15 @@
         deletedIds.clear();
 
         // Create a new message
-        Message msg4 =
-            ProviderTestUtils.setupMessage("message3", acct.mId, box1.mId, true, true, context);
-        assertEquals(1, EmailContent.count(context, Message.CONTENT_URI, null, null));
+        Message msg4 = ProviderTestUtils.setupMessage("message4", acct.mId, box1.mId,
+                true, true, mMockContext);
+        assertEquals(1, EmailContent.count(mMockContext, Message.CONTENT_URI, null, null));
         // Find the body for this message
-        Body body = Body.restoreBodyWithMessageId(context, msg4.mId);
+        Body body = Body.restoreBodyWithMessageId(mMockContext, msg4.mId);
         // Set its source message to msg2's id
         ContentValues values = new ContentValues();
         values.put(Body.SOURCE_MESSAGE_KEY, msg2.mId);
-        body.update(context, values);
+        body.update(mMockContext, values);
 
         // Now send deletions again; this time only two should get deleted; msg2 should NOT be
         // deleted as it's referenced by msg4
@@ -192,4 +207,128 @@
         assertEquals(2, deletedIds.size());
         assertFalse(deletedIds.contains(msg2.mId));
     }
+
+    void setupSyncParserAndAdapter(Account account, Mailbox mailbox) throws IOException {
+        EasSyncService service = getTestService(account, mailbox);
+        mSyncAdapter = new EmailSyncAdapter(mailbox, service);
+        mSyncParser = mSyncAdapter.new EasEmailSyncParser(getTestInputStream(), mSyncAdapter);
+    }
+
+    ArrayList<Long> setupAccountMailboxAndMessages(int numMessages) {
+        ArrayList<Long> ids = new ArrayList<Long>();
+
+        // Create account and two mailboxes
+        mAccount = ProviderTestUtils.setupAccount("account", true, mMockContext);
+        mMailbox = ProviderTestUtils.setupMailbox("box1", mAccount.mId, true, mMockContext);
+
+        for (int i = 0; i < numMessages; i++) {
+            Message msg = ProviderTestUtils.setupMessage("message" + i, mAccount.mId, mMailbox.mId,
+                    true, true, mMockContext);
+            ids.add(msg.mId);
+        }
+
+        assertEquals(numMessages, EmailContent.count(mMockContext, Message.CONTENT_URI,
+                null, null));
+        return ids;
+    }
+
+    public void testDeleteParser() throws IOException {
+        // Setup some messages
+        ArrayList<Long> messageIds = setupAccountMailboxAndMessages(3);
+        ContentValues cv = new ContentValues();
+        cv.put(SyncColumns.SERVER_ID, "1:22");
+        long deleteMessageId = messageIds.get(1);
+        mMockResolver.update(ContentUris.withAppendedId(Message.CONTENT_URI, deleteMessageId), cv,
+                null, null);
+
+        // Setup our adapter and parser
+        setupSyncParserAndAdapter(mAccount, mMailbox);
+
+        // Set up an input stream with a delete command
+        Serializer s = new Serializer(false);
+        s.start(Tags.SYNC_DELETE).data(Tags.SYNC_SERVER_ID, "1:22").end().done();
+        byte[] bytes = s.toByteArray();
+        mSyncParser.resetInput(new ByteArrayInputStream(bytes));
+        mSyncParser.nextTag(0);
+
+        // Run the delete parser
+        ArrayList<Long> deleteList = new ArrayList<Long>();
+        mSyncParser.deleteParser(deleteList, Tags.SYNC_DELETE);
+        // It should have found the message
+        assertEquals(1, deleteList.size());
+        long id = deleteList.get(0);
+        // And the id's should match
+        assertEquals(deleteMessageId, id);
+    }
+
+    public void testChangeParser() throws IOException {
+        // Setup some messages
+        ArrayList<Long> messageIds = setupAccountMailboxAndMessages(3);
+        ContentValues cv = new ContentValues();
+        cv.put(SyncColumns.SERVER_ID, "1:22");
+        long changeMessageId = messageIds.get(1);
+        mMockResolver.update(ContentUris.withAppendedId(Message.CONTENT_URI, changeMessageId), cv,
+                null, null);
+
+        // Setup our adapter and parser
+        setupSyncParserAndAdapter(mAccount, mMailbox);
+
+        // Set up an input stream with a change command (marking 1:22 unread)
+        // Note that the test message creation code sets read to "true"
+        Serializer s = new Serializer(false);
+        s.start(Tags.SYNC_CHANGE).data(Tags.SYNC_SERVER_ID, "1:22");
+        s.start(Tags.SYNC_APPLICATION_DATA).data(Tags.EMAIL_READ, "0").end();
+        s.end().done();
+        byte[] bytes = s.toByteArray();
+        mSyncParser.resetInput(new ByteArrayInputStream(bytes));
+        mSyncParser.nextTag(0);
+
+        // Run the delete parser
+        ArrayList<ServerChange> changeList = new ArrayList<ServerChange>();
+        mSyncParser.changeParser(changeList);
+        // It should have found the message
+        assertEquals(1, changeList.size());
+        // And the id's should match
+        ServerChange change = changeList.get(0);
+        assertEquals(changeMessageId, change.id);
+        assertNotNull(change.read);
+        assertFalse(change.read);
+    }
+
+    public void testCleanup() throws IOException {
+        // Setup some messages
+        ArrayList<Long> messageIds = setupAccountMailboxAndMessages(3);
+        // Setup our adapter and parser
+        setupSyncParserAndAdapter(mAccount, mMailbox);
+
+        // Delete two of the messages, change one
+        long id = messageIds.get(0);
+        mMockResolver.delete(ContentUris.withAppendedId(Message.SYNCED_CONTENT_URI, id),
+                null, null);
+        mSyncAdapter.mDeletedIdList.add(id);
+        id = messageIds.get(1);
+        mMockResolver.delete(ContentUris.withAppendedId(Message.SYNCED_CONTENT_URI,
+                id), null, null);
+        mSyncAdapter.mDeletedIdList.add(id);
+        id = messageIds.get(2);
+        ContentValues cv = new ContentValues();
+        cv.put(Message.FLAG_READ, 0);
+        mMockResolver.update(ContentUris.withAppendedId(Message.SYNCED_CONTENT_URI,
+                id), cv, null, null);
+        mSyncAdapter.mUpdatedIdList.add(id);
+
+        // The changed message should still exist
+        assertEquals(1, EmailContent.count(mMockContext, Message.CONTENT_URI, null, null));
+
+        // As well, the two deletions and one update
+        assertEquals(2, EmailContent.count(mMockContext, Message.DELETED_CONTENT_URI, null, null));
+        assertEquals(1, EmailContent.count(mMockContext, Message.UPDATED_CONTENT_URI, null, null));
+
+        // Cleanup (i.e. after sync); should remove items from delete/update tables
+        mSyncAdapter.cleanup();
+
+        // The three should be gone
+        assertEquals(0, EmailContent.count(mMockContext, Message.DELETED_CONTENT_URI, null, null));
+        assertEquals(0, EmailContent.count(mMockContext, Message.UPDATED_CONTENT_URI, null, null));
+    }
 }