Improve handling of exception downsync

* We needed to copy some more columns, and also copy reminders
  and attendees to the Exception
* Also found a bug in which Description was referenced as a boolean
  rather than a String

Bug: 2511405
Change-Id: I485b8d44a6090f8190e681838d2141551227d8f3
diff --git a/src/com/android/exchange/adapter/CalendarSyncAdapter.java b/src/com/android/exchange/adapter/CalendarSyncAdapter.java
index 804e200..6e18c70 100644
--- a/src/com/android/exchange/adapter/CalendarSyncAdapter.java
+++ b/src/com/android/exchange/adapter/CalendarSyncAdapter.java
@@ -246,6 +246,11 @@
             long eventId = -1;
             long startTime = -1;
             long endTime = -1;
+
+            // Keep track of the attendees; exceptions will need them
+            ArrayList<ContentValues> attendeeValues = null;
+            int reminderMins = -1;
+
             while (nextTag(Tags.SYNC_APPLICATION_DATA) != END) {
                 if (update && firstTag) {
                     // Find the event that's being updated
@@ -292,7 +297,7 @@
                         break;
                     case Tags.CALENDAR_ATTENDEES:
                         // If eventId >= 0, this is an update; otherwise, a new Event
-                        attendeesParser(ops, eventId);
+                        attendeeValues = attendeesParser(ops, eventId);
                         break;
                     case Tags.BASE_BODY:
                         cv.put(Events.DESCRIPTION, bodyParser());
@@ -317,7 +322,7 @@
                         endTime = Utility.parseDateTimeToMillis(getValue());
                         break;
                     case Tags.CALENDAR_EXCEPTIONS:
-                        exceptionsParser(ops, cv);
+                        exceptionsParser(ops, cv, attendeeValues, reminderMins);
                         break;
                     case Tags.CALENDAR_LOCATION:
                         cv.put(Events.EVENT_LOCATION, getValue());
@@ -342,7 +347,8 @@
                         organizerName = getValue();
                         break;
                     case Tags.CALENDAR_REMINDER_MINS_BEFORE:
-                        ops.newReminder(getValueInt());
+                        reminderMins = getValueInt();
+                        ops.newReminder(reminderMins);
                         cv.put(Events.HAS_ALARM, 1);
                         break;
                     // The following are fields we should save (for changes), though they don't
@@ -496,8 +502,8 @@
                     dow, dom, wom, moy, until);
         }
 
