Apply contact save ops before we reach the max batch op threshold

In order to test the special case handled by this CL, you may want
to use a contact consisting of at least 23 contacts.

Bug: 22986058
Change-Id: Ic141a7b52907be930d898d1710e95aa5ffe9be8a
diff --git a/src/com/android/contacts/ContactSaveService.java b/src/com/android/contacts/ContactSaveService.java
index 12ae150..d014087 100755
--- a/src/com/android/contacts/ContactSaveService.java
+++ b/src/com/android/contacts/ContactSaveService.java
@@ -56,7 +56,6 @@
 import com.android.contacts.common.model.RawContactModifier;
 import com.android.contacts.common.model.account.AccountWithDataSet;
 import com.android.contacts.common.util.PermissionsUtil;
-import com.android.contacts.editor.ContactEditorFragment;
 import com.android.contacts.util.ContactPhotoUtils;
 
 import com.google.common.collect.Lists;
@@ -375,6 +374,7 @@
         Uri lookupUri = null;
 
         final ContentResolver resolver = getContentResolver();
+
         boolean succeeded = false;
 
         // Keep track of the id of a newly raw-contact (if any... there can be at most one).
@@ -386,6 +386,7 @@
             try {
                 // Build operations and try applying
                 final ArrayList<ContentProviderOperation> diff = state.buildDiff();
+
                 if (DEBUG) {
                     Log.v(TAG, "Content Provider Operations:");
                     for (ContentProviderOperation operation : diff) {
@@ -393,16 +394,25 @@
                     }
                 }
 
-                ContentProviderResult[] results = null;
-                if (!diff.isEmpty()) {
-                    results = resolver.applyBatch(ContactsContract.AUTHORITY, diff);
-                    if (results == null) {
+                int numberProcessed = 0;
+                boolean batchFailed = false;
+                final ContentProviderResult[] results = new ContentProviderResult[diff.size()];
+                while (numberProcessed < diff.size()) {
+                    final int subsetCount = applyDiffSubset(diff, numberProcessed, results, resolver);
+                    if (subsetCount == -1) {
                         Log.w(TAG, "Resolver.applyBatch failed in saveContacts");
-                        // Retry save
-                        continue;
+                        batchFailed = true;
+                        break;
+                    } else {
+                        numberProcessed += subsetCount;
                     }
                 }
 
+                if (batchFailed) {
+                    // Retry save
+                    continue;
+                }
+
                 final long rawContactId = getRawContactId(state, diff, results);
                 if (rawContactId == -1) {
                     throw new IllegalStateException("Could not determine RawContact ID after save");
@@ -526,6 +536,29 @@
     }
 
     /**
+     * Splits "diff" into subsets based on "MAX_CONTACTS_PROVIDER_BATCH_SIZE", applies each of the
+     * subsets, adds the returned array to "results".
+     *
+     * @return the size of the array, if not null; -1 when the array is null.
+     */
+    private int applyDiffSubset(ArrayList<ContentProviderOperation> diff, int offset,
+            ContentProviderResult[] results, ContentResolver resolver)
+            throws RemoteException, OperationApplicationException {
+        final int subsetCount = Math.min(diff.size() - offset, MAX_CONTACTS_PROVIDER_BATCH_SIZE);
+        final ArrayList<ContentProviderOperation> subset = new ArrayList<>();
+        subset.addAll(diff.subList(offset, offset + subsetCount));
+        final ContentProviderResult[] subsetResult = resolver.applyBatch(ContactsContract
+                .AUTHORITY, subset);
+        if (subsetResult == null || (offset + subsetResult.length) > results.length) {
+            return -1;
+        }
+        for (ContentProviderResult c : subsetResult) {
+            results[offset++] = c;
+        }
+        return subsetResult.length;
+    }
+
+    /**
      * Save updated photo for the specified raw-contact.
      * @return true for success, false for failure
      */