Work around problem w/ large CalendarProvider2 transactions

* We're seeing binder transaction failures when we try to send more than around
  1500 CPO's to CalendarProvider2 in a batch (the limit is related to memory
  usage in binder transactions)
* When an event has A attendees and E exceptions in an event, we currently must
  create A*E CPO's; this number can become very large and cause a binder failure
* The result of a failure is looping behavior, resulting in failed sync and very
  much reduced battery life
* The workaround here is to redact all non-organizer and non-user attendees from
  exceptions once we reach half of the maximum number of CPO's.  This has been
  determined empirically and is set to 500 CPO's in this CL
* We also reduce our sync "window" to 4 calendar/contact items per sync command
  to help limit the potential size of our batch
* For later releases, we should reconsider this and see if something that is more
  of a "fix", rather than a workaround, can be implemented

Bug: 2760514
Change-Id: I06ca1a1ae88c772342a9e46b5997c41678e95144
diff --git a/src/com/android/exchange/EasSyncService.java b/src/com/android/exchange/EasSyncService.java
index 82a375f..3eb48c6 100644
--- a/src/com/android/exchange/EasSyncService.java
+++ b/src/com/android/exchange/EasSyncService.java
@@ -105,7 +105,7 @@
     public static final boolean DEBUG_GAL_SERVICE = false;
 
     private static final String EMAIL_WINDOW_SIZE = "5";
-    public static final String PIM_WINDOW_SIZE = "5";
+    public static final String PIM_WINDOW_SIZE = "4";
     private static final String WHERE_ACCOUNT_KEY_AND_SERVER_ID =
         MailboxColumns.ACCOUNT_KEY + "=? and " + MailboxColumns.SERVER_ID + "=?";
     private static final String WHERE_ACCOUNT_AND_SYNC_INTERVAL_PING =
diff --git a/src/com/android/exchange/adapter/CalendarSyncAdapter.java b/src/com/android/exchange/adapter/CalendarSyncAdapter.java
index 9a4909b..cfefb67 100644
--- a/src/com/android/exchange/adapter/CalendarSyncAdapter.java
+++ b/src/com/android/exchange/adapter/CalendarSyncAdapter.java
@@ -139,11 +139,17 @@
 
     // Maximum number of allowed attendees; above this number, we mark the Event with the
     // attendeesRedacted extended property and don't allow the event to be upsynced to the server
-    private static final int MAX_ATTENDEES = 50;
+    private static final int MAX_SYNCED_ATTENDEES = 50;
     // We set the organizer to this when the user is the organizer and we've redacted the
     // attendee list.  By making the meeting organizer OTHER than the user, we cause the UI to
     // prevent edits to this event (except local changes like reminder).
     private static final String BOGUS_ORGANIZER_EMAIL = "upload_disallowed@uploadisdisallowed.aaa";
+    // Maximum number of CPO's before we start redacting attendees in exceptions
+    // The number 500 has been determined empirically; 1500 CPOs appears to be the limit before
+    // binder failures occur, but we need room at any point for additional events/exceptions so
+    // we set our limit at 1/3 of the apparent maximum for extra safety
+    // TODO Find a better solution to this workaround
+    private static final int MAX_OPS_BEFORE_EXCEPTION_ATTENDEE_REDACTION = 500;
 
     private long mCalendarId = -1;
     private String mCalendarIdString;
@@ -560,7 +566,7 @@
             // busyStatus is inherited from the parent unless it's specified in the exception)
             // Add the insert/update operation for each attendee (based on whether it's add/change)
             int numAttendees = attendeeValues.size();
-            if (numAttendees > MAX_ATTENDEES) {
+            if (numAttendees > MAX_SYNCED_ATTENDEES) {
                 // Indicate that we've redacted attendees.  If we're the organizer, disable edit
                 // by setting organizerEmail to a bogus value and by setting the upsync prohibited
                 // extended properly.
@@ -692,7 +698,7 @@
                 logEventColumns(cv, "DTEND/DURATION missing");
                 return false;
             // Exceptions require DTEND
-            } else if (isException && !cv.containsKey(Events.DTEND)) { 
+            } else if (isException && !cv.containsKey(Events.DTEND)) {
                 logEventColumns(cv, "Exception missing DTEND");
                 return false;
             // If this is a recurrence, we need a DURATION (in days if an all-day event)
@@ -853,21 +859,32 @@
             int exceptionStart = ops.mCount;
             ops.newException(cv);
             // Also add the attendees, because they need to be copied over from the parent event
+            boolean attendeesRedacted = false;
             if (attendeeValues != null) {
                 for (ContentValues attValues: attendeeValues) {
                     // If this is the user, use his busy status for attendee status
                     String attendeeEmail = attValues.getAsString(Attendees.ATTENDEE_EMAIL);
+                    // Note that the exception at which we surpass the redaction limit might have
+                    // any number of attendees shown; since this is an edge case and a workaround,
+                    // it seems to be an acceptable implementation
                     if (mEmailAddress.equalsIgnoreCase(attendeeEmail)) {
                         attValues.put(Attendees.ATTENDEE_STATUS,
                                 CalendarUtilities.attendeeStatusFromBusyStatus(busyStatus));
+                        ops.newAttendee(attValues, exceptionStart);
+                    } else if (ops.size() < MAX_OPS_BEFORE_EXCEPTION_ATTENDEE_REDACTION) {
+                        ops.newAttendee(attValues, exceptionStart);
+                    } else {
+                        attendeesRedacted = true;
                     }
-                    ops.newAttendee(attValues, exceptionStart);
                 }
             }
             // And add the parent's reminder value
             if (reminderMins > 0) {
                 ops.newReminder(reminderMins, exceptionStart);
             }
+            if (attendeesRedacted) {
+                mService.userLog("Attendees redacted in this exception");
+            }
         }
 
         private int encodeVisibility(int easVisibility) {
@@ -935,7 +952,7 @@
                         attendeeCount++;
                         // Allow one more than MAX_ATTENDEES, so that the check for "too many" will
                         // succeed in addEvent
-                        if (attendeeCount <= (MAX_ATTENDEES+1)) {
+                        if (attendeeCount <= (MAX_SYNCED_ATTENDEES+1)) {
                             attendeeValues.add(cv);
                         }
                         break;