-        private void exceptionParser(CalendarOperations ops, ContentValues parentCv)
-                throws IOException {
+        private void exceptionParser(CalendarOperations ops, ContentValues parentCv,
+                ArrayList<ContentValues> attendeeValues, int reminderMins) throws IOException {
             ContentValues cv = new ContentValues();
             cv.put(Events.CALENDAR_ID, mCalendarId);
             cv.put(Events._SYNC_ACCOUNT, mAccount.mEmailAddress);
@@ -507,8 +513,10 @@
             // Note that they can be overridden below
             cv.put(Events.ORGANIZER, parentCv.getAsString(Events.ORGANIZER));
             cv.put(Events.TITLE, parentCv.getAsString(Events.TITLE));
-            cv.put(Events.DESCRIPTION, parentCv.getAsBoolean(Events.DESCRIPTION));
+            cv.put(Events.DESCRIPTION, parentCv.getAsString(Events.DESCRIPTION));
             cv.put(Events.ORIGINAL_ALL_DAY, parentCv.getAsInteger(Events.ALL_DAY));
+            cv.put(Events.EVENT_LOCATION, parentCv.getAsString(Events.EVENT_LOCATION));
+            cv.put(Events.VISIBILITY, parentCv.getAsString(Events.VISIBILITY));
 
             // This column is the key that links the exception to the serverId
             // TODO Make sure calendar knows this isn't globally unique!!
@@ -587,10 +595,18 @@
             if (!cv.containsKey(Events.DTEND)) {
                 cv.put(Events.DTEND, parentCv.getAsLong(Events.DTEND));
             }
-            // TODO See if this is necessary
-            //cv.put(Events.LAST_DATE, cv.getAsLong(Events.DTEND));
 
+            // Add the exception insert
+            int exceptionStart = ops.mCount;
             ops.newException(cv);
+            // Also add the attendees, because they need to be copied over from the parent event
+            for (ContentValues attValues: attendeeValues) {
+                ops.newAttendee(attValues, exceptionStart);
+            }
+            // And add the parent's reminder value
+            if (reminderMins > 0) {
+                ops.newReminder(reminderMins, exceptionStart);
+            }
         }
 
         private int encodeVisibility(int easVisibility) {
@@ -612,12 +628,12 @@
             return visibility;
         }
 
-        private void exceptionsParser(CalendarOperations ops, ContentValues cv)
-                throws IOException {
+        private void exceptionsParser(CalendarOperations ops, ContentValues cv,
+                ArrayList<ContentValues> attendeeValues, int reminderMins) throws IOException {
             while (nextTag(Tags.CALENDAR_EXCEPTIONS) != END) {
                 switch (tag) {
                     case Tags.CALENDAR_EXCEPTION:
-                        exceptionParser(ops, cv);
+                        exceptionParser(ops, cv, attendeeValues, reminderMins);
                         break;
                     default:
                         skipTag();
@@ -642,19 +658,23 @@
             return categories.toString();
         }
 
-        private void attendeesParser(CalendarOperations ops, long eventId) throws IOException {
+        private ArrayList<ContentValues> attendeesParser(CalendarOperations ops, long eventId)
+                throws IOException {
+            ArrayList<ContentValues> attendeeValues = new ArrayList<ContentValues>();
             while (nextTag(Tags.CALENDAR_ATTENDEES) != END) {
                 switch (tag) {
                     case Tags.CALENDAR_ATTENDEE:
-                        attendeeParser(ops, eventId);
+                        attendeeValues.add(attendeeParser(ops, eventId));
                         break;
                     default:
                         skipTag();
                 }
             }
+            return attendeeValues;
         }
 
-        private void attendeeParser(CalendarOperations ops, long eventId) throws IOException {
+        private ContentValues attendeeParser(CalendarOperations ops, long eventId)
+                throws IOException {
             ContentValues cv = new ContentValues();
             while (nextTag(Tags.CALENDAR_ATTENDEE) != END) {
                 switch (tag) {
@@ -696,6 +716,7 @@
             } else {
                 ops.updatedAttendee(cv, eventId);
             }
+            return cv;
         }
 
         private String bodyParser() throws IOException {
@@ -966,10 +987,14 @@
         }
 
         public void newAttendee(ContentValues cv) {
+            newAttendee(cv, mEventStart);
+        }
+
+        public void newAttendee(ContentValues cv, int eventStart) {
             add(ContentProviderOperation
                     .newInsert(sAttendeesUri)
                     .withValues(cv)
-                    .withValueBackReference(Attendees.EVENT_ID, mEventStart)
+                    .withValueBackReference(Attendees.EVENT_ID, eventStart)
                     .build());
         }
 
@@ -991,15 +1016,19 @@
                     .build());
         }
 
-        public void newReminder(int mins) {
+        public void newReminder(int mins, int eventStart) {
             add(ContentProviderOperation
                     .newInsert(sRemindersUri)
                     .withValue(Reminders.MINUTES, mins)
                     .withValue(Reminders.METHOD, Reminders.METHOD_DEFAULT)
-                    .withValueBackReference(ExtendedProperties.EVENT_ID, mEventStart)
+                    .withValueBackReference(ExtendedProperties.EVENT_ID, eventStart)
                     .build());
         }
 
+        public void newReminder(int mins) {
+            newReminder(mins, mEventStart);
+        }
+
         public void delete(long id) {
             add(ContentProviderOperation
                     .newDelete(ContentUris.withAppendedId(sEventsUri, id)).build());