Fix stack overflow exception

Bug: 7304064
Change-Id: I795878ee11d681d7020ba9a9b25e06ba807c0107
diff --git a/exchange2/src/com/android/exchange/adapter/EmailSyncAdapter.java b/exchange2/src/com/android/exchange/adapter/EmailSyncAdapter.java
index 3e1ff68..d795475 100644
--- a/exchange2/src/com/android/exchange/adapter/EmailSyncAdapter.java
+++ b/exchange2/src/com/android/exchange/adapter/EmailSyncAdapter.java
@@ -76,6 +76,7 @@
 import java.util.ArrayList;
 import java.util.Calendar;
 import java.util.GregorianCalendar;
+import java.util.List;
 import java.util.TimeZone;
 
 /**
@@ -110,6 +111,8 @@
 
     private static final String EMAIL_WINDOW_SIZE = "5";
 
+    private static final int MAX_NUM_FETCH_SIZE_REDUCTIONS = 5;
+
     @VisibleForTesting
     static final int LAST_VERB_REPLY = 1;
     @VisibleForTesting
@@ -1116,9 +1119,10 @@
             commitImpl(0);
         }
 
-        public void commitImpl(int tryCount) {
+        public void commitImpl(final int tryCount) {
             // Use a batch operation to handle the changes
-            ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
+            final ArrayList<ContentProviderOperation> ops =
+                    new ArrayList<ContentProviderOperation>();
 
             // Maximum size of message text per fetch
             int numFetched = fetchedEmails.size();
@@ -1167,6 +1171,10 @@
                             .withSelection(Body.MESSAGE_KEY + "=?", mBindArgument)
                             .withValue(Body.TEXT_CONTENT, msg.mText)
                             .build());
+                    ops.add(ContentProviderOperation.newUpdate(Body.CONTENT_URI)
+                            .withSelection(Body.MESSAGE_KEY + "=?", mBindArgument)
+                            .withValue(Body.HTML_CONTENT, msg.mHtml)
+                            .build());
                     ops.add(ContentProviderOperation.newUpdate(Message.CONTENT_URI)
                             .withSelection(EmailContent.RECORD_ID + "=?", mBindArgument)
                             .withValue(Message.FLAG_LOADED, Message.FLAG_LOADED_COMPLETE)
@@ -1219,7 +1227,16 @@
                     userLog(mMailbox.mDisplayName, " SyncKey saved as: ", mMailbox.mSyncKey);
                 } catch (TransactionTooLargeException e) {
                     Log.w(TAG, "Transaction failed on fetched message; retrying...");
-                    commitImpl(++tryCount);
+
+                    if (tryCount <= MAX_NUM_FETCH_SIZE_REDUCTIONS || ops.size() == 1) {
+                        // We haven't tried reducing the message body size enough yet. Try this
+                        // commit again.
+                        commitImpl(tryCount + 1);
+                    } else {
+                        // We have tried too many time to with just reducing the fetch size.
+                        // Try applying the batch operations in smaller chunks
+                        applyBatchOperations(ops);
+                    }
                 } catch (RemoteException e) {
                     // There is nothing to be done here; fail by returning null
                 } catch (OperationApplicationException e) {
@@ -1229,6 +1246,57 @@
         }
     }
 
+    private void applyBatchOperations(List<ContentProviderOperation> operations) {
+        // Assume that since this method is being called, we want to break this batch up in chunks
+        // First assume that we want to take the list and do it in two chunks
+        int numberOfBatches = 2;
+
+        // Make a copy of the operation list
+        final List<ContentProviderOperation> remainingOperations = new ArrayList(operations);
+
+        // determin the batch size
+        int batchSize = remainingOperations.size() / numberOfBatches;
+        try {
+            while (remainingOperations.size() > 0) {
+                // Ensure that batch size is smaller than the list size
+                if (batchSize > remainingOperations.size()) {
+                    batchSize = remainingOperations.size();
+                }
+
+                final List<ContentProviderOperation> workingBatch;
+                // If the batch size and the list size is the same, just use the whole list
+                if (batchSize == remainingOperations.size()) {
+                    workingBatch = remainingOperations;
+                } else {
+                    // Get the sublist of of the remaining batch that contains only the batch size
+                    workingBatch = remainingOperations.subList(0, batchSize - 1);
+                }
+
+                try {
+                    // This is a waste, but ContentResolver#applyBatch requies an ArrayList, but
+                    // List#subList retuns only a List
+                    final ArrayList<ContentProviderOperation> batch = new ArrayList(workingBatch);
+                    mContentResolver.applyBatch(EmailContent.AUTHORITY, batch);
+
+                    // We successfully applied that batch.  Remove it from the remaining work
+                    workingBatch.clear();
+                } catch (TransactionTooLargeException e) {
+                    if (batchSize == 1) {
+                        Log.w(TAG, "Transaction failed applying batch. smallest possible batch.");
+                        throw e;
+                    }
+                    Log.w(TAG, "Transaction failed applying batch. trying smaller size...");
+                    numberOfBatches++;
+                    batchSize = remainingOperations.size() / numberOfBatches;
+                }
+            }
+        } 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
+        }
+    }
+
     @Override
     public String getCollectionName() {
         return "Email";