1de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdonpackage com.android.exchange.adapter;
2de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
3de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdonimport android.content.ContentProviderOperation;
4de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdonimport android.content.ContentProviderResult;
5de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdonimport android.content.ContentResolver;
6de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdonimport android.content.ContentUris;
7de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdonimport android.content.ContentValues;
8de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdonimport android.content.Context;
9de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdonimport android.content.OperationApplicationException;
10de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdonimport android.database.Cursor;
11de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdonimport android.net.Uri;
12de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdonimport android.os.RemoteException;
13de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdonimport android.os.TransactionTooLargeException;
14de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdonimport android.provider.CalendarContract;
15de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdonimport android.provider.CalendarContract.Attendees;
16de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdonimport android.provider.CalendarContract.Calendars;
17de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdonimport android.provider.CalendarContract.Events;
18de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdonimport android.provider.CalendarContract.ExtendedProperties;
19de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdonimport android.provider.CalendarContract.Reminders;
20de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdonimport android.provider.CalendarContract.SyncState;
21110837ebff288a75f9bda067c38e2c46797d99b5Alon Albertimport android.provider.SyncStateContract;
22de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdonimport android.text.format.DateUtils;
23de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
24de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdonimport com.android.emailcommon.provider.Account;
25de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdonimport com.android.emailcommon.provider.Mailbox;
26de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdonimport com.android.emailcommon.utility.Utility;
27de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdonimport com.android.exchange.Eas;
28de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdonimport com.android.exchange.adapter.AbstractSyncAdapter.Operation;
2928f3556f92b20b2efb800c1d95c29b04a3bbbb4fMartin Hibdonimport com.android.exchange.service.EasCalendarSyncHandler;
30de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdonimport com.android.exchange.utility.CalendarUtilities;
31de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdonimport com.android.mail.utils.LogUtils;
32de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdonimport com.google.common.annotations.VisibleForTesting;
33de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
34de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdonimport java.io.IOException;
35de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdonimport java.io.InputStream;
36de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdonimport java.util.ArrayList;
37de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdonimport java.util.GregorianCalendar;
38de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdonimport java.util.Map.Entry;
39110837ebff288a75f9bda067c38e2c46797d99b5Alon Albertimport java.util.TimeZone;
40de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
41de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdonpublic class CalendarSyncParser extends AbstractSyncParser {
42110837ebff288a75f9bda067c38e2c46797d99b5Alon Albert    private static final String TAG = Eas.LOG_TAG;
43de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
44de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    private final TimeZone UTC_TIMEZONE = TimeZone.getTimeZone("UTC");
45de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    private final TimeZone mLocalTimeZone = TimeZone.getDefault();
46de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
47de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    private final long mCalendarId;
48de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    private final android.accounts.Account mAccountManagerAccount;
49de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    private final Uri mAsSyncAdapterAttendees;
50de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    private final Uri mAsSyncAdapterEvents;
51de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
52de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    private final String[] mBindArgument = new String[1];
53de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    private final CalendarOperations mOps;
54de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
55de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
56de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    private static final String EVENT_SAVED_TIMEZONE_COLUMN = Events.SYNC_DATA1;
57de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    // Since exceptions will have the same _SYNC_ID as the original event we have to check that
58de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    // there's no original event when finding an item by _SYNC_ID
59de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    private static final String SERVER_ID_AND_CALENDAR_ID = Events._SYNC_ID + "=? AND " +
60de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        Events.ORIGINAL_SYNC_ID + " ISNULL AND " + Events.CALENDAR_ID + "=?";
61de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    private static final String CLIENT_ID_SELECTION = Events.SYNC_DATA2 + "=?";
62de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    private static final String ATTENDEES_EXCEPT_ORGANIZER = Attendees.EVENT_ID + "=? AND " +
63de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        Attendees.ATTENDEE_RELATIONSHIP + "!=" + Attendees.RELATIONSHIP_ORGANIZER;
64de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    private static final String[] ID_PROJECTION = new String[] {Events._ID};
65de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    private static final String EVENT_ID_AND_NAME =
66de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        ExtendedProperties.EVENT_ID + "=? AND " + ExtendedProperties.NAME + "=?";
67de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
68de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    private static final String[] EXTENDED_PROPERTY_PROJECTION =
69de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        new String[] {ExtendedProperties._ID};
70de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    private static final int EXTENDED_PROPERTY_ID = 0;
71de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
72de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    private static final String CATEGORY_TOKENIZER_DELIMITER = "\\";
73de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    private static final String ATTENDEE_TOKENIZER_DELIMITER = CATEGORY_TOKENIZER_DELIMITER;
74de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
75de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    private static final String EXTENDED_PROPERTY_USER_ATTENDEE_STATUS = "userAttendeeStatus";
76de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    private static final String EXTENDED_PROPERTY_ATTENDEES = "attendees";
77de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    private static final String EXTENDED_PROPERTY_DTSTAMP = "dtstamp";
78de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    private static final String EXTENDED_PROPERTY_MEETING_STATUS = "meeting_status";
79de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    private static final String EXTENDED_PROPERTY_CATEGORIES = "categories";
80de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    // Used to indicate that we removed the attendee list because it was too large
81de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    private static final String EXTENDED_PROPERTY_ATTENDEES_REDACTED = "attendeesRedacted";
82de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    // Used to indicate that upsyncs aren't allowed (we catch this in sendLocalChanges)
83de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    private static final String EXTENDED_PROPERTY_UPSYNC_PROHIBITED = "upsyncProhibited";
84de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
85de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    private static final Operation PLACEHOLDER_OPERATION =
86de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        new Operation(ContentProviderOperation.newInsert(Uri.EMPTY));
87de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
88de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    private static final long SEPARATOR_ID = Long.MAX_VALUE;
89de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
90de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    // Maximum number of allowed attendees; above this number, we mark the Event with the
91de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    // attendeesRedacted extended property and don't allow the event to be upsynced to the server
92de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    private static final int MAX_SYNCED_ATTENDEES = 50;
93de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    // We set the organizer to this when the user is the organizer and we've redacted the
94de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    // attendee list.  By making the meeting organizer OTHER than the user, we cause the UI to
95de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    // prevent edits to this event (except local changes like reminder).
96de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    private static final String BOGUS_ORGANIZER_EMAIL = "upload_disallowed@uploadisdisallowed.aaa";
97de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    // Maximum number of CPO's before we start redacting attendees in exceptions
98de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    // The number 500 has been determined empirically; 1500 CPOs appears to be the limit before
99de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    // binder failures occur, but we need room at any point for additional events/exceptions so
100de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    // we set our limit at 1/3 of the apparent maximum for extra safety
101de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    // TODO Find a better solution to this workaround
102de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    private static final int MAX_OPS_BEFORE_EXCEPTION_ATTENDEE_REDACTION = 500;
103de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
104de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    public CalendarSyncParser(final Context context, final ContentResolver resolver,
105de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            final InputStream in, final Mailbox mailbox, final Account account,
106de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            final android.accounts.Account accountManagerAccount,
107de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            final long calendarId) throws IOException {
108de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        super(context, resolver, in, mailbox, account);
109de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        mAccountManagerAccount = accountManagerAccount;
110de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        mCalendarId = calendarId;
111de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        mAsSyncAdapterAttendees = asSyncAdapter(Attendees.CONTENT_URI,
112de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                mAccount.mEmailAddress, Eas.EXCHANGE_ACCOUNT_MANAGER_TYPE);
113de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        mAsSyncAdapterEvents = asSyncAdapter(Events.CONTENT_URI,
114de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                mAccount.mEmailAddress, Eas.EXCHANGE_ACCOUNT_MANAGER_TYPE);
115de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        mOps = new CalendarOperations(resolver, mAsSyncAdapterAttendees, mAsSyncAdapterEvents,
116de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                asSyncAdapter(Reminders.CONTENT_URI, mAccount.mEmailAddress,
117de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                        Eas.EXCHANGE_ACCOUNT_MANAGER_TYPE),
118de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                asSyncAdapter(ExtendedProperties.CONTENT_URI, mAccount.mEmailAddress,
119de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                        Eas.EXCHANGE_ACCOUNT_MANAGER_TYPE));
120de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    }
121de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
122de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    protected static class CalendarOperations extends ArrayList<Operation> {
123de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        private static final long serialVersionUID = 1L;
124de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        public int mCount = 0;
125de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        private int mEventStart = 0;
126de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        private final ContentResolver mContentResolver;
127de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        private final Uri mAsSyncAdapterAttendees;
128de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        private final Uri mAsSyncAdapterEvents;
129de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        private final Uri mAsSyncAdapterReminders;
130de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        private final Uri mAsSyncAdapterExtendedProperties;
131de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
132de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        public CalendarOperations(final ContentResolver contentResolver,
133de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                final Uri asSyncAdapterAttendees, final Uri asSyncAdapterEvents,
134de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                final Uri asSyncAdapterReminders, final Uri asSyncAdapterExtendedProperties) {
135de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            mContentResolver = contentResolver;
136de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            mAsSyncAdapterAttendees = asSyncAdapterAttendees;
137de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            mAsSyncAdapterEvents = asSyncAdapterEvents;
138de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            mAsSyncAdapterReminders = asSyncAdapterReminders;
139de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            mAsSyncAdapterExtendedProperties = asSyncAdapterExtendedProperties;
140de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        }
141de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
142de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        @Override
143de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        public boolean add(Operation op) {
144de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            super.add(op);
145de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            mCount++;
146de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            return true;
147de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        }
148de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
149de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        public int newEvent(Operation op) {
150de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            mEventStart = mCount;
151de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            add(op);
152de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            return mEventStart;
153de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        }
154de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
155de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        public int newDelete(long id, String serverId) {
156de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            int offset = mCount;
157de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            delete(id, serverId);
158de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            return offset;
159de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        }
160de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
161de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        public void newAttendee(ContentValues cv) {
162de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            newAttendee(cv, mEventStart);
163de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        }
164de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
165de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        public void newAttendee(ContentValues cv, int eventStart) {
166de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            add(new Operation(ContentProviderOperation.newInsert(mAsSyncAdapterAttendees)
167de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    .withValues(cv),
168de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    Attendees.EVENT_ID,
169de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    eventStart));
170de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        }
171de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
172de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        public void updatedAttendee(ContentValues cv, long id) {
173de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            cv.put(Attendees.EVENT_ID, id);
174de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            add(new Operation(ContentProviderOperation.newInsert(mAsSyncAdapterAttendees)
175de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    .withValues(cv)));
176de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        }
177de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
178de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        public void newException(ContentValues cv) {
179de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            add(new Operation(ContentProviderOperation.newInsert(mAsSyncAdapterEvents)
180de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    .withValues(cv)));
181de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        }
182de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
183de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        public void newExtendedProperty(String name, String value) {
184de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            add(new Operation(ContentProviderOperation.newInsert(mAsSyncAdapterExtendedProperties)
185de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    .withValue(ExtendedProperties.NAME, name)
186de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    .withValue(ExtendedProperties.VALUE, value),
187de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    ExtendedProperties.EVENT_ID,
188de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    mEventStart));
189de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        }
190de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
191de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        public void updatedExtendedProperty(String name, String value, long id) {
192de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            // Find an existing ExtendedProperties row for this event and property name
193de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            Cursor c = mContentResolver.query(ExtendedProperties.CONTENT_URI,
194de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    EXTENDED_PROPERTY_PROJECTION, EVENT_ID_AND_NAME,
195de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    new String[] {Long.toString(id), name}, null);
196de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            long extendedPropertyId = -1;
197de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            // If there is one, capture its _id
198de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            if (c != null) {
199de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                try {
200de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    if (c.moveToFirst()) {
201de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                        extendedPropertyId = c.getLong(EXTENDED_PROPERTY_ID);
202de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    }
203de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                } finally {
204de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    c.close();
205de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                }
206de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            }
207de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            // Either do an update or an insert, depending on whether one
208de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            // already exists
209de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            if (extendedPropertyId >= 0) {
210de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                add(new Operation(ContentProviderOperation
211de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                        .newUpdate(
212de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                                ContentUris.withAppendedId(mAsSyncAdapterExtendedProperties,
213de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                                        extendedPropertyId))
214de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                        .withValue(ExtendedProperties.VALUE, value)));
215de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            } else {
216de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                newExtendedProperty(name, value);
217de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            }
218de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        }
219de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
220de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        public void newReminder(int mins, int eventStart) {
221de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            add(new Operation(ContentProviderOperation.newInsert(mAsSyncAdapterReminders)
222de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    .withValue(Reminders.MINUTES, mins)
223de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    .withValue(Reminders.METHOD, Reminders.METHOD_ALERT),
224de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    ExtendedProperties.EVENT_ID,
225de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    eventStart));
226de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        }
227de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
228de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        public void newReminder(int mins) {
229de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            newReminder(mins, mEventStart);
230de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        }
231de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
232de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        public void delete(long id, String syncId) {
233de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            add(new Operation(ContentProviderOperation.newDelete(
234de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    ContentUris.withAppendedId(mAsSyncAdapterEvents, id))));
235de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            // Delete the exceptions for this Event (CalendarProvider doesn't do this)
236de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            add(new Operation(ContentProviderOperation
237de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    .newDelete(mAsSyncAdapterEvents)
238de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    .withSelection(Events.ORIGINAL_SYNC_ID + "=?", new String[] {syncId})));
239de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        }
240de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    }
241de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
242de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    private static Uri asSyncAdapter(Uri uri, String account, String accountType) {
243de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        return uri.buildUpon().appendQueryParameter(CalendarContract.CALLER_IS_SYNCADAPTER, "true")
244de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                .appendQueryParameter(Calendars.ACCOUNT_NAME, account)
245de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                .appendQueryParameter(Calendars.ACCOUNT_TYPE, accountType).build();
246de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    }
247de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
248de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    private static void addOrganizerToAttendees(CalendarOperations ops, long eventId,
249de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            String organizerName, String organizerEmail) {
250de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        // Handle the organizer (who IS an attendee on device, but NOT in EAS)
251de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        if (organizerName != null || organizerEmail != null) {
252de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            ContentValues attendeeCv = new ContentValues();
253de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            if (organizerName != null) {
254de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                attendeeCv.put(Attendees.ATTENDEE_NAME, organizerName);
255de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            }
256de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            if (organizerEmail != null) {
257de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                attendeeCv.put(Attendees.ATTENDEE_EMAIL, organizerEmail);
258de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            }
259de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            attendeeCv.put(Attendees.ATTENDEE_RELATIONSHIP, Attendees.RELATIONSHIP_ORGANIZER);
260de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            attendeeCv.put(Attendees.ATTENDEE_TYPE, Attendees.TYPE_REQUIRED);
261de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            attendeeCv.put(Attendees.ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_ACCEPTED);
262de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            if (eventId < 0) {
263de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                ops.newAttendee(attendeeCv);
264de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            } else {
265de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                ops.updatedAttendee(attendeeCv, eventId);
266de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            }
267de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        }
268de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    }
269de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
270de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    /**
271de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon     * Set DTSTART, DTEND, DURATION and EVENT_TIMEZONE as appropriate for the given Event
272de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon     * The follow rules are enforced by CalendarProvider2:
273de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon     *   Events that aren't exceptions MUST have either 1) a DTEND or 2) a DURATION
274de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon     *   Recurring events (i.e. events with RRULE) must have a DURATION
275de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon     *   All-day recurring events MUST have a DURATION that is in the form P<n>D
276de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon     *   Other events MAY have a DURATION in any valid form (we use P<n>M)
277de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon     *   All-day events MUST have hour, minute, and second = 0; in addition, they must have
278de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon     *   the EVENT_TIMEZONE set to UTC
279de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon     *   Also, exceptions to all-day events need to have an ORIGINAL_INSTANCE_TIME that has
280de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon     *   hour, minute, and second = 0 and be set in UTC
281de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon     * @param cv the ContentValues for the Event
282de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon     * @param startTime the start time for the Event
283de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon     * @param endTime the end time for the Event
284de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon     * @param allDayEvent whether this is an all day event (1) or not (0)
285de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon     */
286de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    /*package*/ void setTimeRelatedValues(ContentValues cv, long startTime, long endTime,
287de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            int allDayEvent) {
288de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        // If there's no startTime, the event will be found to be invalid, so return
289de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        if (startTime < 0) return;
290de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        // EAS events can arrive without an end time, but CalendarProvider requires them
291de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        // so we'll default to 30 minutes; this will be superceded if this is an all-day event
292de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        if (endTime < 0) endTime = startTime + (30 * DateUtils.MINUTE_IN_MILLIS);
293de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
294de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        // If this is an all-day event, set hour, minute, and second to zero, and use UTC
295de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        if (allDayEvent != 0) {
296de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            startTime = CalendarUtilities.getUtcAllDayCalendarTime(startTime, mLocalTimeZone);
297de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            endTime = CalendarUtilities.getUtcAllDayCalendarTime(endTime, mLocalTimeZone);
298de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            String originalTimeZone = cv.getAsString(Events.EVENT_TIMEZONE);
299de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            cv.put(EVENT_SAVED_TIMEZONE_COLUMN, originalTimeZone);
300de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            cv.put(Events.EVENT_TIMEZONE, UTC_TIMEZONE.getID());
301de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        }
302de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
303de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        // If this is an exception, and the original was an all-day event, make sure the
304de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        // original instance time has hour, minute, and second set to zero, and is in UTC
305de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        if (cv.containsKey(Events.ORIGINAL_INSTANCE_TIME) &&
306de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                cv.containsKey(Events.ORIGINAL_ALL_DAY)) {
307de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            Integer ade = cv.getAsInteger(Events.ORIGINAL_ALL_DAY);
308de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            if (ade != null && ade != 0) {
309de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                long exceptionTime = cv.getAsLong(Events.ORIGINAL_INSTANCE_TIME);
310de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                final GregorianCalendar cal = new GregorianCalendar(UTC_TIMEZONE);
311de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                exceptionTime = CalendarUtilities.getUtcAllDayCalendarTime(exceptionTime,
312de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                        mLocalTimeZone);
313de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                cal.setTimeInMillis(exceptionTime);
314de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                cal.set(GregorianCalendar.HOUR_OF_DAY, 0);
315de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                cal.set(GregorianCalendar.MINUTE, 0);
316de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                cal.set(GregorianCalendar.SECOND, 0);
317de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                cv.put(Events.ORIGINAL_INSTANCE_TIME, cal.getTimeInMillis());
318de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            }
319de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        }
320de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
321de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        // Always set DTSTART
322de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        cv.put(Events.DTSTART, startTime);
323de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        // For recurring events, set DURATION.  Use P<n>D format for all day events
324de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        if (cv.containsKey(Events.RRULE)) {
325de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            if (allDayEvent != 0) {
326de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                cv.put(Events.DURATION, "P" + ((endTime - startTime) / DateUtils.DAY_IN_MILLIS) + "D");
327de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            }
328de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            else {
329de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                cv.put(Events.DURATION, "P" + ((endTime - startTime) / DateUtils.MINUTE_IN_MILLIS) + "M");
330de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            }
331de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        // For other events, set DTEND and LAST_DATE
332de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        } else {
333de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            cv.put(Events.DTEND, endTime);
334de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            cv.put(Events.LAST_DATE, endTime);
335de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        }
336de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    }
337de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
338de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    public void addEvent(CalendarOperations ops, String serverId, boolean update)
339de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            throws IOException {
340de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        ContentValues cv = new ContentValues();
341de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        cv.put(Events.CALENDAR_ID, mCalendarId);
342de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        cv.put(Events._SYNC_ID, serverId);
343de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        cv.put(Events.HAS_ATTENDEE_DATA, 1);
344de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        cv.put(Events.SYNC_DATA2, "0");
345de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
346de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        int allDayEvent = 0;
347de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        String organizerName = null;
348de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        String organizerEmail = null;
349de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        int eventOffset = -1;
350de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        int deleteOffset = -1;
351de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        int busyStatus = CalendarUtilities.BUSY_STATUS_TENTATIVE;
352de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        int responseType = CalendarUtilities.RESPONSE_TYPE_NONE;
353de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
354de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        boolean firstTag = true;
355de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        long eventId = -1;
356de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        long startTime = -1;
357de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        long endTime = -1;
358de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        TimeZone timeZone = null;
359de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
360de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        // Keep track of the attendees; exceptions will need them
361de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        ArrayList<ContentValues> attendeeValues = new ArrayList<ContentValues>();
362de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        int reminderMins = -1;
363de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        String dtStamp = null;
364de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        boolean organizerAdded = false;
365de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
366de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        while (nextTag(Tags.SYNC_APPLICATION_DATA) != END) {
367de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            if (update && firstTag) {
368de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                // Find the event that's being updated
369de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                Cursor c = getServerIdCursor(serverId);
370de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                long id = -1;
371de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                try {
372de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    if (c != null && c.moveToFirst()) {
373de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                        id = c.getLong(0);
374de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    }
375de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                } finally {
376de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    if (c != null) c.close();
377de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                }
378de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                if (id > 0) {
379de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    // DTSTAMP can come first, and we simply need to track it
380de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    if (tag == Tags.CALENDAR_DTSTAMP) {
381de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                        dtStamp = getValue();
382de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                        continue;
383de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    } else if (tag == Tags.CALENDAR_ATTENDEES) {
384de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                        // This is an attendees-only update; just
385de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                        // delete/re-add attendees
386de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                        mBindArgument[0] = Long.toString(id);
387de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                        ops.add(new Operation(ContentProviderOperation
388de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                                .newDelete(mAsSyncAdapterAttendees)
389de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                                .withSelection(ATTENDEES_EXCEPT_ORGANIZER, mBindArgument)));
390de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                        eventId = id;
391de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    } else {
392de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                        // Otherwise, delete the original event and recreate it
393de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                        userLog("Changing (delete/add) event ", serverId);
394de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                        deleteOffset = ops.newDelete(id, serverId);
395de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                        // Add a placeholder event so that associated tables can reference
396de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                        // this as a back reference.  We add the event at the end of the method
397de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                        eventOffset = ops.newEvent(PLACEHOLDER_OPERATION);
398de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    }
399de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                } else {
400de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    // The changed item isn't found. We'll treat this as a new item
401de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    eventOffset = ops.newEvent(PLACEHOLDER_OPERATION);
402de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    userLog(TAG, "Changed item not found; treating as new.");
403de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                }
404de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            } else if (firstTag) {
405de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                // Add a placeholder event so that associated tables can reference
406de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                // this as a back reference.  We add the event at the end of the method
407de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon               eventOffset = ops.newEvent(PLACEHOLDER_OPERATION);
408de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            }
409de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            firstTag = false;
410de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            switch (tag) {
411de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                case Tags.CALENDAR_ALL_DAY_EVENT:
412de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    allDayEvent = getValueInt();
413de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    if (allDayEvent != 0 && timeZone != null) {
414de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                        // If the event doesn't start at midnight local time, we won't consider
415de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                        // this an all-day event in the local time zone (this is what OWA does)
416de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                        GregorianCalendar cal = new GregorianCalendar(mLocalTimeZone);
417de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                        cal.setTimeInMillis(startTime);
418de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                        userLog("All-day event arrived in: " + timeZone.getID());
419de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                        if (cal.get(GregorianCalendar.HOUR_OF_DAY) != 0 ||
420de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                                cal.get(GregorianCalendar.MINUTE) != 0) {
421de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                            allDayEvent = 0;
422de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                            userLog("Not an all-day event locally: " + mLocalTimeZone.getID());
423de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                        }
424de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    }
425de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    cv.put(Events.ALL_DAY, allDayEvent);
426de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    break;
427de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                case Tags.CALENDAR_ATTACHMENTS:
428de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    attachmentsParser();
429de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    break;
430de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                case Tags.CALENDAR_ATTENDEES:
431de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    // If eventId >= 0, this is an update; otherwise, a new Event
432de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    attendeeValues = attendeesParser();
433de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    break;
434de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                case Tags.BASE_BODY:
435de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    cv.put(Events.DESCRIPTION, bodyParser());
436de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    break;
437de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                case Tags.CALENDAR_BODY:
438de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    cv.put(Events.DESCRIPTION, getValue());
439de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    break;
440de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                case Tags.CALENDAR_TIME_ZONE:
441de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    timeZone = CalendarUtilities.tziStringToTimeZone(getValue());
442de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    if (timeZone == null) {
443de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                        timeZone = mLocalTimeZone;
444de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    }
445de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    cv.put(Events.EVENT_TIMEZONE, timeZone.getID());
446de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    break;
447de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                case Tags.CALENDAR_START_TIME:
448de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    startTime = Utility.parseDateTimeToMillis(getValue());
449de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    break;
450de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                case Tags.CALENDAR_END_TIME:
451de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    endTime = Utility.parseDateTimeToMillis(getValue());
452de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    break;
453de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                case Tags.CALENDAR_EXCEPTIONS:
454de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    // For exceptions to show the organizer, the organizer must be added before
455de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    // we call exceptionsParser
456de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    addOrganizerToAttendees(ops, eventId, organizerName, organizerEmail);
457de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    organizerAdded = true;
458de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    exceptionsParser(ops, cv, attendeeValues, reminderMins, busyStatus,
459de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                            startTime, endTime);
460de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    break;
461de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                case Tags.CALENDAR_LOCATION:
462de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    cv.put(Events.EVENT_LOCATION, getValue());
463de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    break;
464de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                case Tags.CALENDAR_RECURRENCE:
465de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    String rrule = recurrenceParser();
466de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    if (rrule != null) {
467de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                        cv.put(Events.RRULE, rrule);
468de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    }
469de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    break;
470de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                case Tags.CALENDAR_ORGANIZER_EMAIL:
471de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    organizerEmail = getValue();
472de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    cv.put(Events.ORGANIZER, organizerEmail);
473de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    break;
474de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                case Tags.CALENDAR_SUBJECT:
475de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    cv.put(Events.TITLE, getValue());
476de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    break;
477de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                case Tags.CALENDAR_SENSITIVITY:
478de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    cv.put(Events.ACCESS_LEVEL, encodeVisibility(getValueInt()));
479de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    break;
480de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                case Tags.CALENDAR_ORGANIZER_NAME:
481de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    organizerName = getValue();
482de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    break;
483de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                case Tags.CALENDAR_REMINDER_MINS_BEFORE:
484de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    // Save away whether this tag has content; Exchange 2010 sends an empty tag
485de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    // rather than not sending one (as with Ex07 and Ex03)
486de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    boolean hasContent = !noContent;
487de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    reminderMins = getValueInt();
488de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    if (hasContent) {
489de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                        ops.newReminder(reminderMins);
490de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                        cv.put(Events.HAS_ALARM, 1);
491de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    }
492de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    break;
493de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                // The following are fields we should save (for changes), though they don't
494de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                // relate to data used by CalendarProvider at this point
495de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                case Tags.CALENDAR_UID:
496de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    cv.put(Events.SYNC_DATA2, getValue());
497de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    break;
498de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                case Tags.CALENDAR_DTSTAMP:
499de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    dtStamp = getValue();
500de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    break;
501de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                case Tags.CALENDAR_MEETING_STATUS:
502de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    ops.newExtendedProperty(EXTENDED_PROPERTY_MEETING_STATUS, getValue());
503de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    break;
504de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                case Tags.CALENDAR_BUSY_STATUS:
505de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    // We'll set the user's status in the Attendees table below
506de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    // Don't set selfAttendeeStatus or CalendarProvider will create a duplicate
507de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    // attendee!
508de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    busyStatus = getValueInt();
509de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    break;
510de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                case Tags.CALENDAR_RESPONSE_TYPE:
511de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    // EAS 14+ uses this for the user's response status; we'll use this instead
512de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    // of busy status, if it appears
513de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    responseType = getValueInt();
514de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    break;
515de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                case Tags.CALENDAR_CATEGORIES:
516de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    String categories = categoriesParser();
517de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    if (categories.length() > 0) {
518de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                        ops.newExtendedProperty(EXTENDED_PROPERTY_CATEGORIES, categories);
519de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    }
520de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    break;
521de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                default:
522de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    skipTag();
523de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            }
524de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        }
525de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
526de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        // Enforce CalendarProvider required properties
527de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        setTimeRelatedValues(cv, startTime, endTime, allDayEvent);
528de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
529de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        // Set user's availability
530de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        cv.put(Events.AVAILABILITY, CalendarUtilities.availabilityFromBusyStatus(busyStatus));
531de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
532de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        // If we haven't added the organizer to attendees, do it now
533de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        if (!organizerAdded) {
534de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            addOrganizerToAttendees(ops, eventId, organizerName, organizerEmail);
535de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        }
536de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
537de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        // Note that organizerEmail can be null with a DTSTAMP only change from the server
538de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        boolean selfOrganizer = (mAccount.mEmailAddress.equals(organizerEmail));
539de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
540de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        // Store email addresses of attendees (in a tokenizable string) in ExtendedProperties
541de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        // If the user is an attendee, set the attendee status using busyStatus (note that the
542de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        // busyStatus is inherited from the parent unless it's specified in the exception)
543de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        // Add the insert/update operation for each attendee (based on whether it's add/change)
544de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        int numAttendees = attendeeValues.size();
545de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        if (numAttendees > MAX_SYNCED_ATTENDEES) {
546de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            // Indicate that we've redacted attendees.  If we're the organizer, disable edit
547de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            // by setting organizerEmail to a bogus value and by setting the upsync prohibited
548de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            // extended properly.
549de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            // Note that we don't set ANY attendees if we're in this branch; however, the
550de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            // organizer has already been included above, and WILL show up (which is good)
551de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            if (eventId < 0) {
552de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                ops.newExtendedProperty(EXTENDED_PROPERTY_ATTENDEES_REDACTED, "1");
553de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                if (selfOrganizer) {
554de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    ops.newExtendedProperty(EXTENDED_PROPERTY_UPSYNC_PROHIBITED, "1");
555de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                }
556de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            } else {
557de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                ops.updatedExtendedProperty(EXTENDED_PROPERTY_ATTENDEES_REDACTED, "1", eventId);
558de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                if (selfOrganizer) {
559de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    ops.updatedExtendedProperty(EXTENDED_PROPERTY_UPSYNC_PROHIBITED, "1",
560de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                            eventId);
561de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                }
562de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            }
563de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            if (selfOrganizer) {
564de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                organizerEmail = BOGUS_ORGANIZER_EMAIL;
565de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                cv.put(Events.ORGANIZER, organizerEmail);
566de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            }
567de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            // Tell UI that we don't have any attendees
568de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            cv.put(Events.HAS_ATTENDEE_DATA, "0");
5693eef378426c7c88608f53f5a268baed40259ccf6Alon Albert            LogUtils.d(TAG, "Maximum number of attendees exceeded; redacting");
570de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        } else if (numAttendees > 0) {
571de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            StringBuilder sb = new StringBuilder();
572de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            for (ContentValues attendee: attendeeValues) {
573de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                String attendeeEmail = attendee.getAsString(Attendees.ATTENDEE_EMAIL);
574de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                sb.append(attendeeEmail);
575de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                sb.append(ATTENDEE_TOKENIZER_DELIMITER);
576de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                if (mAccount.mEmailAddress.equalsIgnoreCase(attendeeEmail)) {
577de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    int attendeeStatus;
578de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    // We'll use the response type (EAS 14), if we've got one; otherwise, we'll
579de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    // try to infer it from busy status
580de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    if (responseType != CalendarUtilities.RESPONSE_TYPE_NONE) {
581de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                        attendeeStatus =
582de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                            CalendarUtilities.attendeeStatusFromResponseType(responseType);
583de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    } else if (!update) {
584de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                        // For new events in EAS < 14, we have no idea what the busy status
585de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                        // means, so we show "none", allowing the user to select an option.
586de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                        attendeeStatus = Attendees.ATTENDEE_STATUS_NONE;
587de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    } else {
588de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                        // For updated events, we'll try to infer the attendee status from the
589de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                        // busy status
590de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                        attendeeStatus =
591de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                            CalendarUtilities.attendeeStatusFromBusyStatus(busyStatus);
592de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    }
593de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    attendee.put(Attendees.ATTENDEE_STATUS, attendeeStatus);
594de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    // If we're an attendee, save away our initial attendee status in the
595de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    // event's ExtendedProperties (we look for differences between this and
596de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    // the user's current attendee status to determine whether an email needs
597de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    // to be sent to the organizer)
598de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    // organizerEmail will be null in the case that this is an attendees-only
599de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    // change from the server
600de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    if (organizerEmail == null ||
601de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                            !organizerEmail.equalsIgnoreCase(attendeeEmail)) {
602de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                        if (eventId < 0) {
603de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                            ops.newExtendedProperty(EXTENDED_PROPERTY_USER_ATTENDEE_STATUS,
604de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                                    Integer.toString(attendeeStatus));
605de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                        } else {
606de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                            ops.updatedExtendedProperty(EXTENDED_PROPERTY_USER_ATTENDEE_STATUS,
607de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                                    Integer.toString(attendeeStatus), eventId);
608de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
609de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                        }
610de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    }
611de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                }
612de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                if (eventId < 0) {
613de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    ops.newAttendee(attendee);
614de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                } else {
615de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    ops.updatedAttendee(attendee, eventId);
616de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                }
617de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            }
618de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            if (eventId < 0) {
619de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                ops.newExtendedProperty(EXTENDED_PROPERTY_ATTENDEES, sb.toString());
620de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                ops.newExtendedProperty(EXTENDED_PROPERTY_ATTENDEES_REDACTED, "0");
621de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                ops.newExtendedProperty(EXTENDED_PROPERTY_UPSYNC_PROHIBITED, "0");
622de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            } else {
623de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                ops.updatedExtendedProperty(EXTENDED_PROPERTY_ATTENDEES, sb.toString(),
624de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                        eventId);
625de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                ops.updatedExtendedProperty(EXTENDED_PROPERTY_ATTENDEES_REDACTED, "0", eventId);
626de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                ops.updatedExtendedProperty(EXTENDED_PROPERTY_UPSYNC_PROHIBITED, "0", eventId);
627de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            }
628de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        }
629de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
630de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        // Put the real event in the proper place in the ops ArrayList
631de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        if (eventOffset >= 0) {
632de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            // Store away the DTSTAMP here
633de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            if (dtStamp != null) {
634de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                ops.newExtendedProperty(EXTENDED_PROPERTY_DTSTAMP, dtStamp);
635de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            }
636de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
637de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            if (isValidEventValues(cv)) {
638de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                ops.set(eventOffset,
639de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                        new Operation(ContentProviderOperation
640de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                                .newInsert(mAsSyncAdapterEvents).withValues(cv)));
641de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            } else {
642de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                // If we can't add this event (it's invalid), remove all of the inserts
643de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                // we've built for it
644de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                int cnt = ops.mCount - eventOffset;
645de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                userLog(TAG, "Removing " + cnt + " inserts from mOps");
646de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                for (int i = 0; i < cnt; i++) {
647de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    ops.remove(eventOffset);
648de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                }
649de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                ops.mCount = eventOffset;
650de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                // If this is a change, we need to also remove the deletion that comes
651de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                // before the addition
652de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                if (deleteOffset >= 0) {
653de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    // Remove the deletion
654de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    ops.remove(deleteOffset);
655de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    // And the deletion of exceptions
656de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    ops.remove(deleteOffset);
657de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    userLog(TAG, "Removing deletion ops from mOps");
658de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    ops.mCount = deleteOffset;
659de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                }
660de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            }
661de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        }
662de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        // Mark the end of the event
663de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        addSeparatorOperation(ops, Events.CONTENT_URI);
664de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    }
665de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
666de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    private void logEventColumns(ContentValues cv, String reason) {
667de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        if (Eas.USER_LOG) {
668de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            StringBuilder sb =
669de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                new StringBuilder("Event invalid, " + reason + ", skipping: Columns = ");
670de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            for (Entry<String, Object> entry: cv.valueSet()) {
671de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                sb.append(entry.getKey());
672de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                sb.append('/');
673de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            }
674de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            userLog(TAG, sb.toString());
675de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        }
676de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    }
677de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
678de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    /*package*/ boolean isValidEventValues(ContentValues cv) {
679de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        boolean isException = cv.containsKey(Events.ORIGINAL_INSTANCE_TIME);
680de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        // All events require DTSTART
681de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        if (!cv.containsKey(Events.DTSTART)) {
682de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            logEventColumns(cv, "DTSTART missing");
683de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            return false;
684de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        // If we're a top-level event, we must have _SYNC_DATA (uid)
685de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        } else if (!isException && !cv.containsKey(Events.SYNC_DATA2)) {
686de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            logEventColumns(cv, "_SYNC_DATA missing");
687de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            return false;
688de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        // We must also have DTEND or DURATION if we're not an exception
689de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        } else if (!isException && !cv.containsKey(Events.DTEND) &&
690de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                !cv.containsKey(Events.DURATION)) {
691de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            logEventColumns(cv, "DTEND/DURATION missing");
692de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            return false;
693de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        // Exceptions require DTEND
694de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        } else if (isException && !cv.containsKey(Events.DTEND)) {
695de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            logEventColumns(cv, "Exception missing DTEND");
696de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            return false;
697de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        // If this is a recurrence, we need a DURATION (in days if an all-day event)
698de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        } else if (cv.containsKey(Events.RRULE)) {
699de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            String duration = cv.getAsString(Events.DURATION);
700de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            if (duration == null) return false;
701de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            if (cv.containsKey(Events.ALL_DAY)) {
702de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                Integer ade = cv.getAsInteger(Events.ALL_DAY);
703de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                if (ade != null && ade != 0 && !duration.endsWith("D")) {
704de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    return false;
705de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                }
706de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            }
707de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        }
708de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        return true;
709de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    }
710de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
711de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    public String recurrenceParser() throws IOException {
712de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        // Turn this information into an RRULE
713de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        int type = -1;
714de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        int occurrences = -1;
715de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        int interval = -1;
716de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        int dow = -1;
717de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        int dom = -1;
718de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        int wom = -1;
719de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        int moy = -1;
720de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        String until = null;
721de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
722de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        while (nextTag(Tags.CALENDAR_RECURRENCE) != END) {
723de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            switch (tag) {
724de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                case Tags.CALENDAR_RECURRENCE_TYPE:
725de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    type = getValueInt();
726de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    break;
727de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                case Tags.CALENDAR_RECURRENCE_INTERVAL:
728de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    interval = getValueInt();
729de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    break;
730de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                case Tags.CALENDAR_RECURRENCE_OCCURRENCES:
731de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    occurrences = getValueInt();
732de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    break;
733de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                case Tags.CALENDAR_RECURRENCE_DAYOFWEEK:
734de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    dow = getValueInt();
735de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    break;
736de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                case Tags.CALENDAR_RECURRENCE_DAYOFMONTH:
737de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    dom = getValueInt();
738de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    break;
739de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                case Tags.CALENDAR_RECURRENCE_WEEKOFMONTH:
740de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    wom = getValueInt();
741de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    break;
742de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                case Tags.CALENDAR_RECURRENCE_MONTHOFYEAR:
743de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    moy = getValueInt();
744de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    break;
745de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                case Tags.CALENDAR_RECURRENCE_UNTIL:
746de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    until = getValue();
747de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    break;
748de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                default:
749de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                   skipTag();
750de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            }
751de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        }
752de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
753de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        return CalendarUtilities.rruleFromRecurrence(type, occurrences, interval,
754de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                dow, dom, wom, moy, until);
755de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    }
756de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
757de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    private void exceptionParser(CalendarOperations ops, ContentValues parentCv,
758de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            ArrayList<ContentValues> attendeeValues, int reminderMins, int busyStatus,
759de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            long startTime, long endTime) throws IOException {
760de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        ContentValues cv = new ContentValues();
761de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        cv.put(Events.CALENDAR_ID, mCalendarId);
762de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
763de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        // It appears that these values have to be copied from the parent if they are to appear
764de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        // Note that they can be overridden below
765de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        cv.put(Events.ORGANIZER, parentCv.getAsString(Events.ORGANIZER));
766de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        cv.put(Events.TITLE, parentCv.getAsString(Events.TITLE));
767de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        cv.put(Events.DESCRIPTION, parentCv.getAsString(Events.DESCRIPTION));
768de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        cv.put(Events.ORIGINAL_ALL_DAY, parentCv.getAsInteger(Events.ALL_DAY));
769de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        cv.put(Events.EVENT_LOCATION, parentCv.getAsString(Events.EVENT_LOCATION));
770de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        cv.put(Events.ACCESS_LEVEL, parentCv.getAsString(Events.ACCESS_LEVEL));
771de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        cv.put(Events.EVENT_TIMEZONE, parentCv.getAsString(Events.EVENT_TIMEZONE));
772de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        // Exceptions should always have this set to zero, since EAS has no concept of
773de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        // separate attendee lists for exceptions; if we fail to do this, then the UI will
774de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        // allow the user to change attendee data, and this change would never get reflected
775de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        // on the server.
776de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        cv.put(Events.HAS_ATTENDEE_DATA, 0);
777de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
778de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        int allDayEvent = 0;
779de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
780de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        // This column is the key that links the exception to the serverId
781de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        cv.put(Events.ORIGINAL_SYNC_ID, parentCv.getAsString(Events._SYNC_ID));
782de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
783de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        String exceptionStartTime = "_noStartTime";
784de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        while (nextTag(Tags.SYNC_APPLICATION_DATA) != END) {
785de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            switch (tag) {
786de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                case Tags.CALENDAR_ATTACHMENTS:
787de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    attachmentsParser();
788de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    break;
789de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                case Tags.CALENDAR_EXCEPTION_START_TIME:
790de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    exceptionStartTime = getValue();
791de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    cv.put(Events.ORIGINAL_INSTANCE_TIME,
792de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                            Utility.parseDateTimeToMillis(exceptionStartTime));
793de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    break;
794de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                case Tags.CALENDAR_EXCEPTION_IS_DELETED:
795de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    if (getValueInt() == 1) {
796de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                        cv.put(Events.STATUS, Events.STATUS_CANCELED);
797de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    }
798de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    break;
799de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                case Tags.CALENDAR_ALL_DAY_EVENT:
800de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    allDayEvent = getValueInt();
801de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    cv.put(Events.ALL_DAY, allDayEvent);
802de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    break;
803de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                case Tags.BASE_BODY:
804de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    cv.put(Events.DESCRIPTION, bodyParser());
805de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    break;
806de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                case Tags.CALENDAR_BODY:
807de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    cv.put(Events.DESCRIPTION, getValue());
808de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    break;
809de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                case Tags.CALENDAR_START_TIME:
810de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    startTime = Utility.parseDateTimeToMillis(getValue());
811de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    break;
812de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                case Tags.CALENDAR_END_TIME:
813de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    endTime = Utility.parseDateTimeToMillis(getValue());
814de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    break;
815de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                case Tags.CALENDAR_LOCATION:
816de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    cv.put(Events.EVENT_LOCATION, getValue());
817de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    break;
818de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                case Tags.CALENDAR_RECURRENCE:
819de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    String rrule = recurrenceParser();
820de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    if (rrule != null) {
821de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                        cv.put(Events.RRULE, rrule);
822de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    }
823de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    break;
824de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                case Tags.CALENDAR_SUBJECT:
825de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    cv.put(Events.TITLE, getValue());
826de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    break;
827de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                case Tags.CALENDAR_SENSITIVITY:
828de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    cv.put(Events.ACCESS_LEVEL, encodeVisibility(getValueInt()));
829de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    break;
830de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                case Tags.CALENDAR_BUSY_STATUS:
831de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    busyStatus = getValueInt();
832de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    // Don't set selfAttendeeStatus or CalendarProvider will create a duplicate
833de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    // attendee!
834de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    break;
835de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    // TODO How to handle these items that are linked to event id!
836de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon//                case Tags.CALENDAR_DTSTAMP:
837de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon//                    ops.newExtendedProperty("dtstamp", getValue());
838de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon//                    break;
839de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon//                case Tags.CALENDAR_REMINDER_MINS_BEFORE:
840de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon//                    ops.newReminder(getValueInt());
841de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon//                    break;
842de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                default:
843de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    skipTag();
844de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            }
845de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        }
846de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
847de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        // We need a _sync_id, but it can't be the parent's id, so we generate one
848de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        cv.put(Events._SYNC_ID, parentCv.getAsString(Events._SYNC_ID) + '_' +
849de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                exceptionStartTime);
850de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
851de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        // Enforce CalendarProvider required properties
852de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        setTimeRelatedValues(cv, startTime, endTime, allDayEvent);
853de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
854de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        // Don't insert an invalid exception event
855de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        if (!isValidEventValues(cv)) return;
856de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
857de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        // Add the exception insert
858de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        int exceptionStart = ops.mCount;
859de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        ops.newException(cv);
860de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        // Also add the attendees, because they need to be copied over from the parent event
861de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        boolean attendeesRedacted = false;
862de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        if (attendeeValues != null) {
863de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            for (ContentValues attValues: attendeeValues) {
864de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                // If this is the user, use his busy status for attendee status
865de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                String attendeeEmail = attValues.getAsString(Attendees.ATTENDEE_EMAIL);
866de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                // Note that the exception at which we surpass the redaction limit might have
867de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                // any number of attendees shown; since this is an edge case and a workaround,
868de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                // it seems to be an acceptable implementation
869de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                if (mAccount.mEmailAddress.equalsIgnoreCase(attendeeEmail)) {
870de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    attValues.put(Attendees.ATTENDEE_STATUS,
871de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                            CalendarUtilities.attendeeStatusFromBusyStatus(busyStatus));
872de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    ops.newAttendee(attValues, exceptionStart);
873de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                } else if (ops.size() < MAX_OPS_BEFORE_EXCEPTION_ATTENDEE_REDACTION) {
874de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    ops.newAttendee(attValues, exceptionStart);
875de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                } else {
876de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    attendeesRedacted = true;
877de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                }
878de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            }
879de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        }
880de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        // And add the parent's reminder value
881de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        if (reminderMins > 0) {
882de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            ops.newReminder(reminderMins, exceptionStart);
883de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        }
884de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        if (attendeesRedacted) {
8853eef378426c7c88608f53f5a268baed40259ccf6Alon Albert            LogUtils.d(TAG, "Attendees redacted in this exception");
886de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        }
887de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    }
888de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
889de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    private static int encodeVisibility(int easVisibility) {
890de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        int visibility = 0;
891de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        switch(easVisibility) {
892de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            case 0:
893de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                visibility = Events.ACCESS_DEFAULT;
894de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                break;
895de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            case 1:
896de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                visibility = Events.ACCESS_PUBLIC;
897de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                break;
898de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            case 2:
899de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                visibility = Events.ACCESS_PRIVATE;
900de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                break;
901de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            case 3:
902de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                visibility = Events.ACCESS_CONFIDENTIAL;
903de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                break;
904de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        }
905de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        return visibility;
906de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    }
907de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
908de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    private void exceptionsParser(CalendarOperations ops, ContentValues cv,
909de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            ArrayList<ContentValues> attendeeValues, int reminderMins, int busyStatus,
910de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            long startTime, long endTime) throws IOException {
911de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        while (nextTag(Tags.CALENDAR_EXCEPTIONS) != END) {
912de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            switch (tag) {
913de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                case Tags.CALENDAR_EXCEPTION:
914de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    exceptionParser(ops, cv, attendeeValues, reminderMins, busyStatus,
915de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                            startTime, endTime);
916de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    break;
917de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                default:
918de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    skipTag();
919de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            }
920de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        }
921de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    }
922de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
923de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    private String categoriesParser() throws IOException {
924de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        StringBuilder categories = new StringBuilder();
925de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        while (nextTag(Tags.CALENDAR_CATEGORIES) != END) {
926de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            switch (tag) {
927de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                case Tags.CALENDAR_CATEGORY:
928de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    // TODO Handle categories (there's no similar concept for gdata AFAIK)
929de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    // We need to save them and spit them back when we update the event
930de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    categories.append(getValue());
931de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    categories.append(CATEGORY_TOKENIZER_DELIMITER);
932de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    break;
933de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                default:
934de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    skipTag();
935de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            }
936de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        }
937de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        return categories.toString();
938de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    }
939de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
940de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    /**
941de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon     * For now, we ignore (but still have to parse) event attachments; these are new in EAS 14
942de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon     */
943de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    private void attachmentsParser() throws IOException {
944de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        while (nextTag(Tags.CALENDAR_ATTACHMENTS) != END) {
945de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            switch (tag) {
946de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                case Tags.CALENDAR_ATTACHMENT:
947de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    skipParser(Tags.CALENDAR_ATTACHMENT);
948de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    break;
949de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                default:
950de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    skipTag();
951de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            }
952de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        }
953de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    }
954de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
955de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    private ArrayList<ContentValues> attendeesParser()
956de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            throws IOException {
957de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        int attendeeCount = 0;
958de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        ArrayList<ContentValues> attendeeValues = new ArrayList<ContentValues>();
959de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        while (nextTag(Tags.CALENDAR_ATTENDEES) != END) {
960de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            switch (tag) {
961de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                case Tags.CALENDAR_ATTENDEE:
962de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    ContentValues cv = attendeeParser();
963de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    // If we're going to redact these attendees anyway, let's avoid unnecessary
964de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    // memory pressure, and not keep them around
965de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    // We still need to parse them all, however
966de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    attendeeCount++;
967de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    // Allow one more than MAX_ATTENDEES, so that the check for "too many" will
968de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    // succeed in addEvent
969de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    if (attendeeCount <= (MAX_SYNCED_ATTENDEES+1)) {
970de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                        attendeeValues.add(cv);
971de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    }
972de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    break;
973de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                default:
974de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    skipTag();
975de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            }
976de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        }
977de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        return attendeeValues;
978de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    }
979de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
980de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    private ContentValues attendeeParser()
981de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            throws IOException {
982de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        ContentValues cv = new ContentValues();
983de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        while (nextTag(Tags.CALENDAR_ATTENDEE) != END) {
984de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            switch (tag) {
985de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                case Tags.CALENDAR_ATTENDEE_EMAIL:
986de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    cv.put(Attendees.ATTENDEE_EMAIL, getValue());
987de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    break;
988de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                case Tags.CALENDAR_ATTENDEE_NAME:
989de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    cv.put(Attendees.ATTENDEE_NAME, getValue());
990de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    break;
991de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                case Tags.CALENDAR_ATTENDEE_STATUS:
992de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    int status = getValueInt();
993de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    cv.put(Attendees.ATTENDEE_STATUS,
994de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                            (status == 2) ? Attendees.ATTENDEE_STATUS_TENTATIVE :
995de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                            (status == 3) ? Attendees.ATTENDEE_STATUS_ACCEPTED :
996de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                            (status == 4) ? Attendees.ATTENDEE_STATUS_DECLINED :
997de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                            (status == 5) ? Attendees.ATTENDEE_STATUS_INVITED :
998de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                                Attendees.ATTENDEE_STATUS_NONE);
999de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    break;
1000de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                case Tags.CALENDAR_ATTENDEE_TYPE:
1001de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    int type = Attendees.TYPE_NONE;
1002de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    // EAS types: 1 = req'd, 2 = opt, 3 = resource
1003de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    switch (getValueInt()) {
1004de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                        case 1:
1005de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                            type = Attendees.TYPE_REQUIRED;
1006de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                            break;
1007de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                        case 2:
1008de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                            type = Attendees.TYPE_OPTIONAL;
1009de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                            break;
1010de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    }
1011de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    cv.put(Attendees.ATTENDEE_TYPE, type);
1012de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    break;
1013de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                default:
1014de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    skipTag();
1015de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            }
1016de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        }
1017de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        cv.put(Attendees.ATTENDEE_RELATIONSHIP, Attendees.RELATIONSHIP_ATTENDEE);
1018de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        return cv;
1019de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    }
1020de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
1021de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    private String bodyParser() throws IOException {
1022de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        String body = null;
1023de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        while (nextTag(Tags.BASE_BODY) != END) {
1024de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            switch (tag) {
1025de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                case Tags.BASE_DATA:
1026de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    body = getValue();
1027de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    break;
1028de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                default:
1029de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    skipTag();
1030de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            }
1031de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        }
1032de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
1033de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        // Handle null data without error
1034de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        if (body == null) return "";
1035de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        // Remove \r's from any body text
1036de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        return body.replace("\r\n", "\n");
1037de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    }
1038de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
1039de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    public void addParser(CalendarOperations ops) throws IOException {
1040de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        String serverId = null;
1041de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        while (nextTag(Tags.SYNC_ADD) != END) {
1042de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            switch (tag) {
1043de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                case Tags.SYNC_SERVER_ID: // same as
1044de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    serverId = getValue();
1045de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    break;
1046de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                case Tags.SYNC_APPLICATION_DATA:
1047de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    addEvent(ops, serverId, false);
1048de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    break;
1049de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                default:
1050de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    skipTag();
1051de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            }
1052de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        }
1053de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    }
1054de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
1055de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    private Cursor getServerIdCursor(String serverId) {
1056de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        return mContentResolver.query(Events.CONTENT_URI, ID_PROJECTION,
1057de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                SERVER_ID_AND_CALENDAR_ID, new String[] {serverId, Long.toString(mCalendarId)},
1058de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                null);
1059de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    }
1060de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
1061de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    private Cursor getClientIdCursor(String clientId) {
1062de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        mBindArgument[0] = clientId;
1063de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        return mContentResolver.query(Events.CONTENT_URI, ID_PROJECTION, CLIENT_ID_SELECTION,
1064de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                mBindArgument, null);
1065de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    }
1066de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
1067de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    public void deleteParser(CalendarOperations ops) throws IOException {
1068de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        while (nextTag(Tags.SYNC_DELETE) != END) {
1069de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            switch (tag) {
1070de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                case Tags.SYNC_SERVER_ID:
1071de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    String serverId = getValue();
1072de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    // Find the event with the given serverId
1073de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    Cursor c = getServerIdCursor(serverId);
1074de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    try {
1075de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                        if (c.moveToFirst()) {
1076de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                            userLog("Deleting ", serverId);
1077de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                            ops.delete(c.getLong(0), serverId);
1078de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                        }
1079de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    } finally {
1080de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                        c.close();
1081de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    }
1082de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    break;
1083de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                default:
1084de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    skipTag();
1085de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            }
1086de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        }
1087de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    }
1088de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
1089de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    /**
1090de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon     * A change is handled as a delete (including all exceptions) and an add
1091de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon     * This isn't as efficient as attempting to traverse the original and all of its exceptions,
1092de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon     * but changes happen infrequently and this code is both simpler and easier to maintain
1093de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon     * @param ops the array of pending ContactProviderOperations.
1094de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon     * @throws IOException
1095de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon     */
1096de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    public void changeParser(CalendarOperations ops) throws IOException {
1097de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        String serverId = null;
1098de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        while (nextTag(Tags.SYNC_CHANGE) != END) {
1099de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            switch (tag) {
1100de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                case Tags.SYNC_SERVER_ID:
1101de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    serverId = getValue();
1102de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    break;
1103de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                case Tags.SYNC_APPLICATION_DATA:
1104de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    userLog("Changing " + serverId);
1105de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    addEvent(ops, serverId, true);
1106de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    break;
1107de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                default:
1108de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    skipTag();
1109de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            }
1110de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        }
1111de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    }
1112de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
1113de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    @Override
1114de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    public void commandsParser() throws IOException {
1115de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        while (nextTag(Tags.SYNC_COMMANDS) != END) {
1116de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            if (tag == Tags.SYNC_ADD) {
1117de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                addParser(mOps);
1118de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            } else if (tag == Tags.SYNC_DELETE) {
1119de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                deleteParser(mOps);
1120de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            } else if (tag == Tags.SYNC_CHANGE) {
1121de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                changeParser(mOps);
1122de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            } else
1123de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                skipTag();
1124de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        }
1125de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    }
1126de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
1127de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    @Override
1128de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    public void commit() throws IOException {
1129de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        userLog("Calendar SyncKey saved as: ", mMailbox.mSyncKey);
1130de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        // Save the syncKey here, using the Helper provider by Calendar provider
1131de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        mOps.add(new Operation(SyncStateContract.Helpers.newSetOperation(
1132de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                asSyncAdapter(SyncState.CONTENT_URI, mAccount.mEmailAddress,
1133de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                        Eas.EXCHANGE_ACCOUNT_MANAGER_TYPE),
1134de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                mAccountManagerAccount,
1135de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                mMailbox.mSyncKey.getBytes())));
1136de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
1137de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        // Execute our CPO's safely
1138de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        try {
1139de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            safeExecute(mContentResolver, CalendarContract.AUTHORITY, mOps);
1140de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        } catch (RemoteException e) {
1141de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            throw new IOException("Remote exception caught; will retry");
1142de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        }
1143de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    }
1144de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
1145de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    public void addResponsesParser() throws IOException {
1146de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        String serverId = null;
1147de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        String clientId = null;
1148de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        int status = -1;
1149de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        ContentValues cv = new ContentValues();
1150de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        while (nextTag(Tags.SYNC_ADD) != END) {
1151de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            switch (tag) {
1152de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                case Tags.SYNC_SERVER_ID:
1153de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    serverId = getValue();
1154de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    break;
1155de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                case Tags.SYNC_CLIENT_ID:
1156de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    clientId = getValue();
1157de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    break;
1158de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                case Tags.SYNC_STATUS:
1159de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    status = getValueInt();
1160de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    if (status != 1) {
1161de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                        userLog("Attempt to add event failed with status: " + status);
1162de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    }
1163de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    break;
1164de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                default:
1165de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    skipTag();
1166de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            }
1167de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        }
1168de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
1169de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        if (clientId == null) return;
1170de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        if (serverId == null) {
1171de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            // TODO Reconsider how to handle this
1172de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            serverId = "FAIL:" + status;
1173de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        }
1174de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
1175de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        Cursor c = getClientIdCursor(clientId);
1176de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        try {
1177de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            if (c.moveToFirst()) {
1178de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                cv.put(Events._SYNC_ID, serverId);
1179de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                cv.put(Events.SYNC_DATA2, clientId);
1180de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                long id = c.getLong(0);
1181de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                // Write the serverId into the Event
1182de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                mOps.add(new Operation(ContentProviderOperation
1183de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                        .newUpdate(ContentUris.withAppendedId(mAsSyncAdapterEvents, id))
1184de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                        .withValues(cv)));
1185de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                userLog("New event " + clientId + " was given serverId: " + serverId);
1186de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            }
1187de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        } finally {
1188de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            c.close();
1189de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        }
1190de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    }
1191de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
1192de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    public void changeResponsesParser() throws IOException {
1193de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        String serverId = null;
1194de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        String status = null;
1195de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        while (nextTag(Tags.SYNC_CHANGE) != END) {
1196de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            switch (tag) {
1197de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                case Tags.SYNC_SERVER_ID:
1198de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    serverId = getValue();
1199de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    break;
1200de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                case Tags.SYNC_STATUS:
1201de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    status = getValue();
1202de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    break;
1203de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                default:
1204de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    skipTag();
1205de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            }
1206de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        }
1207de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        if (serverId != null && status != null) {
1208de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            userLog("Changed event " + serverId + " failed with status: " + status);
1209de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        }
1210de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    }
1211de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
1212de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
1213de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    @Override
1214de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    public void responsesParser() throws IOException {
1215de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        // Handle server responses here (for Add and Change)
1216de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        while (nextTag(Tags.SYNC_RESPONSES) != END) {
1217de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            if (tag == Tags.SYNC_ADD) {
1218de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                addResponsesParser();
1219de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            } else if (tag == Tags.SYNC_CHANGE) {
1220de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                changeResponsesParser();
1221de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            } else
1222de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                skipTag();
1223de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        }
1224de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    }
1225de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
1226de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    /**
1227de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon     * We apply the batch of CPO's here.  We synchronize on the service to avoid thread-nasties,
1228de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon     * and we just return quickly if the service has already been stopped.
1229de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon     */
1230de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    private static ContentProviderResult[] execute(final ContentResolver contentResolver,
1231de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            final String authority, final ArrayList<ContentProviderOperation> ops)
1232de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            throws RemoteException, OperationApplicationException {
1233de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        if (!ops.isEmpty()) {
1234de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            ContentProviderResult[] result = contentResolver.applyBatch(authority, ops);
1235de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            //mService.userLog("Results: " + result.length);
1236de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            return result;
1237de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        }
1238de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        return new ContentProviderResult[0];
1239de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    }
1240de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
1241de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    /**
1242de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon     * Convert an Operation to a CPO; if the Operation has a back reference, apply it with the
1243de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon     * passed-in offset
1244de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon     */
1245de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    @VisibleForTesting
1246de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    static ContentProviderOperation operationToContentProviderOperation(Operation op, int offset) {
1247de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        if (op.mOp != null) {
1248de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            return op.mOp;
1249de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        } else if (op.mBuilder == null) {
1250de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            throw new IllegalArgumentException("Operation must have CPO.Builder");
1251de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        }
1252de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        ContentProviderOperation.Builder builder = op.mBuilder;
1253de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        if (op.mColumnName != null) {
1254de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            builder.withValueBackReference(op.mColumnName, op.mOffset - offset);
1255de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        }
1256de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        return builder.build();
1257de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    }
1258de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
1259de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    /**
1260de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon     * Create a list of CPOs from a list of Operations, and then apply them in a batch
1261de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon     */
1262de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    private static ContentProviderResult[] applyBatch(final ContentResolver contentResolver,
1263de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            final String authority, final ArrayList<Operation> ops, final int offset)
1264de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            throws RemoteException, OperationApplicationException {
1265de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        // Handle the empty case
1266de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        if (ops.isEmpty()) {
1267de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            return new ContentProviderResult[0];
1268de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        }
1269de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        ArrayList<ContentProviderOperation> cpos = new ArrayList<ContentProviderOperation>();
1270de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        for (Operation op: ops) {
1271de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            cpos.add(operationToContentProviderOperation(op, offset));
1272de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        }
1273de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        return execute(contentResolver, authority, cpos);
1274de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    }
1275de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
1276de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    /**
1277de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon     * Apply the list of CPO's in the provider and copy the "mini" result into our full result array
1278de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon     */
1279de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    private static void applyAndCopyResults(final ContentResolver contentResolver,
1280de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            final String authority, final ArrayList<Operation> mini,
1281de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            final ContentProviderResult[] result, final int offset) throws RemoteException {
1282de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        // Empty lists are ok; we just ignore them
1283de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        if (mini.isEmpty()) return;
1284de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        try {
1285de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            ContentProviderResult[] miniResult = applyBatch(contentResolver, authority, mini,
1286de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    offset);
1287de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            // Copy the results from this mini-batch into our results array
1288de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            System.arraycopy(miniResult, 0, result, offset, miniResult.length);
1289de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        } catch (OperationApplicationException e) {
1290de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            // Not possible since we're building the ops ourselves
1291de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        }
1292de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    }
1293de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
1294de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    /**
1295de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon     * Called by a sync adapter to execute a list of Operations in the ContentProvider handling
1296de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon     * the passed-in authority.  If the attempt to apply the batch fails due to a too-large
1297de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon     * binder transaction, we split the Operations as directed by separators.  If any of the
1298de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon     * "mini" batches fails due to a too-large transaction, we're screwed, but this would be
1299de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon     * vanishingly rare.  Other, possibly transient, errors are handled by throwing a
1300de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon     * RemoteException, which the caller will likely re-throw as an IOException so that the sync
1301de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon     * can be attempted again.
1302de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon     *
1303de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon     * Callers MAY leave a dangling separator at the end of the list; note that the separators
1304de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon     * themselves are only markers and are not sent to the provider.
1305de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon     */
1306de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    protected static ContentProviderResult[] safeExecute(final ContentResolver contentResolver,
1307de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            final String authority, final ArrayList<Operation> ops) throws RemoteException {
1308de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        //mService.userLog("Try to execute ", ops.size(), " CPO's for " + authority);
1309de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        ContentProviderResult[] result = null;
1310de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        try {
1311de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            // Try to execute the whole thing
1312de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            return applyBatch(contentResolver, authority, ops, 0);
1313de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        } catch (TransactionTooLargeException e) {
1314de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            // Nope; split into smaller chunks, demarcated by the separator operation
1315de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            //mService.userLog("Transaction too large; spliting!");
1316de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            ArrayList<Operation> mini = new ArrayList<Operation>();
1317de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            // Build a result array with the total size we're sending
1318de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            result = new ContentProviderResult[ops.size()];
1319de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            int count = 0;
1320de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            int offset = 0;
1321de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            for (Operation op: ops) {
1322de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                if (op.mSeparator) {
1323add575db94f86397c9246d89ed85d29968b5aeaaAlon Albert                    //mService.userLog("Try mini-batch of ", mini.size(), " CPO's");
1324add575db94f86397c9246d89ed85d29968b5aeaaAlon Albert                    applyAndCopyResults(contentResolver, authority, mini, result, offset);
1325add575db94f86397c9246d89ed85d29968b5aeaaAlon Albert                    mini.clear();
1326add575db94f86397c9246d89ed85d29968b5aeaaAlon Albert                    // Save away the offset here; this will need to be subtracted out of the
1327add575db94f86397c9246d89ed85d29968b5aeaaAlon Albert                    // value originally set by the adapter
1328add575db94f86397c9246d89ed85d29968b5aeaaAlon Albert                    offset = count + 1; // Remember to add 1 for the separator!
1329de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                } else {
1330de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                    mini.add(op);
1331de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                }
1332de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                count++;
1333de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            }
1334de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            // Check out what's left; if it's more than just a separator, apply the batch
1335de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            int miniSize = mini.size();
1336de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            if ((miniSize > 0) && !(miniSize == 1 && mini.get(0).mSeparator)) {
1337de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                applyAndCopyResults(contentResolver, authority, mini, result, offset);
1338de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            }
1339de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        } catch (RemoteException e) {
1340de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            throw e;
1341de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        } catch (OperationApplicationException e) {
1342de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon            // Not possible since we're building the ops ourselves
1343de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        }
1344de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        return result;
1345de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    }
1346de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
1347de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    /**
1348de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon     * Called by a sync adapter to indicate a relatively safe place to split a batch of CPO's
1349de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon     */
1350de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    protected static void addSeparatorOperation(ArrayList<Operation> ops, Uri uri) {
1351de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        Operation op = new Operation(
1352de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                ContentProviderOperation.newDelete(ContentUris.withAppendedId(uri, SEPARATOR_ID)));
1353de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        op.mSeparator = true;
1354de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        ops.add(op);
1355de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon    }
1356de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
135728f3556f92b20b2efb800c1d95c29b04a3bbbb4fMartin Hibdon    @Override
135828f3556f92b20b2efb800c1d95c29b04a3bbbb4fMartin Hibdon    protected void wipe() {
135928f3556f92b20b2efb800c1d95c29b04a3bbbb4fMartin Hibdon        LogUtils.w(TAG, "Wiping calendar for account %d", mAccount.mId);
136028f3556f92b20b2efb800c1d95c29b04a3bbbb4fMartin Hibdon        EasCalendarSyncHandler.wipeAccountFromContentProvider(mContext,
136128f3556f92b20b2efb800c1d95c29b04a3bbbb4fMartin Hibdon                mAccount.mEmailAddress);
136228f3556f92b20b2efb800c1d95c29b04a3bbbb4fMartin Hibdon    }
1363de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon}
1364