Merge "When syncing folders, read all data first, then process" into honeycomb
diff --git a/src/com/android/exchange/EasSyncService.java b/src/com/android/exchange/EasSyncService.java
index 3b0c173..2a1560d 100644
--- a/src/com/android/exchange/EasSyncService.java
+++ b/src/com/android/exchange/EasSyncService.java
@@ -1883,6 +1883,7 @@
         ArrayList<String> readyMailboxes = new ArrayList<String>();
         ArrayList<String> notReadyMailboxes = new ArrayList<String>();
         int pingWaitCount = 0;
+        long inboxId = -1;
 
         while ((System.currentTimeMillis() < endTime) && !mStop) {
             // Count of pushable mailboxes
@@ -1898,6 +1899,10 @@
                     AND_FREQUENCY_PING_PUSH_AND_NOT_ACCOUNT_MAILBOX, null, null);
             notReadyMailboxes.clear();
             readyMailboxes.clear();
+            // Look for an inbox, and remember its id
+            if (inboxId == -1) {
+                inboxId = Mailbox.findMailboxOfType(mContext, mAccount.mId, Mailbox.TYPE_INBOX);
+            }
             try {
                 // Loop through our pushed boxes seeing what is available to push
                 while (c.moveToNext()) {
@@ -2082,6 +2087,9 @@
                 // we're in one of the other possible states.
                 userLog("pingLoop waiting for initial sync of ", uninitCount, " box(es)");
                 sleep(10*SECONDS, true);
+            } else if (inboxId == -1) {
+                // In this case, we're still syncing mailboxes, so sleep for only a short time
+                sleep(45*SECONDS, true);
             } else {
                 // We've got nothing to do, so we'll check again in 20 minutes at which time
                 // we'll update the folder list, check for policy changes and/or remote wipe, etc.
diff --git a/src/com/android/exchange/ExchangeService.java b/src/com/android/exchange/ExchangeService.java
index 3d1466b..e9e2af7 100644
--- a/src/com/android/exchange/ExchangeService.java
+++ b/src/com/android/exchange/ExchangeService.java
@@ -2336,7 +2336,7 @@
     }
 
     // DO NOT CALL THIS IN A LOOP ON THE SERVICEMAP
-    static private void stopManualSync(long mailboxId) {
+    static public void stopManualSync(long mailboxId) {
         ExchangeService exchangeService = INSTANCE;
         if (exchangeService == null) return;
         synchronized (sSyncLock) {
diff --git a/src/com/android/exchange/adapter/FolderSyncParser.java b/src/com/android/exchange/adapter/FolderSyncParser.java
index 0d36a0e..ae20fd9 100644
--- a/src/com/android/exchange/adapter/FolderSyncParser.java
+++ b/src/com/android/exchange/adapter/FolderSyncParser.java
@@ -20,11 +20,11 @@
 import com.android.email.Utility;
 import com.android.email.provider.AttachmentProvider;
 import com.android.email.provider.EmailContent;
-import com.android.email.provider.EmailProvider;
 import com.android.email.provider.EmailContent.Account;
 import com.android.email.provider.EmailContent.AccountColumns;
 import com.android.email.provider.EmailContent.Mailbox;
 import com.android.email.provider.EmailContent.MailboxColumns;
+import com.android.email.provider.EmailProvider;
 import com.android.exchange.Eas;
 import com.android.exchange.ExchangeService;
 import com.android.exchange.MockParserStream;
@@ -361,9 +361,9 @@
         }
     }
 
-    private void commitMailboxes(ArrayList<Mailbox> validMailboxes,
+    private boolean commitMailboxes(ArrayList<Mailbox> validMailboxes,
             ArrayList<Mailbox> userMailboxes, HashMap<String, Mailbox> mailboxMap,
-            ArrayList<ContentProviderOperation> ops) throws IOException {
+            ArrayList<ContentProviderOperation> ops) {
 
         // Go through the generic user mailboxes; we'll call them valid if any parent is valid
         for (Mailbox m: userMailboxes) {
@@ -388,44 +388,26 @@
         // If it IS repeatable, there's no good result, since the folder list will be invalid
         try {
             mContentResolver.applyBatch(EmailProvider.EMAIL_AUTHORITY, mOperations);
+            return true;
         } catch (RemoteException e) {
-            throw new IOException("RemoteException committing folders.");
+            userLog("RemoteException in commitMailboxes");
+            return false;
         } catch (OperationApplicationException e) {
-            throw new IOException("OperationApplicationException committing folders.");
+            userLog("OperationApplicationException in commitMailboxes");
+            return false;
         }
     }
 
-    public void changesParser(ArrayList<ContentProviderOperation> ops, boolean initialSync)
-            throws IOException {
-        // Mailboxes that we known contain email
-        ArrayList<Mailbox> validMailboxes = new ArrayList<Mailbox>();
-        // Mailboxes that we're unsure about
-        ArrayList<Mailbox> userMailboxes = new ArrayList<Mailbox>();
-        // Maps folder serverId to mailbox type
-        HashMap<String, Mailbox> mailboxMap = new HashMap<String, Mailbox>();
+    public void changesParser(final ArrayList<ContentProviderOperation> ops,
+            final boolean initialSync) throws IOException {
+        // Array of added mailboxes
+        final ArrayList<Mailbox> addMailboxes = new ArrayList<Mailbox>();
 
-        int mailboxAddCount = 0;
         while (nextTag(Tags.FOLDER_CHANGES) != END) {
             if (tag == Tags.FOLDER_ADD) {
                 Mailbox mailbox = addParser();
                 if (mailbox != null) {
-                    // Save away the type of this folder
-                    mailboxMap.put(mailbox.mServerId, mailbox);
-                    // And add the mailbox to the proper list
-                    if (type == USER_MAILBOX_TYPE) {
-                        userMailboxes.add(mailbox);
-                    } else {
-                        validMailboxes.add(mailbox);
-                    }
-                    // On initial sync, we commit what we have every 20 mailboxes
-                    if (initialSync && (++mailboxAddCount == MAILBOX_COMMIT_SIZE)) {
-                        commitMailboxes(validMailboxes, userMailboxes, mailboxMap, ops);
-                        // Clear our arrays to prepare for more
-                        userMailboxes.clear();
-                        validMailboxes.clear();
-                        ops.clear();
-                        mailboxAddCount = 0;
-                    }
+                    addMailboxes.add(mailbox);
                 }
             } else if (tag == Tags.FOLDER_DELETE) {
                 deleteParser(ops);
@@ -437,22 +419,53 @@
                 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;
-        }
-
-        // Commit the sync key and mailboxes
-        ContentValues cv = new ContentValues();
-        cv.put(AccountColumns.SYNC_KEY, mAccount.mSyncKey);
-        ops.add(ContentProviderOperation.newUpdate(
-                ContentUris.withAppendedId(Account.CONTENT_URI, mAccount.mId))
-                .withValues(cv)
-                .build());
-        commitMailboxes(validMailboxes, userMailboxes, mailboxMap, ops);
+        Utility.runAsync(new Runnable() {
+            @Override
+            public void run() {
+                // Synchronize on the parser to prevent this being run multiple times concurrently
+                // (an extremely unlikely event, but nonetheless possible)
+                synchronized (FolderSyncParser.this) {
+                    // Mailboxes that we known contain email
+                    ArrayList<Mailbox> validMailboxes = new ArrayList<Mailbox>();
+                    // Mailboxes that we're unsure about
+                    ArrayList<Mailbox> userMailboxes = new ArrayList<Mailbox>();
+                    // Maps folder serverId to mailbox type
+                    HashMap<String, Mailbox> mailboxMap = new HashMap<String, Mailbox>();
+                    int mailboxCommitCount = 0;
+                    for (Mailbox mailbox : addMailboxes) {
+                        // Save away the type of this folder
+                        mailboxMap.put(mailbox.mServerId, mailbox);
+                        // And add the mailbox to the proper list
+                        if (type == USER_MAILBOX_TYPE) {
+                            userMailboxes.add(mailbox);
+                        } else {
+                            validMailboxes.add(mailbox);
+                        }
+                        // On initial sync, we commit what we have every 20 mailboxes
+                        if (initialSync && (++mailboxCommitCount == MAILBOX_COMMIT_SIZE)) {
+                            if (!commitMailboxes(validMailboxes, userMailboxes, mailboxMap, ops)) {
+                                mService.stop();
+                                return;
+                            }
+                            // Clear our arrays to prepare for more
+                            userMailboxes.clear();
+                            validMailboxes.clear();
+                            ops.clear();
+                            mailboxCommitCount = 0;
+                        }
+                    }
+                    // Commit the sync key and mailboxes
+                    ContentValues cv = new ContentValues();
+                    cv.put(AccountColumns.SYNC_KEY, mAccount.mSyncKey);
+                    ops.add(ContentProviderOperation
+                            .newUpdate(
+                                    ContentUris.withAppendedId(Account.CONTENT_URI, mAccount.mId))
+                            .withValues(cv).build());
+                    if (!commitMailboxes(validMailboxes, userMailboxes, mailboxMap, ops)) {
+                        mService.stop();
+                    }
+                }
+            }});
     }
 
     /**