1ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank/*
2ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * Copyright (C) 2008-2009 Marc Blank
3ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * Licensed to The Android Open Source Project.
4ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank *
5ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * Licensed under the Apache License, Version 2.0 (the "License");
6ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * you may not use this file except in compliance with the License.
7ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * You may obtain a copy of the License at
8ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank *
9ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank *      http://www.apache.org/licenses/LICENSE-2.0
10ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank *
11ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * Unless required by applicable law or agreed to in writing, software
12ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * distributed under the License is distributed on an "AS IS" BASIS,
13ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * See the License for the specific language governing permissions and
15ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * limitations under the License.
16ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank */
17ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
18ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blankpackage com.android.exchange.adapter;
19ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
209a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blankimport android.content.ContentProviderClient;
215862a85e17e81866ca82a9905577931947fbd44eMarc Blankimport android.content.ContentProviderOperation;
225862a85e17e81866ca82a9905577931947fbd44eMarc Blankimport android.content.ContentProviderResult;
235862a85e17e81866ca82a9905577931947fbd44eMarc Blankimport android.content.ContentResolver;
245862a85e17e81866ca82a9905577931947fbd44eMarc Blankimport android.content.ContentUris;
255862a85e17e81866ca82a9905577931947fbd44eMarc Blankimport android.content.ContentValues;
265862a85e17e81866ca82a9905577931947fbd44eMarc Blankimport android.content.Entity;
276f898deac953e5838fa28f47a16e0fb92bbc81ebMarc Blankimport android.content.Entity.NamedContentValues;
285862a85e17e81866ca82a9905577931947fbd44eMarc Blankimport android.content.EntityIterator;
295862a85e17e81866ca82a9905577931947fbd44eMarc Blankimport android.database.Cursor;
30beb88906fd9d7c992dea04f75d8d57df17b0ddb1Marc Blankimport android.database.DatabaseUtils;
315862a85e17e81866ca82a9905577931947fbd44eMarc Blankimport android.net.Uri;
325862a85e17e81866ca82a9905577931947fbd44eMarc Blankimport android.os.RemoteException;
33693ed7fdd5a7ec7af87d105b76267c78a8acc3dbRoboErikimport android.provider.CalendarContract;
34693ed7fdd5a7ec7af87d105b76267c78a8acc3dbRoboErikimport android.provider.CalendarContract.Attendees;
35693ed7fdd5a7ec7af87d105b76267c78a8acc3dbRoboErikimport android.provider.CalendarContract.Calendars;
36693ed7fdd5a7ec7af87d105b76267c78a8acc3dbRoboErikimport android.provider.CalendarContract.Events;
37693ed7fdd5a7ec7af87d105b76267c78a8acc3dbRoboErikimport android.provider.CalendarContract.EventsEntity;
38693ed7fdd5a7ec7af87d105b76267c78a8acc3dbRoboErikimport android.provider.CalendarContract.ExtendedProperties;
39693ed7fdd5a7ec7af87d105b76267c78a8acc3dbRoboErikimport android.provider.CalendarContract.Reminders;
40693ed7fdd5a7ec7af87d105b76267c78a8acc3dbRoboErikimport android.provider.CalendarContract.SyncState;
415862a85e17e81866ca82a9905577931947fbd44eMarc Blankimport android.provider.ContactsContract.RawContacts;
426f898deac953e5838fa28f47a16e0fb92bbc81ebMarc Blankimport android.provider.SyncStateContract;
43d8161bff94bbaf513601243d7697f140d76deffeMarc Blankimport android.text.TextUtils;
445862a85e17e81866ca82a9905577931947fbd44eMarc Blankimport android.util.Log;
4567698e240187c902bed123bf18d342ff25ec75c7Marc Blank
4660306788d571f234883d9833a97216c40289d0cfMichael Chanimport com.android.calendarcommon2.DateException;
4760306788d571f234883d9833a97216c40289d0cfMichael Chanimport com.android.calendarcommon2.Duration;
48b931f82fa44c2e26e2645c0d5fde9eef3e666efdPaul Westbrookimport com.android.emailcommon.AccountManagerTypes;
4940dde4349f0f24260ffeba9dd8768e6d82e0188bMarc Blankimport com.android.emailcommon.provider.EmailContent;
5040dde4349f0f24260ffeba9dd8768e6d82e0188bMarc Blankimport com.android.emailcommon.provider.EmailContent.Message;
5140dde4349f0f24260ffeba9dd8768e6d82e0188bMarc Blankimport com.android.emailcommon.utility.Utility;
5240dde4349f0f24260ffeba9dd8768e6d82e0188bMarc Blankimport com.android.exchange.CommandStatusException;
5340dde4349f0f24260ffeba9dd8768e6d82e0188bMarc Blankimport com.android.exchange.Eas;
5440dde4349f0f24260ffeba9dd8768e6d82e0188bMarc Blankimport com.android.exchange.EasOutboxService;
5540dde4349f0f24260ffeba9dd8768e6d82e0188bMarc Blankimport com.android.exchange.EasSyncService;
5640dde4349f0f24260ffeba9dd8768e6d82e0188bMarc Blankimport com.android.exchange.ExchangeService;
5740dde4349f0f24260ffeba9dd8768e6d82e0188bMarc Blankimport com.android.exchange.utility.CalendarUtilities;
5840dde4349f0f24260ffeba9dd8768e6d82e0188bMarc Blank
59ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blankimport java.io.IOException;
608047ef058e41c164c2c8ab230ae8d123f042c167Marc Blankimport java.io.InputStream;
615862a85e17e81866ca82a9905577931947fbd44eMarc Blankimport java.util.ArrayList;
6219d0a651baee4b8e2333befe359e131952144f35Marc Blankimport java.util.GregorianCalendar;
636f898deac953e5838fa28f47a16e0fb92bbc81ebMarc Blankimport java.util.Map.Entry;
6414045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blankimport java.util.StringTokenizer;
655862a85e17e81866ca82a9905577931947fbd44eMarc Blankimport java.util.TimeZone;
66c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blankimport java.util.UUID;
67ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
68ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank/**
69ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * Sync adapter class for EAS calendars
70ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank *
71ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank */
727c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blankpublic class CalendarSyncAdapter extends AbstractSyncAdapter {
73ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
745862a85e17e81866ca82a9905577931947fbd44eMarc Blank    private static final String TAG = "EasCalendarSyncAdapter";
7504c880a6b5ad041f172d4b1eeecc06d6a06e4141RoboErik
7604c880a6b5ad041f172d4b1eeecc06d6a06e4141RoboErik    private static final String EVENT_SAVED_TIMEZONE_COLUMN = Events.SYNC_DATA1;
7704c880a6b5ad041f172d4b1eeecc06d6a06e4141RoboErik    /**
7804c880a6b5ad041f172d4b1eeecc06d6a06e4141RoboErik     * Used to keep track of exception vs parent event dirtiness.
7904c880a6b5ad041f172d4b1eeecc06d6a06e4141RoboErik     */
8004c880a6b5ad041f172d4b1eeecc06d6a06e4141RoboErik    private static final String EVENT_SYNC_MARK = Events.SYNC_DATA8;
8104c880a6b5ad041f172d4b1eeecc06d6a06e4141RoboErik    private static final String EVENT_SYNC_VERSION = Events.SYNC_DATA4;
825862a85e17e81866ca82a9905577931947fbd44eMarc Blank    // Since exceptions will have the same _SYNC_ID as the original event we have to check that
835862a85e17e81866ca82a9905577931947fbd44eMarc Blank    // there's no original event when finding an item by _SYNC_ID
8494ceb38eefbc7cd5c62250197d99ea171a8f2bfbMarc Blank    private static final String SERVER_ID_AND_CALENDAR_ID = Events._SYNC_ID + "=? AND " +
859e86eb14c6e1f7d7730f8ca6953fdfd95fe2b143RoboErik        Events.ORIGINAL_SYNC_ID + " ISNULL AND " + Events.CALENDAR_ID + "=?";
86c4ff4569299e8df6da7812af61dbff6775097018RoboErik    private static final String EVENT_ID_AND_CALENDAR_ID = Events._ID + "=? AND " +
87c4ff4569299e8df6da7812af61dbff6775097018RoboErik        Events.ORIGINAL_SYNC_ID + " ISNULL AND " + Events.CALENDAR_ID + "=?";
8804c880a6b5ad041f172d4b1eeecc06d6a06e4141RoboErik    private static final String DIRTY_OR_MARKED_TOP_LEVEL_IN_CALENDAR = "(" + Events.DIRTY
8904c880a6b5ad041f172d4b1eeecc06d6a06e4141RoboErik            + "=1 OR " + EVENT_SYNC_MARK + "= 1) AND " +
90c4ff4569299e8df6da7812af61dbff6775097018RoboErik        Events.ORIGINAL_ID + " ISNULL AND " + Events.CALENDAR_ID + "=?";
919cf6e79160ee3d11f2aceebadecab2520ee85841Marc Blank    private static final String DIRTY_EXCEPTION_IN_CALENDAR =
92c4ff4569299e8df6da7812af61dbff6775097018RoboErik        Events.DIRTY + "=1 AND " + Events.ORIGINAL_ID + " NOTNULL AND " +
939cf6e79160ee3d11f2aceebadecab2520ee85841Marc Blank        Events.CALENDAR_ID + "=?";
9404c880a6b5ad041f172d4b1eeecc06d6a06e4141RoboErik    private static final String CLIENT_ID_SELECTION = Events.SYNC_DATA2 + "=?";
958b3730a39a19c01577c53f3d1099c97539198deeMarc Blank    private static final String ORIGINAL_EVENT_AND_CALENDAR =
969e86eb14c6e1f7d7730f8ca6953fdfd95fe2b143RoboErik        Events.ORIGINAL_SYNC_ID + "=? AND " + Events.CALENDAR_ID + "=?";
975862a85e17e81866ca82a9905577931947fbd44eMarc Blank    private static final String ATTENDEES_EXCEPT_ORGANIZER = Attendees.EVENT_ID + "=? AND " +
985862a85e17e81866ca82a9905577931947fbd44eMarc Blank        Attendees.ATTENDEE_RELATIONSHIP + "!=" + Attendees.RELATIONSHIP_ORGANIZER;
995862a85e17e81866ca82a9905577931947fbd44eMarc Blank    private static final String[] ID_PROJECTION = new String[] {Events._ID};
100dafc866120dac68fabbddcc9943e3995894c5244Marc Blank    private static final String[] ORIGINAL_EVENT_PROJECTION =
101c4ff4569299e8df6da7812af61dbff6775097018RoboErik        new String[] {Events.ORIGINAL_ID, Events._ID};
102ac3283a6b46bf27a5b418638c95a13121ea180e1Marc Blank    private static final String EVENT_ID_AND_NAME =
103ac3283a6b46bf27a5b418638c95a13121ea180e1Marc Blank        ExtendedProperties.EVENT_ID + "=? AND " + ExtendedProperties.NAME + "=?";
1045862a85e17e81866ca82a9905577931947fbd44eMarc Blank
105b931f82fa44c2e26e2645c0d5fde9eef3e666efdPaul Westbrook    // Note that we use LIKE below for its case insensitivity
106b931f82fa44c2e26e2645c0d5fde9eef3e666efdPaul Westbrook    private static final String EVENT_AND_EMAIL  =
107b931f82fa44c2e26e2645c0d5fde9eef3e666efdPaul Westbrook        Attendees.EVENT_ID + "=? AND "+ Attendees.ATTENDEE_EMAIL + " LIKE ?";
108b931f82fa44c2e26e2645c0d5fde9eef3e666efdPaul Westbrook    private static final int ATTENDEE_STATUS_COLUMN_STATUS = 0;
109b931f82fa44c2e26e2645c0d5fde9eef3e666efdPaul Westbrook    private static final String[] ATTENDEE_STATUS_PROJECTION =
110b931f82fa44c2e26e2645c0d5fde9eef3e666efdPaul Westbrook        new String[] {Attendees.ATTENDEE_STATUS};
111b931f82fa44c2e26e2645c0d5fde9eef3e666efdPaul Westbrook
1121f20d94a3c2b8c124c7242a81d7c661ad03a2426Marc Blank    public static final String CALENDAR_SELECTION =
1139e86eb14c6e1f7d7730f8ca6953fdfd95fe2b143RoboErik        Calendars.ACCOUNT_NAME + "=? AND " + Calendars.ACCOUNT_TYPE + "=?";
1145862a85e17e81866ca82a9905577931947fbd44eMarc Blank    private static final int CALENDAR_SELECTION_ID = 0;
1155862a85e17e81866ca82a9905577931947fbd44eMarc Blank
116ac3283a6b46bf27a5b418638c95a13121ea180e1Marc Blank    private static final String[] EXTENDED_PROPERTY_PROJECTION =
117ac3283a6b46bf27a5b418638c95a13121ea180e1Marc Blank        new String[] {ExtendedProperties._ID};
118ac3283a6b46bf27a5b418638c95a13121ea180e1Marc Blank    private static final int EXTENDED_PROPERTY_ID = 0;
119ac3283a6b46bf27a5b418638c95a13121ea180e1Marc Blank
12014045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank    private static final String CATEGORY_TOKENIZER_DELIMITER = "\\";
121332d08cc2fc37d9936a73e3a120125c60b0587d1Marc Blank    private static final String ATTENDEE_TOKENIZER_DELIMITER = CATEGORY_TOKENIZER_DELIMITER;
12214045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank
123ac3283a6b46bf27a5b418638c95a13121ea180e1Marc Blank    private static final String EXTENDED_PROPERTY_USER_ATTENDEE_STATUS = "userAttendeeStatus";
124ac3283a6b46bf27a5b418638c95a13121ea180e1Marc Blank    private static final String EXTENDED_PROPERTY_ATTENDEES = "attendees";
125ac3283a6b46bf27a5b418638c95a13121ea180e1Marc Blank    private static final String EXTENDED_PROPERTY_DTSTAMP = "dtstamp";
126ac3283a6b46bf27a5b418638c95a13121ea180e1Marc Blank    private static final String EXTENDED_PROPERTY_MEETING_STATUS = "meeting_status";
127ac3283a6b46bf27a5b418638c95a13121ea180e1Marc Blank    private static final String EXTENDED_PROPERTY_CATEGORIES = "categories";
128e588dbc379872f431426dbd3a2d5fe578e37e0b4Marc Blank    // Used to indicate that we removed the attendee list because it was too large
129e588dbc379872f431426dbd3a2d5fe578e37e0b4Marc Blank    private static final String EXTENDED_PROPERTY_ATTENDEES_REDACTED = "attendeesRedacted";
130e588dbc379872f431426dbd3a2d5fe578e37e0b4Marc Blank    // Used to indicate that upsyncs aren't allowed (we catch this in sendLocalChanges)
131e588dbc379872f431426dbd3a2d5fe578e37e0b4Marc Blank    private static final String EXTENDED_PROPERTY_UPSYNC_PROHIBITED = "upsyncProhibited";
132ac3283a6b46bf27a5b418638c95a13121ea180e1Marc Blank
133151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank    private static final Operation PLACEHOLDER_OPERATION =
134151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank        new Operation(ContentProviderOperation.newInsert(Uri.EMPTY));
135c50b499d8992cdf30dbc6355347dd43a7a5ca6e8Makoto Onuki
1368caeb46f09d6b50e573c91f29c7eb9bac3682a8fMarc Blank    private static final Object sSyncKeyLock = new Object();
1378caeb46f09d6b50e573c91f29c7eb9bac3682a8fMarc Blank
13827b81d71cc0f892a4d32b64da4ee18f20cc98df8Marc Blank    private static final TimeZone UTC_TIMEZONE = TimeZone.getTimeZone("UTC");
139270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank    private final TimeZone mLocalTimeZone = TimeZone.getDefault();
140270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank
14127b81d71cc0f892a4d32b64da4ee18f20cc98df8Marc Blank
142e588dbc379872f431426dbd3a2d5fe578e37e0b4Marc Blank    // Maximum number of allowed attendees; above this number, we mark the Event with the
143e588dbc379872f431426dbd3a2d5fe578e37e0b4Marc Blank    // attendeesRedacted extended property and don't allow the event to be upsynced to the server
144450dd050b8c23e2bd5c4288490d24d7ca69a7269Marc Blank    private static final int MAX_SYNCED_ATTENDEES = 50;
145e588dbc379872f431426dbd3a2d5fe578e37e0b4Marc Blank    // We set the organizer to this when the user is the organizer and we've redacted the
146e588dbc379872f431426dbd3a2d5fe578e37e0b4Marc Blank    // attendee list.  By making the meeting organizer OTHER than the user, we cause the UI to
147e588dbc379872f431426dbd3a2d5fe578e37e0b4Marc Blank    // prevent edits to this event (except local changes like reminder).
148e588dbc379872f431426dbd3a2d5fe578e37e0b4Marc Blank    private static final String BOGUS_ORGANIZER_EMAIL = "upload_disallowed@uploadisdisallowed.aaa";
149450dd050b8c23e2bd5c4288490d24d7ca69a7269Marc Blank    // Maximum number of CPO's before we start redacting attendees in exceptions
150450dd050b8c23e2bd5c4288490d24d7ca69a7269Marc Blank    // The number 500 has been determined empirically; 1500 CPOs appears to be the limit before
151450dd050b8c23e2bd5c4288490d24d7ca69a7269Marc Blank    // binder failures occur, but we need room at any point for additional events/exceptions so
152450dd050b8c23e2bd5c4288490d24d7ca69a7269Marc Blank    // we set our limit at 1/3 of the apparent maximum for extra safety
153450dd050b8c23e2bd5c4288490d24d7ca69a7269Marc Blank    // TODO Find a better solution to this workaround
154450dd050b8c23e2bd5c4288490d24d7ca69a7269Marc Blank    private static final int MAX_OPS_BEFORE_EXCEPTION_ATTENDEE_REDACTION = 500;
155e588dbc379872f431426dbd3a2d5fe578e37e0b4Marc Blank
1565862a85e17e81866ca82a9905577931947fbd44eMarc Blank    private long mCalendarId = -1;
15794ceb38eefbc7cd5c62250197d99ea171a8f2bfbMarc Blank    private String mCalendarIdString;
15894ceb38eefbc7cd5c62250197d99ea171a8f2bfbMarc Blank    private String[] mCalendarIdArgument;
1597a882dc9d2ecded51759ad2b8a5f40f0a4545c18Marc Blank    /*package*/ String mEmailAddress;
1605862a85e17e81866ca82a9905577931947fbd44eMarc Blank
1615862a85e17e81866ca82a9905577931947fbd44eMarc Blank    private ArrayList<Long> mDeletedIdList = new ArrayList<Long>();
162e96bd20257c47db94f9166fd37ca00e0c73788afMarc Blank    private ArrayList<Long> mUploadedIdList = new ArrayList<Long>();
16330d2d4ea74cfca9d27dfd495cebc8387b8f2454dMarc Blank    private ArrayList<Long> mSendCancelIdList = new ArrayList<Long>();
164125f1c07b605ec798e81c9fb0f153b2b5200fe1fMarc Blank    private ArrayList<Message> mOutgoingMailList = new ArrayList<Message>();
1655862a85e17e81866ca82a9905577931947fbd44eMarc Blank
166151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank    private final Uri mAsSyncAdapterAttendees;
167151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank    private final Uri mAsSyncAdapterEvents;
168151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank    private final Uri mAsSyncAdapterReminders;
169151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank    private final Uri mAsSyncAdapterExtendedProperties;
170151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank
1716f898deac953e5838fa28f47a16e0fb92bbc81ebMarc Blank    public CalendarSyncAdapter(EasSyncService service) {
1726f898deac953e5838fa28f47a16e0fb92bbc81ebMarc Blank        super(service);
173edcfd554728a27a7678306ea7432bd8676ec6990Marc Blank        mEmailAddress = mAccount.mEmailAddress;
174151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank
175b931f82fa44c2e26e2645c0d5fde9eef3e666efdPaul Westbrook        String amType = Eas.EXCHANGE_ACCOUNT_MANAGER_TYPE;
176151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank        mAsSyncAdapterAttendees =
177151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank                asSyncAdapter(Attendees.CONTENT_URI, mEmailAddress, amType);
178151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank        mAsSyncAdapterEvents =
179151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank                asSyncAdapter(Events.CONTENT_URI, mEmailAddress, amType);
180151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank        mAsSyncAdapterReminders =
181151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank                asSyncAdapter(Reminders.CONTENT_URI, mEmailAddress, amType);
182151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank        mAsSyncAdapterExtendedProperties =
183151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank                asSyncAdapter(ExtendedProperties.CONTENT_URI, mEmailAddress, amType);
184151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank
1855862a85e17e81866ca82a9905577931947fbd44eMarc Blank        Cursor c = mService.mContentResolver.query(Calendars.CONTENT_URI,
1865862a85e17e81866ca82a9905577931947fbd44eMarc Blank                new String[] {Calendars._ID}, CALENDAR_SELECTION,
187c8e4352ea6cfa67f15140512e84af8ccede222d2Marc Blank                new String[] {mEmailAddress, Eas.EXCHANGE_ACCOUNT_MANAGER_TYPE}, null);
18820d5e152abafc87b9840fdc36cc33c3ca243cd1bMarc Blank        if (c == null) return;
1895862a85e17e81866ca82a9905577931947fbd44eMarc Blank        try {
1905862a85e17e81866ca82a9905577931947fbd44eMarc Blank            if (c.moveToFirst()) {
1915862a85e17e81866ca82a9905577931947fbd44eMarc Blank                mCalendarId = c.getLong(CALENDAR_SELECTION_ID);
19277110d3a646dd691d84abd0b1e083385c1418ac5Marc Blank            } else {
19377110d3a646dd691d84abd0b1e083385c1418ac5Marc Blank                mCalendarId = CalendarUtilities.createCalendar(mService, mAccount, mMailbox);
1945862a85e17e81866ca82a9905577931947fbd44eMarc Blank            }
19594ceb38eefbc7cd5c62250197d99ea171a8f2bfbMarc Blank            mCalendarIdString = Long.toString(mCalendarId);
19694ceb38eefbc7cd5c62250197d99ea171a8f2bfbMarc Blank            mCalendarIdArgument = new String[] {mCalendarIdString};
1975862a85e17e81866ca82a9905577931947fbd44eMarc Blank        } finally {
1985862a85e17e81866ca82a9905577931947fbd44eMarc Blank            c.close();
1995862a85e17e81866ca82a9905577931947fbd44eMarc Blank        }
200151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank        }
201ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
202ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    @Override
203ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    public String getCollectionName() {
204ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        return "Calendar";
205ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
206ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
207ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    @Override
2085862a85e17e81866ca82a9905577931947fbd44eMarc Blank    public void cleanup() {
209ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
2108480f5bc2eb612920f7e17312a693b4d8c26f14bMarc Blank
2118480f5bc2eb612920f7e17312a693b4d8c26f14bMarc Blank    @Override
2126f898deac953e5838fa28f47a16e0fb92bbc81ebMarc Blank    public void wipe() {
2136f898deac953e5838fa28f47a16e0fb92bbc81ebMarc Blank        // Delete the calendar associated with this account
214beb88906fd9d7c992dea04f75d8d57df17b0ddb1Marc Blank        // CalendarProvider2 does NOT handle selection arguments in deletions
2156989716b639d274a98141674556ac9402be32ebeRoboErik        mContentResolver.delete(
2166989716b639d274a98141674556ac9402be32ebeRoboErik                asSyncAdapter(Calendars.CONTENT_URI, mEmailAddress,
2176989716b639d274a98141674556ac9402be32ebeRoboErik                        Eas.EXCHANGE_ACCOUNT_MANAGER_TYPE),
2189e86eb14c6e1f7d7730f8ca6953fdfd95fe2b143RoboErik                Calendars.ACCOUNT_NAME + "=" + DatabaseUtils.sqlEscapeString(mEmailAddress)
2199e86eb14c6e1f7d7730f8ca6953fdfd95fe2b143RoboErik                        + " AND " + Calendars.ACCOUNT_TYPE + "="
220b931f82fa44c2e26e2645c0d5fde9eef3e666efdPaul Westbrook                        + DatabaseUtils.sqlEscapeString(AccountManagerTypes.TYPE_EXCHANGE), null);
2210b4eb5923070d89c7221498c831cb2d19e3da2ddMarc Blank        // Invalidate our calendar observers
2220b4eb5923070d89c7221498c831cb2d19e3da2ddMarc Blank        ExchangeService.unregisterCalendarObservers();
2236f898deac953e5838fa28f47a16e0fb92bbc81ebMarc Blank    }
2246f898deac953e5838fa28f47a16e0fb92bbc81ebMarc Blank
2256f898deac953e5838fa28f47a16e0fb92bbc81ebMarc Blank    @Override
226e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank    public void sendSyncOptions(Double protocolVersion, Serializer s, boolean initialSync)
227e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank            throws IOException  {
228e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank        if (!initialSync) {
229e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank            setPimSyncOptions(protocolVersion, Eas.FILTER_2_WEEKS, s);
230e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank        }
2314f15001bdfd11c79524b4e44d60041967779e763Marc Blank    }
2324f15001bdfd11c79524b4e44d60041967779e763Marc Blank
2334f15001bdfd11c79524b4e44d60041967779e763Marc Blank    @Override
234aa288fe7ccbd28abcf990ce8337f2da677a1d370Marc Blank    public boolean isSyncable() {
235693ed7fdd5a7ec7af87d105b76267c78a8acc3dbRoboErik        return ContentResolver.getSyncAutomatically(mAccountManagerAccount,
236693ed7fdd5a7ec7af87d105b76267c78a8acc3dbRoboErik                CalendarContract.AUTHORITY);
237aa288fe7ccbd28abcf990ce8337f2da677a1d370Marc Blank    }
238aa288fe7ccbd28abcf990ce8337f2da677a1d370Marc Blank
239aa288fe7ccbd28abcf990ce8337f2da677a1d370Marc Blank    @Override
24077186bb1a174432ef272584374942d8b9228e39cMarc Blank    public boolean parse(InputStream is) throws IOException, CommandStatusException {
2415862a85e17e81866ca82a9905577931947fbd44eMarc Blank        EasCalendarSyncParser p = new EasCalendarSyncParser(is, this);
2425862a85e17e81866ca82a9905577931947fbd44eMarc Blank        return p.parse();
2435862a85e17e81866ca82a9905577931947fbd44eMarc Blank    }
244c50b499d8992cdf30dbc6355347dd43a7a5ca6e8Makoto Onuki
24587f1d558d69d324e399e2c1eb265c9301c9b410aMarc Blank    public static Uri asSyncAdapter(Uri uri, String account, String accountType) {
246693ed7fdd5a7ec7af87d105b76267c78a8acc3dbRoboErik        return uri.buildUpon().appendQueryParameter(CalendarContract.CALLER_IS_SYNCADAPTER, "true")
2476989716b639d274a98141674556ac9402be32ebeRoboErik                .appendQueryParameter(Calendars.ACCOUNT_NAME, account)
2486989716b639d274a98141674556ac9402be32ebeRoboErik                .appendQueryParameter(Calendars.ACCOUNT_TYPE, accountType).build();
2495862a85e17e81866ca82a9905577931947fbd44eMarc Blank    }
2505862a85e17e81866ca82a9905577931947fbd44eMarc Blank
2515862a85e17e81866ca82a9905577931947fbd44eMarc Blank    /**
2525862a85e17e81866ca82a9905577931947fbd44eMarc Blank     * Generate the uri for the data row associated with this NamedContentValues object
2535862a85e17e81866ca82a9905577931947fbd44eMarc Blank     * @param ncv the NamedContentValues object
2545862a85e17e81866ca82a9905577931947fbd44eMarc Blank     * @return a uri that can be used to refer to this row
2555862a85e17e81866ca82a9905577931947fbd44eMarc Blank     */
2565862a85e17e81866ca82a9905577931947fbd44eMarc Blank    public Uri dataUriFromNamedContentValues(NamedContentValues ncv) {
2575862a85e17e81866ca82a9905577931947fbd44eMarc Blank        long id = ncv.values.getAsLong(RawContacts._ID);
2585862a85e17e81866ca82a9905577931947fbd44eMarc Blank        Uri dataUri = ContentUris.withAppendedId(ncv.uri, id);
2595862a85e17e81866ca82a9905577931947fbd44eMarc Blank        return dataUri;
2608480f5bc2eb612920f7e17312a693b4d8c26f14bMarc Blank    }
2618047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank
2625862a85e17e81866ca82a9905577931947fbd44eMarc Blank    /**
2639a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank     * We get our SyncKey from CalendarProvider.  If there's not one, we set it to "0" (the reset
2649a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank     * state) and save that away.
2655862a85e17e81866ca82a9905577931947fbd44eMarc Blank     */
2668047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank    @Override
2675862a85e17e81866ca82a9905577931947fbd44eMarc Blank    public String getSyncKey() throws IOException {
2688caeb46f09d6b50e573c91f29c7eb9bac3682a8fMarc Blank        synchronized (sSyncKeyLock) {
2698caeb46f09d6b50e573c91f29c7eb9bac3682a8fMarc Blank            ContentProviderClient client = mService.mContentResolver
270693ed7fdd5a7ec7af87d105b76267c78a8acc3dbRoboErik                    .acquireContentProviderClient(CalendarContract.CONTENT_URI);
2718caeb46f09d6b50e573c91f29c7eb9bac3682a8fMarc Blank            try {
2726989716b639d274a98141674556ac9402be32ebeRoboErik                byte[] data = SyncStateContract.Helpers.get(
2736989716b639d274a98141674556ac9402be32ebeRoboErik                        client,
274670478b801a5e229da39c198f4cb84f95a0da3efMarc Blank                        asSyncAdapter(SyncState.CONTENT_URI, mEmailAddress,
2756989716b639d274a98141674556ac9402be32ebeRoboErik                                Eas.EXCHANGE_ACCOUNT_MANAGER_TYPE), mAccountManagerAccount);
2768caeb46f09d6b50e573c91f29c7eb9bac3682a8fMarc Blank                if (data == null || data.length == 0) {
2778caeb46f09d6b50e573c91f29c7eb9bac3682a8fMarc Blank                    // Initialize the SyncKey
2788caeb46f09d6b50e573c91f29c7eb9bac3682a8fMarc Blank                    setSyncKey("0", false);
2798caeb46f09d6b50e573c91f29c7eb9bac3682a8fMarc Blank                    return "0";
2808caeb46f09d6b50e573c91f29c7eb9bac3682a8fMarc Blank                } else {
2818caeb46f09d6b50e573c91f29c7eb9bac3682a8fMarc Blank                    String syncKey = new String(data);
2828caeb46f09d6b50e573c91f29c7eb9bac3682a8fMarc Blank                    userLog("SyncKey retrieved as ", syncKey, " from CalendarProvider");
2838caeb46f09d6b50e573c91f29c7eb9bac3682a8fMarc Blank                    return syncKey;
2848caeb46f09d6b50e573c91f29c7eb9bac3682a8fMarc Blank                }
2858caeb46f09d6b50e573c91f29c7eb9bac3682a8fMarc Blank            } catch (RemoteException e) {
2868caeb46f09d6b50e573c91f29c7eb9bac3682a8fMarc Blank                throw new IOException("Can't get SyncKey from CalendarProvider");
2879a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank            }
2889a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank        }
2895862a85e17e81866ca82a9905577931947fbd44eMarc Blank    }
2905862a85e17e81866ca82a9905577931947fbd44eMarc Blank
2915862a85e17e81866ca82a9905577931947fbd44eMarc Blank    /**
2929a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank     * We only need to set this when we're forced to make the SyncKey "0" (a reset).  In all other
2939a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank     * cases, the SyncKey is set within Calendar
2945862a85e17e81866ca82a9905577931947fbd44eMarc Blank     */
2955862a85e17e81866ca82a9905577931947fbd44eMarc Blank    @Override
2965862a85e17e81866ca82a9905577931947fbd44eMarc Blank    public void setSyncKey(String syncKey, boolean inCommands) throws IOException {
2978caeb46f09d6b50e573c91f29c7eb9bac3682a8fMarc Blank        synchronized (sSyncKeyLock) {
2988caeb46f09d6b50e573c91f29c7eb9bac3682a8fMarc Blank            if ("0".equals(syncKey) || !inCommands) {
2998caeb46f09d6b50e573c91f29c7eb9bac3682a8fMarc Blank                ContentProviderClient client = mService.mContentResolver
300693ed7fdd5a7ec7af87d105b76267c78a8acc3dbRoboErik                        .acquireContentProviderClient(CalendarContract.CONTENT_URI);
3018caeb46f09d6b50e573c91f29c7eb9bac3682a8fMarc Blank                try {
3026989716b639d274a98141674556ac9402be32ebeRoboErik                    SyncStateContract.Helpers.set(
3036989716b639d274a98141674556ac9402be32ebeRoboErik                            client,
304670478b801a5e229da39c198f4cb84f95a0da3efMarc Blank                            asSyncAdapter(SyncState.CONTENT_URI, mEmailAddress,
3056989716b639d274a98141674556ac9402be32ebeRoboErik                                    Eas.EXCHANGE_ACCOUNT_MANAGER_TYPE), mAccountManagerAccount,
3068caeb46f09d6b50e573c91f29c7eb9bac3682a8fMarc Blank                            syncKey.getBytes());
3078caeb46f09d6b50e573c91f29c7eb9bac3682a8fMarc Blank                    userLog("SyncKey set to ", syncKey, " in CalendarProvider");
3088caeb46f09d6b50e573c91f29c7eb9bac3682a8fMarc Blank                } catch (RemoteException e) {
3098caeb46f09d6b50e573c91f29c7eb9bac3682a8fMarc Blank                    throw new IOException("Can't set SyncKey in CalendarProvider");
3108caeb46f09d6b50e573c91f29c7eb9bac3682a8fMarc Blank                }
3119a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank            }
3128caeb46f09d6b50e573c91f29c7eb9bac3682a8fMarc Blank            mMailbox.mSyncKey = syncKey;
3139a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank        }
3145862a85e17e81866ca82a9905577931947fbd44eMarc Blank    }
3155862a85e17e81866ca82a9905577931947fbd44eMarc Blank
31620d5e152abafc87b9840fdc36cc33c3ca243cd1bMarc Blank    public class EasCalendarSyncParser extends AbstractSyncParser {
3175862a85e17e81866ca82a9905577931947fbd44eMarc Blank
3185862a85e17e81866ca82a9905577931947fbd44eMarc Blank        String[] mBindArgument = new String[1];
3195862a85e17e81866ca82a9905577931947fbd44eMarc Blank        Uri mAccountUri;
3205862a85e17e81866ca82a9905577931947fbd44eMarc Blank        CalendarOperations mOps = new CalendarOperations();
3215862a85e17e81866ca82a9905577931947fbd44eMarc Blank
3225862a85e17e81866ca82a9905577931947fbd44eMarc Blank        public EasCalendarSyncParser(InputStream in, CalendarSyncAdapter adapter)
3235862a85e17e81866ca82a9905577931947fbd44eMarc Blank                throws IOException {
3245862a85e17e81866ca82a9905577931947fbd44eMarc Blank            super(in, adapter);
3255862a85e17e81866ca82a9905577931947fbd44eMarc Blank            setLoggingTag("CalendarParser");
3265862a85e17e81866ca82a9905577931947fbd44eMarc Blank            mAccountUri = Events.CONTENT_URI;
3275862a85e17e81866ca82a9905577931947fbd44eMarc Blank        }
3285862a85e17e81866ca82a9905577931947fbd44eMarc Blank
329ce8ef32983725abe7b7b672c5ae0b7d17b411f1dMarc Blank        private void addOrganizerToAttendees(CalendarOperations ops, long eventId,
330ce8ef32983725abe7b7b672c5ae0b7d17b411f1dMarc Blank                String organizerName, String organizerEmail) {
331ce8ef32983725abe7b7b672c5ae0b7d17b411f1dMarc Blank            // Handle the organizer (who IS an attendee on device, but NOT in EAS)
332ce8ef32983725abe7b7b672c5ae0b7d17b411f1dMarc Blank            if (organizerName != null || organizerEmail != null) {
333ce8ef32983725abe7b7b672c5ae0b7d17b411f1dMarc Blank                ContentValues attendeeCv = new ContentValues();
334ce8ef32983725abe7b7b672c5ae0b7d17b411f1dMarc Blank                if (organizerName != null) {
335ce8ef32983725abe7b7b672c5ae0b7d17b411f1dMarc Blank                    attendeeCv.put(Attendees.ATTENDEE_NAME, organizerName);
336ce8ef32983725abe7b7b672c5ae0b7d17b411f1dMarc Blank                }
337ce8ef32983725abe7b7b672c5ae0b7d17b411f1dMarc Blank                if (organizerEmail != null) {
338ce8ef32983725abe7b7b672c5ae0b7d17b411f1dMarc Blank                    attendeeCv.put(Attendees.ATTENDEE_EMAIL, organizerEmail);
339ce8ef32983725abe7b7b672c5ae0b7d17b411f1dMarc Blank                }
340ce8ef32983725abe7b7b672c5ae0b7d17b411f1dMarc Blank                attendeeCv.put(Attendees.ATTENDEE_RELATIONSHIP, Attendees.RELATIONSHIP_ORGANIZER);
341ce8ef32983725abe7b7b672c5ae0b7d17b411f1dMarc Blank                attendeeCv.put(Attendees.ATTENDEE_TYPE, Attendees.TYPE_REQUIRED);
342edcfd554728a27a7678306ea7432bd8676ec6990Marc Blank                attendeeCv.put(Attendees.ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_ACCEPTED);
343ce8ef32983725abe7b7b672c5ae0b7d17b411f1dMarc Blank                if (eventId < 0) {
344ce8ef32983725abe7b7b672c5ae0b7d17b411f1dMarc Blank                    ops.newAttendee(attendeeCv);
345ce8ef32983725abe7b7b672c5ae0b7d17b411f1dMarc Blank                } else {
346ce8ef32983725abe7b7b672c5ae0b7d17b411f1dMarc Blank                    ops.updatedAttendee(attendeeCv, eventId);
347ce8ef32983725abe7b7b672c5ae0b7d17b411f1dMarc Blank                }
348ce8ef32983725abe7b7b672c5ae0b7d17b411f1dMarc Blank            }
349ce8ef32983725abe7b7b672c5ae0b7d17b411f1dMarc Blank        }
350ce8ef32983725abe7b7b672c5ae0b7d17b411f1dMarc Blank
35120d5e152abafc87b9840fdc36cc33c3ca243cd1bMarc Blank        /**
352e0d5f11dde90b79cb4704647d71d07ca0faebf6fMarc Blank         * Set DTSTART, DTEND, DURATION and EVENT_TIMEZONE as appropriate for the given Event
35320d5e152abafc87b9840fdc36cc33c3ca243cd1bMarc Blank         * The follow rules are enforced by CalendarProvider2:
354e0d5f11dde90b79cb4704647d71d07ca0faebf6fMarc Blank         *   Events that aren't exceptions MUST have either 1) a DTEND or 2) a DURATION
35520d5e152abafc87b9840fdc36cc33c3ca243cd1bMarc Blank         *   Recurring events (i.e. events with RRULE) must have a DURATION
35620d5e152abafc87b9840fdc36cc33c3ca243cd1bMarc Blank         *   All-day recurring events MUST have a DURATION that is in the form P<n>D
35720d5e152abafc87b9840fdc36cc33c3ca243cd1bMarc Blank         *   Other events MAY have a DURATION in any valid form (we use P<n>M)
35820d5e152abafc87b9840fdc36cc33c3ca243cd1bMarc Blank         *   All-day events MUST have hour, minute, and second = 0; in addition, they must have
359e0d5f11dde90b79cb4704647d71d07ca0faebf6fMarc Blank         *   the EVENT_TIMEZONE set to UTC
360e0d5f11dde90b79cb4704647d71d07ca0faebf6fMarc Blank         *   Also, exceptions to all-day events need to have an ORIGINAL_INSTANCE_TIME that has
361e0d5f11dde90b79cb4704647d71d07ca0faebf6fMarc Blank         *   hour, minute, and second = 0 and be set in UTC
36220d5e152abafc87b9840fdc36cc33c3ca243cd1bMarc Blank         * @param cv the ContentValues for the Event
36320d5e152abafc87b9840fdc36cc33c3ca243cd1bMarc Blank         * @param startTime the start time for the Event
36420d5e152abafc87b9840fdc36cc33c3ca243cd1bMarc Blank         * @param endTime the end time for the Event
36520d5e152abafc87b9840fdc36cc33c3ca243cd1bMarc Blank         * @param allDayEvent whether this is an all day event (1) or not (0)
36620d5e152abafc87b9840fdc36cc33c3ca243cd1bMarc Blank         */
367e0d5f11dde90b79cb4704647d71d07ca0faebf6fMarc Blank        /*package*/ void setTimeRelatedValues(ContentValues cv, long startTime, long endTime,
368e0d5f11dde90b79cb4704647d71d07ca0faebf6fMarc Blank                int allDayEvent) {
369e0d5f11dde90b79cb4704647d71d07ca0faebf6fMarc Blank            // If there's no startTime, the event will be found to be invalid, so return
370e0d5f11dde90b79cb4704647d71d07ca0faebf6fMarc Blank            if (startTime < 0) return;
371e0d5f11dde90b79cb4704647d71d07ca0faebf6fMarc Blank            // EAS events can arrive without an end time, but CalendarProvider requires them
372e0d5f11dde90b79cb4704647d71d07ca0faebf6fMarc Blank            // so we'll default to 30 minutes; this will be superceded if this is an all-day event
373e0d5f11dde90b79cb4704647d71d07ca0faebf6fMarc Blank            if (endTime < 0) endTime = startTime + (30*MINUTES);
374e0d5f11dde90b79cb4704647d71d07ca0faebf6fMarc Blank
375e0d5f11dde90b79cb4704647d71d07ca0faebf6fMarc Blank            // If this is an all-day event, set hour, minute, and second to zero, and use UTC
37620d5e152abafc87b9840fdc36cc33c3ca243cd1bMarc Blank            if (allDayEvent != 0) {
377270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank                startTime = CalendarUtilities.getUtcAllDayCalendarTime(startTime, mLocalTimeZone);
378270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank                endTime = CalendarUtilities.getUtcAllDayCalendarTime(endTime, mLocalTimeZone);
379270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank                String originalTimeZone = cv.getAsString(Events.EVENT_TIMEZONE);
3807b0d3548f47b506357b9549af7c9ea230ba88caeMarc Blank                cv.put(EVENT_SAVED_TIMEZONE_COLUMN, originalTimeZone);
38120d5e152abafc87b9840fdc36cc33c3ca243cd1bMarc Blank                cv.put(Events.EVENT_TIMEZONE, UTC_TIMEZONE.getID());
38220d5e152abafc87b9840fdc36cc33c3ca243cd1bMarc Blank            }
383e0d5f11dde90b79cb4704647d71d07ca0faebf6fMarc Blank
384e0d5f11dde90b79cb4704647d71d07ca0faebf6fMarc Blank            // If this is an exception, and the original was an all-day event, make sure the
385e0d5f11dde90b79cb4704647d71d07ca0faebf6fMarc Blank            // original instance time has hour, minute, and second set to zero, and is in UTC
386e0d5f11dde90b79cb4704647d71d07ca0faebf6fMarc Blank            if (cv.containsKey(Events.ORIGINAL_INSTANCE_TIME) &&
387e0d5f11dde90b79cb4704647d71d07ca0faebf6fMarc Blank                    cv.containsKey(Events.ORIGINAL_ALL_DAY)) {
388e0d5f11dde90b79cb4704647d71d07ca0faebf6fMarc Blank                Integer ade = cv.getAsInteger(Events.ORIGINAL_ALL_DAY);
389e0d5f11dde90b79cb4704647d71d07ca0faebf6fMarc Blank                if (ade != null && ade != 0) {
390e0d5f11dde90b79cb4704647d71d07ca0faebf6fMarc Blank                    long exceptionTime = cv.getAsLong(Events.ORIGINAL_INSTANCE_TIME);
391e0d5f11dde90b79cb4704647d71d07ca0faebf6fMarc Blank                    GregorianCalendar cal = new GregorianCalendar(UTC_TIMEZONE);
392e0d5f11dde90b79cb4704647d71d07ca0faebf6fMarc Blank                    cal.setTimeInMillis(exceptionTime);
393e0d5f11dde90b79cb4704647d71d07ca0faebf6fMarc Blank                    cal.set(GregorianCalendar.HOUR_OF_DAY, 0);
394e0d5f11dde90b79cb4704647d71d07ca0faebf6fMarc Blank                    cal.set(GregorianCalendar.MINUTE, 0);
395e0d5f11dde90b79cb4704647d71d07ca0faebf6fMarc Blank                    cal.set(GregorianCalendar.SECOND, 0);
396e0d5f11dde90b79cb4704647d71d07ca0faebf6fMarc Blank                    cv.put(Events.ORIGINAL_INSTANCE_TIME, cal.getTimeInMillis());
397e0d5f11dde90b79cb4704647d71d07ca0faebf6fMarc Blank                }
398e0d5f11dde90b79cb4704647d71d07ca0faebf6fMarc Blank            }
399e0d5f11dde90b79cb4704647d71d07ca0faebf6fMarc Blank
40020d5e152abafc87b9840fdc36cc33c3ca243cd1bMarc Blank            // Always set DTSTART
40120d5e152abafc87b9840fdc36cc33c3ca243cd1bMarc Blank            cv.put(Events.DTSTART, startTime);
40220d5e152abafc87b9840fdc36cc33c3ca243cd1bMarc Blank            // For recurring events, set DURATION.  Use P<n>D format for all day events
40320d5e152abafc87b9840fdc36cc33c3ca243cd1bMarc Blank            if (cv.containsKey(Events.RRULE)) {
40420d5e152abafc87b9840fdc36cc33c3ca243cd1bMarc Blank                if (allDayEvent != 0) {
40520d5e152abafc87b9840fdc36cc33c3ca243cd1bMarc Blank                    cv.put(Events.DURATION, "P" + ((endTime - startTime) / DAYS) + "D");
40620d5e152abafc87b9840fdc36cc33c3ca243cd1bMarc Blank                }
40720d5e152abafc87b9840fdc36cc33c3ca243cd1bMarc Blank                else {
40820d5e152abafc87b9840fdc36cc33c3ca243cd1bMarc Blank                    cv.put(Events.DURATION, "P" + ((endTime - startTime) / MINUTES) + "M");
40920d5e152abafc87b9840fdc36cc33c3ca243cd1bMarc Blank                }
41020d5e152abafc87b9840fdc36cc33c3ca243cd1bMarc Blank            // For other events, set DTEND and LAST_DATE
41120d5e152abafc87b9840fdc36cc33c3ca243cd1bMarc Blank            } else {
41220d5e152abafc87b9840fdc36cc33c3ca243cd1bMarc Blank                cv.put(Events.DTEND, endTime);
41320d5e152abafc87b9840fdc36cc33c3ca243cd1bMarc Blank                cv.put(Events.LAST_DATE, endTime);
41420d5e152abafc87b9840fdc36cc33c3ca243cd1bMarc Blank            }
41520d5e152abafc87b9840fdc36cc33c3ca243cd1bMarc Blank        }
41620d5e152abafc87b9840fdc36cc33c3ca243cd1bMarc Blank
4175862a85e17e81866ca82a9905577931947fbd44eMarc Blank        public void addEvent(CalendarOperations ops, String serverId, boolean update)
4185862a85e17e81866ca82a9905577931947fbd44eMarc Blank                throws IOException {
4195862a85e17e81866ca82a9905577931947fbd44eMarc Blank            ContentValues cv = new ContentValues();
4205862a85e17e81866ca82a9905577931947fbd44eMarc Blank            cv.put(Events.CALENDAR_ID, mCalendarId);
4215862a85e17e81866ca82a9905577931947fbd44eMarc Blank            cv.put(Events._SYNC_ID, serverId);
42268f790bbbd80daf6221d356727ba8ec41aea22aaMarc Blank            cv.put(Events.HAS_ATTENDEE_DATA, 1);
42304c880a6b5ad041f172d4b1eeecc06d6a06e4141RoboErik            cv.put(Events.SYNC_DATA2, "0");
4245862a85e17e81866ca82a9905577931947fbd44eMarc Blank
4255862a85e17e81866ca82a9905577931947fbd44eMarc Blank            int allDayEvent = 0;
4265862a85e17e81866ca82a9905577931947fbd44eMarc Blank            String organizerName = null;
4275862a85e17e81866ca82a9905577931947fbd44eMarc Blank            String organizerEmail = null;
4285862a85e17e81866ca82a9905577931947fbd44eMarc Blank            int eventOffset = -1;
4296eaea8ad13360b1a1b4eafbfcc104a8813bf8855Marc Blank            int deleteOffset = -1;
430edcfd554728a27a7678306ea7432bd8676ec6990Marc Blank            int busyStatus = CalendarUtilities.BUSY_STATUS_TENTATIVE;
43165d022dc43e4461e86fd7bc143591f542b07428bMarc Blank            int responseType = CalendarUtilities.RESPONSE_TYPE_NONE;
4325862a85e17e81866ca82a9905577931947fbd44eMarc Blank
4335862a85e17e81866ca82a9905577931947fbd44eMarc Blank            boolean firstTag = true;
4345862a85e17e81866ca82a9905577931947fbd44eMarc Blank            long eventId = -1;
43544e7ae105430c0271e1af4aee5c76e5f828cfdf8Marc Blank            long startTime = -1;
43644e7ae105430c0271e1af4aee5c76e5f828cfdf8Marc Blank            long endTime = -1;
437270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank            TimeZone timeZone = null;
438cbaebd5d2e2be06473b18b00e15235ef36b63c9eMarc Blank
439cbaebd5d2e2be06473b18b00e15235ef36b63c9eMarc Blank            // Keep track of the attendees; exceptions will need them
440b76a67d3034e65fb6d5d2de2ed3728e44180e651Marc Blank            ArrayList<ContentValues> attendeeValues = new ArrayList<ContentValues>();
441cbaebd5d2e2be06473b18b00e15235ef36b63c9eMarc Blank            int reminderMins = -1;
44202738a0868dfd2bc893afe9a1cbf960db9acd03cMarc Blank            String dtStamp = null;
443ce8ef32983725abe7b7b672c5ae0b7d17b411f1dMarc Blank            boolean organizerAdded = false;
444cbaebd5d2e2be06473b18b00e15235ef36b63c9eMarc Blank
4455862a85e17e81866ca82a9905577931947fbd44eMarc Blank            while (nextTag(Tags.SYNC_APPLICATION_DATA) != END) {
4465862a85e17e81866ca82a9905577931947fbd44eMarc Blank                if (update && firstTag) {
4475862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    // Find the event that's being updated
4485862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    Cursor c = getServerIdCursor(serverId);
4495862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    long id = -1;
4505862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    try {
4517a882dc9d2ecded51759ad2b8a5f40f0a4545c18Marc Blank                        if (c != null && c.moveToFirst()) {
4525862a85e17e81866ca82a9905577931947fbd44eMarc Blank                            id = c.getLong(0);
4535862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        }
4545862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    } finally {
4557a882dc9d2ecded51759ad2b8a5f40f0a4545c18Marc Blank                        if (c != null) c.close();
4565862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    }
4575862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    if (id > 0) {
45802738a0868dfd2bc893afe9a1cbf960db9acd03cMarc Blank                        // DTSTAMP can come first, and we simply need to track it
45902738a0868dfd2bc893afe9a1cbf960db9acd03cMarc Blank                        if (tag == Tags.CALENDAR_DTSTAMP) {
46002738a0868dfd2bc893afe9a1cbf960db9acd03cMarc Blank                            dtStamp = getValue();
46102738a0868dfd2bc893afe9a1cbf960db9acd03cMarc Blank                            continue;
46202738a0868dfd2bc893afe9a1cbf960db9acd03cMarc Blank                        } else if (tag == Tags.CALENDAR_ATTENDEES) {
4636989716b639d274a98141674556ac9402be32ebeRoboErik                            // This is an attendees-only update; just
4646989716b639d274a98141674556ac9402be32ebeRoboErik                            // delete/re-add attendees
4655862a85e17e81866ca82a9905577931947fbd44eMarc Blank                            mBindArgument[0] = Long.toString(id);
466151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank                            ops.add(new Operation(ContentProviderOperation
467151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank                                    .newDelete(mAsSyncAdapterAttendees)
468151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank                                    .withSelection(ATTENDEES_EXCEPT_ORGANIZER, mBindArgument)));
4695862a85e17e81866ca82a9905577931947fbd44eMarc Blank                            eventId = id;
4705862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        } else {
4715862a85e17e81866ca82a9905577931947fbd44eMarc Blank                            // Otherwise, delete the original event and recreate it
4725862a85e17e81866ca82a9905577931947fbd44eMarc Blank                            userLog("Changing (delete/add) event ", serverId);
4733105fd9567d7894b71be5cb3acfdae7fb0c66c9aMarc Blank                            deleteOffset = ops.newDelete(id, serverId);
4745862a85e17e81866ca82a9905577931947fbd44eMarc Blank                            // Add a placeholder event so that associated tables can reference
4755862a85e17e81866ca82a9905577931947fbd44eMarc Blank                            // this as a back reference.  We add the event at the end of the method
4765862a85e17e81866ca82a9905577931947fbd44eMarc Blank                            eventOffset = ops.newEvent(PLACEHOLDER_OPERATION);
4775862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        }
4786eaea8ad13360b1a1b4eafbfcc104a8813bf8855Marc Blank                    } else {
4796eaea8ad13360b1a1b4eafbfcc104a8813bf8855Marc Blank                        // The changed item isn't found. We'll treat this as a new item
4806eaea8ad13360b1a1b4eafbfcc104a8813bf8855Marc Blank                        eventOffset = ops.newEvent(PLACEHOLDER_OPERATION);
4816eaea8ad13360b1a1b4eafbfcc104a8813bf8855Marc Blank                        userLog(TAG, "Changed item not found; treating as new.");
4825862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    }
4835862a85e17e81866ca82a9905577931947fbd44eMarc Blank                } else if (firstTag) {
4845862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    // Add a placeholder event so that associated tables can reference
4855862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    // this as a back reference.  We add the event at the end of the method
4865862a85e17e81866ca82a9905577931947fbd44eMarc Blank                   eventOffset = ops.newEvent(PLACEHOLDER_OPERATION);
4875862a85e17e81866ca82a9905577931947fbd44eMarc Blank                }
4885862a85e17e81866ca82a9905577931947fbd44eMarc Blank                firstTag = false;
4895862a85e17e81866ca82a9905577931947fbd44eMarc Blank                switch (tag) {
4905862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    case Tags.CALENDAR_ALL_DAY_EVENT:
4915862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        allDayEvent = getValueInt();
492270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank                        if (allDayEvent != 0 && timeZone != null) {
493270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank                            // If the event doesn't start at midnight local time, we won't consider
494270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank                            // this an all-day event in the local time zone (this is what OWA does)
495270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank                            GregorianCalendar cal = new GregorianCalendar(mLocalTimeZone);
496270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank                            cal.setTimeInMillis(startTime);
497270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank                            userLog("All-day event arrived in: " + timeZone.getID());
498270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank                            if (cal.get(GregorianCalendar.HOUR_OF_DAY) != 0 ||
499270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank                                    cal.get(GregorianCalendar.MINUTE) != 0) {
500270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank                                allDayEvent = 0;
501270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank                                userLog("Not an all-day event locally: " + mLocalTimeZone.getID());
502270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank                            }
503270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank                        }
5045862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        cv.put(Events.ALL_DAY, allDayEvent);
5055862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        break;
50677186bb1a174432ef272584374942d8b9228e39cMarc Blank                    case Tags.CALENDAR_ATTACHMENTS:
50777186bb1a174432ef272584374942d8b9228e39cMarc Blank                        attachmentsParser();
50877186bb1a174432ef272584374942d8b9228e39cMarc Blank                        break;
5095862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    case Tags.CALENDAR_ATTENDEES:
5105862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        // If eventId >= 0, this is an update; otherwise, a new Event
511cbaebd5d2e2be06473b18b00e15235ef36b63c9eMarc Blank                        attendeeValues = attendeesParser(ops, eventId);
5125862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        break;
5135862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    case Tags.BASE_BODY:
5145862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        cv.put(Events.DESCRIPTION, bodyParser());
5155862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        break;
5165862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    case Tags.CALENDAR_BODY:
5175862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        cv.put(Events.DESCRIPTION, getValue());
5185862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        break;
5195862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    case Tags.CALENDAR_TIME_ZONE:
520270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank                        timeZone = CalendarUtilities.tziStringToTimeZone(getValue());
521270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank                        if (timeZone == null) {
522270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank                            timeZone = mLocalTimeZone;
5235862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        }
524270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank                        cv.put(Events.EVENT_TIMEZONE, timeZone.getID());
5255862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        break;
5265862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    case Tags.CALENDAR_START_TIME:
5278e26c42accbaf72eff6694173496aba0e6aa37f6Mihai Preda                        startTime = Utility.parseDateTimeToMillis(getValue());
5285862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        break;
5295862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    case Tags.CALENDAR_END_TIME:
5308e26c42accbaf72eff6694173496aba0e6aa37f6Mihai Preda                        endTime = Utility.parseDateTimeToMillis(getValue());
5315862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        break;
5325862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    case Tags.CALENDAR_EXCEPTIONS:
533ce8ef32983725abe7b7b672c5ae0b7d17b411f1dMarc Blank                        // For exceptions to show the organizer, the organizer must be added before
534ce8ef32983725abe7b7b672c5ae0b7d17b411f1dMarc Blank                        // we call exceptionsParser
535ce8ef32983725abe7b7b672c5ae0b7d17b411f1dMarc Blank                        addOrganizerToAttendees(ops, eventId, organizerName, organizerEmail);
536ce8ef32983725abe7b7b672c5ae0b7d17b411f1dMarc Blank                        organizerAdded = true;
537e0d5f11dde90b79cb4704647d71d07ca0faebf6fMarc Blank                        exceptionsParser(ops, cv, attendeeValues, reminderMins, busyStatus,
538e0d5f11dde90b79cb4704647d71d07ca0faebf6fMarc Blank                                startTime, endTime);
5395862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        break;
5405862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    case Tags.CALENDAR_LOCATION:
5415862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        cv.put(Events.EVENT_LOCATION, getValue());
5425862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        break;
5435862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    case Tags.CALENDAR_RECURRENCE:
544f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank                        String rrule = recurrenceParser();
5455862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        if (rrule != null) {
5465862a85e17e81866ca82a9905577931947fbd44eMarc Blank                            cv.put(Events.RRULE, rrule);
5475862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        }
5485862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        break;
5495862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    case Tags.CALENDAR_ORGANIZER_EMAIL:
5505862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        organizerEmail = getValue();
5515862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        cv.put(Events.ORGANIZER, organizerEmail);
5525862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        break;
5535862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    case Tags.CALENDAR_SUBJECT:
5545862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        cv.put(Events.TITLE, getValue());
5555862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        break;
5565862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    case Tags.CALENDAR_SENSITIVITY:
5579e86eb14c6e1f7d7730f8ca6953fdfd95fe2b143RoboErik                        cv.put(Events.ACCESS_LEVEL, encodeVisibility(getValueInt()));
5585862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        break;
5595862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    case Tags.CALENDAR_ORGANIZER_NAME:
5605862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        organizerName = getValue();
5615862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        break;
56214045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank                    case Tags.CALENDAR_REMINDER_MINS_BEFORE:
563ae073cce37583f350ee4f1df4847c9c26eadb404Marc Blank                        // Save away whether this tag has content; Exchange 2010 sends an empty tag
564ae073cce37583f350ee4f1df4847c9c26eadb404Marc Blank                        // rather than not sending one (as with Ex07 and Ex03)
565ae073cce37583f350ee4f1df4847c9c26eadb404Marc Blank                        boolean hasContent = !noContent;
566cbaebd5d2e2be06473b18b00e15235ef36b63c9eMarc Blank                        reminderMins = getValueInt();
567ae073cce37583f350ee4f1df4847c9c26eadb404Marc Blank                        if (hasContent) {
568ae073cce37583f350ee4f1df4847c9c26eadb404Marc Blank                            ops.newReminder(reminderMins);
569ae073cce37583f350ee4f1df4847c9c26eadb404Marc Blank                            cv.put(Events.HAS_ALARM, 1);
570ae073cce37583f350ee4f1df4847c9c26eadb404Marc Blank                        }
57114045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank                        break;
57214045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank                    // The following are fields we should save (for changes), though they don't
57314045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank                    // relate to data used by CalendarProvider at this point
57414045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank                    case Tags.CALENDAR_UID:
57504c880a6b5ad041f172d4b1eeecc06d6a06e4141RoboErik                        cv.put(Events.SYNC_DATA2, getValue());
57614045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank                        break;
5775862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    case Tags.CALENDAR_DTSTAMP:
57802738a0868dfd2bc893afe9a1cbf960db9acd03cMarc Blank                        dtStamp = getValue();
5795862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        break;
5805862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    case Tags.CALENDAR_MEETING_STATUS:
581ac3283a6b46bf27a5b418638c95a13121ea180e1Marc Blank                        ops.newExtendedProperty(EXTENDED_PROPERTY_MEETING_STATUS, getValue());
5825862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        break;
5835862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    case Tags.CALENDAR_BUSY_STATUS:
584edcfd554728a27a7678306ea7432bd8676ec6990Marc Blank                        // We'll set the user's status in the Attendees table below
585edcfd554728a27a7678306ea7432bd8676ec6990Marc Blank                        // Don't set selfAttendeeStatus or CalendarProvider will create a duplicate
586edcfd554728a27a7678306ea7432bd8676ec6990Marc Blank                        // attendee!
587edcfd554728a27a7678306ea7432bd8676ec6990Marc Blank                        busyStatus = getValueInt();
5885862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        break;
58965d022dc43e4461e86fd7bc143591f542b07428bMarc Blank                    case Tags.CALENDAR_RESPONSE_TYPE:
59065d022dc43e4461e86fd7bc143591f542b07428bMarc Blank                        // EAS 14+ uses this for the user's response status; we'll use this instead
59165d022dc43e4461e86fd7bc143591f542b07428bMarc Blank                        // of busy status, if it appears
59265d022dc43e4461e86fd7bc143591f542b07428bMarc Blank                        responseType = getValueInt();
59365d022dc43e4461e86fd7bc143591f542b07428bMarc Blank                        break;
59414045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank                    case Tags.CALENDAR_CATEGORIES:
59514045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank                        String categories = categoriesParser(ops);
59614045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank                        if (categories.length() > 0) {
597ac3283a6b46bf27a5b418638c95a13121ea180e1Marc Blank                            ops.newExtendedProperty(EXTENDED_PROPERTY_CATEGORIES, categories);
59814045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank                        }
5995862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        break;
6005862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    default:
6015862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        skipTag();
6025862a85e17e81866ca82a9905577931947fbd44eMarc Blank                }
6035862a85e17e81866ca82a9905577931947fbd44eMarc Blank            }
6045862a85e17e81866ca82a9905577931947fbd44eMarc Blank
605e0d5f11dde90b79cb4704647d71d07ca0faebf6fMarc Blank            // Enforce CalendarProvider required properties
606e0d5f11dde90b79cb4704647d71d07ca0faebf6fMarc Blank            setTimeRelatedValues(cv, startTime, endTime, allDayEvent);
60720d5e152abafc87b9840fdc36cc33c3ca243cd1bMarc Blank
608e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank            // Set user's availability
609e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank            cv.put(Events.AVAILABILITY, CalendarUtilities.availabilityFromBusyStatus(busyStatus));
610e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank
611ce8ef32983725abe7b7b672c5ae0b7d17b411f1dMarc Blank            // If we haven't added the organizer to attendees, do it now
612ce8ef32983725abe7b7b672c5ae0b7d17b411f1dMarc Blank            if (!organizerAdded) {
613ce8ef32983725abe7b7b672c5ae0b7d17b411f1dMarc Blank                addOrganizerToAttendees(ops, eventId, organizerName, organizerEmail);
61468f790bbbd80daf6221d356727ba8ec41aea22aaMarc Blank            }
61568f790bbbd80daf6221d356727ba8ec41aea22aaMarc Blank
61682c64c70652d4853a92222ba46c213a8aaf3bb43Marc Blank            // Note that organizerEmail can be null with a DTSTAMP only change from the server
61782c64c70652d4853a92222ba46c213a8aaf3bb43Marc Blank            boolean selfOrganizer = (mEmailAddress.equals(organizerEmail));
618e588dbc379872f431426dbd3a2d5fe578e37e0b4Marc Blank
619332d08cc2fc37d9936a73e3a120125c60b0587d1Marc Blank            // Store email addresses of attendees (in a tokenizable string) in ExtendedProperties
620edcfd554728a27a7678306ea7432bd8676ec6990Marc Blank            // If the user is an attendee, set the attendee status using busyStatus (note that the
621edcfd554728a27a7678306ea7432bd8676ec6990Marc Blank            // busyStatus is inherited from the parent unless it's specified in the exception)
622edcfd554728a27a7678306ea7432bd8676ec6990Marc Blank            // Add the insert/update operation for each attendee (based on whether it's add/change)
623e588dbc379872f431426dbd3a2d5fe578e37e0b4Marc Blank            int numAttendees = attendeeValues.size();
624450dd050b8c23e2bd5c4288490d24d7ca69a7269Marc Blank            if (numAttendees > MAX_SYNCED_ATTENDEES) {
625e588dbc379872f431426dbd3a2d5fe578e37e0b4Marc Blank                // Indicate that we've redacted attendees.  If we're the organizer, disable edit
626e588dbc379872f431426dbd3a2d5fe578e37e0b4Marc Blank                // by setting organizerEmail to a bogus value and by setting the upsync prohibited
627e588dbc379872f431426dbd3a2d5fe578e37e0b4Marc Blank                // extended properly.
628e588dbc379872f431426dbd3a2d5fe578e37e0b4Marc Blank                // Note that we don't set ANY attendees if we're in this branch; however, the
629e588dbc379872f431426dbd3a2d5fe578e37e0b4Marc Blank                // organizer has already been included above, and WILL show up (which is good)
630e588dbc379872f431426dbd3a2d5fe578e37e0b4Marc Blank                if (eventId < 0) {
631e588dbc379872f431426dbd3a2d5fe578e37e0b4Marc Blank                    ops.newExtendedProperty(EXTENDED_PROPERTY_ATTENDEES_REDACTED, "1");
632e588dbc379872f431426dbd3a2d5fe578e37e0b4Marc Blank                    if (selfOrganizer) {
633e588dbc379872f431426dbd3a2d5fe578e37e0b4Marc Blank                        ops.newExtendedProperty(EXTENDED_PROPERTY_UPSYNC_PROHIBITED, "1");
634e588dbc379872f431426dbd3a2d5fe578e37e0b4Marc Blank                    }
635e588dbc379872f431426dbd3a2d5fe578e37e0b4Marc Blank                } else {
636e588dbc379872f431426dbd3a2d5fe578e37e0b4Marc Blank                    ops.updatedExtendedProperty(EXTENDED_PROPERTY_ATTENDEES_REDACTED, "1", eventId);
637e588dbc379872f431426dbd3a2d5fe578e37e0b4Marc Blank                    if (selfOrganizer) {
638e588dbc379872f431426dbd3a2d5fe578e37e0b4Marc Blank                        ops.updatedExtendedProperty(EXTENDED_PROPERTY_UPSYNC_PROHIBITED, "1",
639e588dbc379872f431426dbd3a2d5fe578e37e0b4Marc Blank                                eventId);
640e588dbc379872f431426dbd3a2d5fe578e37e0b4Marc Blank                    }
641e588dbc379872f431426dbd3a2d5fe578e37e0b4Marc Blank                }
642e588dbc379872f431426dbd3a2d5fe578e37e0b4Marc Blank                if (selfOrganizer) {
643e588dbc379872f431426dbd3a2d5fe578e37e0b4Marc Blank                    organizerEmail = BOGUS_ORGANIZER_EMAIL;
6444916fd34b50185c675ea857a3681d1decb9d1474Marc Blank                    cv.put(Events.ORGANIZER, organizerEmail);
645e588dbc379872f431426dbd3a2d5fe578e37e0b4Marc Blank                }
646e588dbc379872f431426dbd3a2d5fe578e37e0b4Marc Blank                // Tell UI that we don't have any attendees
647e588dbc379872f431426dbd3a2d5fe578e37e0b4Marc Blank                cv.put(Events.HAS_ATTENDEE_DATA, "0");
648e588dbc379872f431426dbd3a2d5fe578e37e0b4Marc Blank                mService.userLog("Maximum number of attendees exceeded; redacting");
649e588dbc379872f431426dbd3a2d5fe578e37e0b4Marc Blank            } else if (numAttendees > 0) {
650332d08cc2fc37d9936a73e3a120125c60b0587d1Marc Blank                StringBuilder sb = new StringBuilder();
651332d08cc2fc37d9936a73e3a120125c60b0587d1Marc Blank                for (ContentValues attendee: attendeeValues) {
652edcfd554728a27a7678306ea7432bd8676ec6990Marc Blank                    String attendeeEmail = attendee.getAsString(Attendees.ATTENDEE_EMAIL);
653edcfd554728a27a7678306ea7432bd8676ec6990Marc Blank                    sb.append(attendeeEmail);
654332d08cc2fc37d9936a73e3a120125c60b0587d1Marc Blank                    sb.append(ATTENDEE_TOKENIZER_DELIMITER);
655951b8877f4d93f2f1bd4249e4ff024fb37fc493fMarc Blank                    if (mEmailAddress.equalsIgnoreCase(attendeeEmail)) {
65665d022dc43e4461e86fd7bc143591f542b07428bMarc Blank                        int attendeeStatus;
65765d022dc43e4461e86fd7bc143591f542b07428bMarc Blank                        // We'll use the response type (EAS 14), if we've got one; otherwise, we'll
65865d022dc43e4461e86fd7bc143591f542b07428bMarc Blank                        // try to infer it from busy status
65965d022dc43e4461e86fd7bc143591f542b07428bMarc Blank                        if (responseType != CalendarUtilities.RESPONSE_TYPE_NONE) {
66065d022dc43e4461e86fd7bc143591f542b07428bMarc Blank                            attendeeStatus =
66165d022dc43e4461e86fd7bc143591f542b07428bMarc Blank                                CalendarUtilities.attendeeStatusFromResponseType(responseType);
662f9d3d43800dcb522a7c492e96d490eca9f120e43Marc Blank                        } else if (!update) {
663f9d3d43800dcb522a7c492e96d490eca9f120e43Marc Blank                            // For new events in EAS < 14, we have no idea what the busy status
664f9d3d43800dcb522a7c492e96d490eca9f120e43Marc Blank                            // means, so we show "none", allowing the user to select an option.
665f961cb24734a23914ca5d0d0ab0b1a6343211313Marc Blank                            attendeeStatus = Attendees.ATTENDEE_STATUS_NONE;
66665d022dc43e4461e86fd7bc143591f542b07428bMarc Blank                        } else {
667f9d3d43800dcb522a7c492e96d490eca9f120e43Marc Blank                            // For updated events, we'll try to infer the attendee status from the
668f9d3d43800dcb522a7c492e96d490eca9f120e43Marc Blank                            // busy status
66965d022dc43e4461e86fd7bc143591f542b07428bMarc Blank                            attendeeStatus =
67065d022dc43e4461e86fd7bc143591f542b07428bMarc Blank                                CalendarUtilities.attendeeStatusFromBusyStatus(busyStatus);
67165d022dc43e4461e86fd7bc143591f542b07428bMarc Blank                        }
6725a75c42d793d1d4234f0fd5cd00ea34bd7c0625fMarc Blank                        attendee.put(Attendees.ATTENDEE_STATUS, attendeeStatus);
6735a75c42d793d1d4234f0fd5cd00ea34bd7c0625fMarc Blank                        // If we're an attendee, save away our initial attendee status in the
6745a75c42d793d1d4234f0fd5cd00ea34bd7c0625fMarc Blank                        // event's ExtendedProperties (we look for differences between this and
6755a75c42d793d1d4234f0fd5cd00ea34bd7c0625fMarc Blank                        // the user's current attendee status to determine whether an email needs
6765a75c42d793d1d4234f0fd5cd00ea34bd7c0625fMarc Blank                        // to be sent to the organizer)
677ac3283a6b46bf27a5b418638c95a13121ea180e1Marc Blank                        // organizerEmail will be null in the case that this is an attendees-only
678ac3283a6b46bf27a5b418638c95a13121ea180e1Marc Blank                        // change from the server
679ac3283a6b46bf27a5b418638c95a13121ea180e1Marc Blank                        if (organizerEmail == null ||
680ac3283a6b46bf27a5b418638c95a13121ea180e1Marc Blank                                !organizerEmail.equalsIgnoreCase(attendeeEmail)) {
681ac3283a6b46bf27a5b418638c95a13121ea180e1Marc Blank                            if (eventId < 0) {
682ac3283a6b46bf27a5b418638c95a13121ea180e1Marc Blank                                ops.newExtendedProperty(EXTENDED_PROPERTY_USER_ATTENDEE_STATUS,
683ac3283a6b46bf27a5b418638c95a13121ea180e1Marc Blank                                        Integer.toString(attendeeStatus));
684ac3283a6b46bf27a5b418638c95a13121ea180e1Marc Blank                            } else {
685ac3283a6b46bf27a5b418638c95a13121ea180e1Marc Blank                                ops.updatedExtendedProperty(EXTENDED_PROPERTY_USER_ATTENDEE_STATUS,
686ac3283a6b46bf27a5b418638c95a13121ea180e1Marc Blank                                        Integer.toString(attendeeStatus), eventId);
687ac3283a6b46bf27a5b418638c95a13121ea180e1Marc Blank
688ac3283a6b46bf27a5b418638c95a13121ea180e1Marc Blank                            }
6895a75c42d793d1d4234f0fd5cd00ea34bd7c0625fMarc Blank                        }
690edcfd554728a27a7678306ea7432bd8676ec6990Marc Blank                    }
691edcfd554728a27a7678306ea7432bd8676ec6990Marc Blank                    if (eventId < 0) {
692edcfd554728a27a7678306ea7432bd8676ec6990Marc Blank                        ops.newAttendee(attendee);
693edcfd554728a27a7678306ea7432bd8676ec6990Marc Blank                    } else {
694edcfd554728a27a7678306ea7432bd8676ec6990Marc Blank                        ops.updatedAttendee(attendee, eventId);
695edcfd554728a27a7678306ea7432bd8676ec6990Marc Blank                    }
696332d08cc2fc37d9936a73e3a120125c60b0587d1Marc Blank                }
697ac3283a6b46bf27a5b418638c95a13121ea180e1Marc Blank                if (eventId < 0) {
698ac3283a6b46bf27a5b418638c95a13121ea180e1Marc Blank                    ops.newExtendedProperty(EXTENDED_PROPERTY_ATTENDEES, sb.toString());
699e588dbc379872f431426dbd3a2d5fe578e37e0b4Marc Blank                    ops.newExtendedProperty(EXTENDED_PROPERTY_ATTENDEES_REDACTED, "0");
700e588dbc379872f431426dbd3a2d5fe578e37e0b4Marc Blank                    ops.newExtendedProperty(EXTENDED_PROPERTY_UPSYNC_PROHIBITED, "0");
701ac3283a6b46bf27a5b418638c95a13121ea180e1Marc Blank                } else {
702ac3283a6b46bf27a5b418638c95a13121ea180e1Marc Blank                    ops.updatedExtendedProperty(EXTENDED_PROPERTY_ATTENDEES, sb.toString(),
703ac3283a6b46bf27a5b418638c95a13121ea180e1Marc Blank                            eventId);
704e588dbc379872f431426dbd3a2d5fe578e37e0b4Marc Blank                    ops.updatedExtendedProperty(EXTENDED_PROPERTY_ATTENDEES_REDACTED, "0", eventId);
705e588dbc379872f431426dbd3a2d5fe578e37e0b4Marc Blank                    ops.updatedExtendedProperty(EXTENDED_PROPERTY_UPSYNC_PROHIBITED, "0", eventId);
706ac3283a6b46bf27a5b418638c95a13121ea180e1Marc Blank                }
707332d08cc2fc37d9936a73e3a120125c60b0587d1Marc Blank            }
708332d08cc2fc37d9936a73e3a120125c60b0587d1Marc Blank
7095862a85e17e81866ca82a9905577931947fbd44eMarc Blank            // Put the real event in the proper place in the ops ArrayList
7105862a85e17e81866ca82a9905577931947fbd44eMarc Blank            if (eventOffset >= 0) {
71102738a0868dfd2bc893afe9a1cbf960db9acd03cMarc Blank                // Store away the DTSTAMP here
71202738a0868dfd2bc893afe9a1cbf960db9acd03cMarc Blank                if (dtStamp != null) {
713ac3283a6b46bf27a5b418638c95a13121ea180e1Marc Blank                    ops.newExtendedProperty(EXTENDED_PROPERTY_DTSTAMP, dtStamp);
71402738a0868dfd2bc893afe9a1cbf960db9acd03cMarc Blank                }
71502738a0868dfd2bc893afe9a1cbf960db9acd03cMarc Blank
716e0d5f11dde90b79cb4704647d71d07ca0faebf6fMarc Blank                if (isValidEventValues(cv)) {
7176989716b639d274a98141674556ac9402be32ebeRoboErik                    ops.set(eventOffset,
718151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank                            new Operation(ContentProviderOperation
719151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank                                    .newInsert(mAsSyncAdapterEvents).withValues(cv)));
7206eaea8ad13360b1a1b4eafbfcc104a8813bf8855Marc Blank                } else {
7216eaea8ad13360b1a1b4eafbfcc104a8813bf8855Marc Blank                    // If we can't add this event (it's invalid), remove all of the inserts
7226eaea8ad13360b1a1b4eafbfcc104a8813bf8855Marc Blank                    // we've built for it
7236eaea8ad13360b1a1b4eafbfcc104a8813bf8855Marc Blank                    int cnt = ops.mCount - eventOffset;
7246eaea8ad13360b1a1b4eafbfcc104a8813bf8855Marc Blank                    userLog(TAG, "Removing " + cnt + " inserts from mOps");
7256eaea8ad13360b1a1b4eafbfcc104a8813bf8855Marc Blank                    for (int i = 0; i < cnt; i++) {
7266eaea8ad13360b1a1b4eafbfcc104a8813bf8855Marc Blank                        ops.remove(eventOffset);
7276eaea8ad13360b1a1b4eafbfcc104a8813bf8855Marc Blank                    }
7286eaea8ad13360b1a1b4eafbfcc104a8813bf8855Marc Blank                    ops.mCount = eventOffset;
7296eaea8ad13360b1a1b4eafbfcc104a8813bf8855Marc Blank                    // If this is a change, we need to also remove the deletion that comes
7306eaea8ad13360b1a1b4eafbfcc104a8813bf8855Marc Blank                    // before the addition
7316eaea8ad13360b1a1b4eafbfcc104a8813bf8855Marc Blank                    if (deleteOffset >= 0) {
7323105fd9567d7894b71be5cb3acfdae7fb0c66c9aMarc Blank                        // Remove the deletion
7336eaea8ad13360b1a1b4eafbfcc104a8813bf8855Marc Blank                        ops.remove(deleteOffset);
7343105fd9567d7894b71be5cb3acfdae7fb0c66c9aMarc Blank                        // And the deletion of exceptions
7353105fd9567d7894b71be5cb3acfdae7fb0c66c9aMarc Blank                        ops.remove(deleteOffset);
7363105fd9567d7894b71be5cb3acfdae7fb0c66c9aMarc Blank                        userLog(TAG, "Removing deletion ops from mOps");
7376eaea8ad13360b1a1b4eafbfcc104a8813bf8855Marc Blank                        ops.mCount = deleteOffset;
7386eaea8ad13360b1a1b4eafbfcc104a8813bf8855Marc Blank                    }
7396eaea8ad13360b1a1b4eafbfcc104a8813bf8855Marc Blank                }
7405862a85e17e81866ca82a9905577931947fbd44eMarc Blank            }
741151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank            // Mark the end of the event
742151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank            addSeparatorOperation(ops, Events.CONTENT_URI);
7435862a85e17e81866ca82a9905577931947fbd44eMarc Blank        }
7445862a85e17e81866ca82a9905577931947fbd44eMarc Blank
745e0d5f11dde90b79cb4704647d71d07ca0faebf6fMarc Blank        private void logEventColumns(ContentValues cv, String reason) {
746e0d5f11dde90b79cb4704647d71d07ca0faebf6fMarc Blank            if (Eas.USER_LOG) {
747e0d5f11dde90b79cb4704647d71d07ca0faebf6fMarc Blank                StringBuilder sb =
748e0d5f11dde90b79cb4704647d71d07ca0faebf6fMarc Blank                    new StringBuilder("Event invalid, " + reason + ", skipping: Columns = ");
7496eaea8ad13360b1a1b4eafbfcc104a8813bf8855Marc Blank                for (Entry<String, Object> entry: cv.valueSet()) {
7506eaea8ad13360b1a1b4eafbfcc104a8813bf8855Marc Blank                    sb.append(entry.getKey());
751e0d5f11dde90b79cb4704647d71d07ca0faebf6fMarc Blank                    sb.append('/');
7526eaea8ad13360b1a1b4eafbfcc104a8813bf8855Marc Blank                }
7536eaea8ad13360b1a1b4eafbfcc104a8813bf8855Marc Blank                userLog(TAG, sb.toString());
754e0d5f11dde90b79cb4704647d71d07ca0faebf6fMarc Blank            }
755e0d5f11dde90b79cb4704647d71d07ca0faebf6fMarc Blank        }
756e0d5f11dde90b79cb4704647d71d07ca0faebf6fMarc Blank
757e0d5f11dde90b79cb4704647d71d07ca0faebf6fMarc Blank        /*package*/ boolean isValidEventValues(ContentValues cv) {
758e0d5f11dde90b79cb4704647d71d07ca0faebf6fMarc Blank            boolean isException = cv.containsKey(Events.ORIGINAL_INSTANCE_TIME);
759e0d5f11dde90b79cb4704647d71d07ca0faebf6fMarc Blank            // All events require DTSTART
760e0d5f11dde90b79cb4704647d71d07ca0faebf6fMarc Blank            if (!cv.containsKey(Events.DTSTART)) {
761e0d5f11dde90b79cb4704647d71d07ca0faebf6fMarc Blank                logEventColumns(cv, "DTSTART missing");
762e0d5f11dde90b79cb4704647d71d07ca0faebf6fMarc Blank                return false;
763e0d5f11dde90b79cb4704647d71d07ca0faebf6fMarc Blank            // If we're a top-level event, we must have _SYNC_DATA (uid)
76404c880a6b5ad041f172d4b1eeecc06d6a06e4141RoboErik            } else if (!isException && !cv.containsKey(Events.SYNC_DATA2)) {
765e0d5f11dde90b79cb4704647d71d07ca0faebf6fMarc Blank                logEventColumns(cv, "_SYNC_DATA missing");
766e0d5f11dde90b79cb4704647d71d07ca0faebf6fMarc Blank                return false;
767e0d5f11dde90b79cb4704647d71d07ca0faebf6fMarc Blank            // We must also have DTEND or DURATION if we're not an exception
768e0d5f11dde90b79cb4704647d71d07ca0faebf6fMarc Blank            } else if (!isException && !cv.containsKey(Events.DTEND) &&
769e0d5f11dde90b79cb4704647d71d07ca0faebf6fMarc Blank                    !cv.containsKey(Events.DURATION)) {
770e0d5f11dde90b79cb4704647d71d07ca0faebf6fMarc Blank                logEventColumns(cv, "DTEND/DURATION missing");
7716eaea8ad13360b1a1b4eafbfcc104a8813bf8855Marc Blank                return false;
772e0d5f11dde90b79cb4704647d71d07ca0faebf6fMarc Blank            // Exceptions require DTEND
773450dd050b8c23e2bd5c4288490d24d7ca69a7269Marc Blank            } else if (isException && !cv.containsKey(Events.DTEND)) {
774e0d5f11dde90b79cb4704647d71d07ca0faebf6fMarc Blank                logEventColumns(cv, "Exception missing DTEND");
775e0d5f11dde90b79cb4704647d71d07ca0faebf6fMarc Blank                return false;
776e0d5f11dde90b79cb4704647d71d07ca0faebf6fMarc Blank            // If this is a recurrence, we need a DURATION (in days if an all-day event)
77720d5e152abafc87b9840fdc36cc33c3ca243cd1bMarc Blank            } else if (cv.containsKey(Events.RRULE)) {
77820d5e152abafc87b9840fdc36cc33c3ca243cd1bMarc Blank                String duration = cv.getAsString(Events.DURATION);
77920d5e152abafc87b9840fdc36cc33c3ca243cd1bMarc Blank                if (duration == null) return false;
78020d5e152abafc87b9840fdc36cc33c3ca243cd1bMarc Blank                if (cv.containsKey(Events.ALL_DAY)) {
78120d5e152abafc87b9840fdc36cc33c3ca243cd1bMarc Blank                    Integer ade = cv.getAsInteger(Events.ALL_DAY);
782e0d5f11dde90b79cb4704647d71d07ca0faebf6fMarc Blank                    if (ade != null && ade != 0 && !duration.endsWith("D")) {
78320d5e152abafc87b9840fdc36cc33c3ca243cd1bMarc Blank                        return false;
78420d5e152abafc87b9840fdc36cc33c3ca243cd1bMarc Blank                    }
78520d5e152abafc87b9840fdc36cc33c3ca243cd1bMarc Blank                }
7866eaea8ad13360b1a1b4eafbfcc104a8813bf8855Marc Blank            }
7876eaea8ad13360b1a1b4eafbfcc104a8813bf8855Marc Blank            return true;
7886eaea8ad13360b1a1b4eafbfcc104a8813bf8855Marc Blank        }
7896eaea8ad13360b1a1b4eafbfcc104a8813bf8855Marc Blank
790f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank        public String recurrenceParser() throws IOException {
7915862a85e17e81866ca82a9905577931947fbd44eMarc Blank            // Turn this information into an RRULE
7925862a85e17e81866ca82a9905577931947fbd44eMarc Blank            int type = -1;
7935862a85e17e81866ca82a9905577931947fbd44eMarc Blank            int occurrences = -1;
7945862a85e17e81866ca82a9905577931947fbd44eMarc Blank            int interval = -1;
7955862a85e17e81866ca82a9905577931947fbd44eMarc Blank            int dow = -1;
7965862a85e17e81866ca82a9905577931947fbd44eMarc Blank            int dom = -1;
7975862a85e17e81866ca82a9905577931947fbd44eMarc Blank            int wom = -1;
7985862a85e17e81866ca82a9905577931947fbd44eMarc Blank            int moy = -1;
7995862a85e17e81866ca82a9905577931947fbd44eMarc Blank            String until = null;
8005862a85e17e81866ca82a9905577931947fbd44eMarc Blank
8015862a85e17e81866ca82a9905577931947fbd44eMarc Blank            while (nextTag(Tags.CALENDAR_RECURRENCE) != END) {
8025862a85e17e81866ca82a9905577931947fbd44eMarc Blank                switch (tag) {
8035862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    case Tags.CALENDAR_RECURRENCE_TYPE:
8045862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        type = getValueInt();
8055862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        break;
8065862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    case Tags.CALENDAR_RECURRENCE_INTERVAL:
8075862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        interval = getValueInt();
8085862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        break;
8095862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    case Tags.CALENDAR_RECURRENCE_OCCURRENCES:
8105862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        occurrences = getValueInt();
8115862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        break;
8125862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    case Tags.CALENDAR_RECURRENCE_DAYOFWEEK:
8135862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        dow = getValueInt();
8145862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        break;
8155862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    case Tags.CALENDAR_RECURRENCE_DAYOFMONTH:
8165862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        dom = getValueInt();
8175862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        break;
8185862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    case Tags.CALENDAR_RECURRENCE_WEEKOFMONTH:
8195862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        wom = getValueInt();
8205862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        break;
8215862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    case Tags.CALENDAR_RECURRENCE_MONTHOFYEAR:
8225862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        moy = getValueInt();
8235862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        break;
8245862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    case Tags.CALENDAR_RECURRENCE_UNTIL:
8255862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        until = getValue();
8265862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        break;
8275862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    default:
8285862a85e17e81866ca82a9905577931947fbd44eMarc Blank                       skipTag();
8295862a85e17e81866ca82a9905577931947fbd44eMarc Blank                }
8305862a85e17e81866ca82a9905577931947fbd44eMarc Blank            }
8315862a85e17e81866ca82a9905577931947fbd44eMarc Blank
8325862a85e17e81866ca82a9905577931947fbd44eMarc Blank            return CalendarUtilities.rruleFromRecurrence(type, occurrences, interval,
8335862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    dow, dom, wom, moy, until);
8345862a85e17e81866ca82a9905577931947fbd44eMarc Blank        }
8355862a85e17e81866ca82a9905577931947fbd44eMarc Blank
836cbaebd5d2e2be06473b18b00e15235ef36b63c9eMarc Blank        private void exceptionParser(CalendarOperations ops, ContentValues parentCv,
837e0d5f11dde90b79cb4704647d71d07ca0faebf6fMarc Blank                ArrayList<ContentValues> attendeeValues, int reminderMins, int busyStatus,
838e0d5f11dde90b79cb4704647d71d07ca0faebf6fMarc Blank                long startTime, long endTime) throws IOException {
8395862a85e17e81866ca82a9905577931947fbd44eMarc Blank            ContentValues cv = new ContentValues();
8405862a85e17e81866ca82a9905577931947fbd44eMarc Blank            cv.put(Events.CALENDAR_ID, mCalendarId);
8415862a85e17e81866ca82a9905577931947fbd44eMarc Blank
8425862a85e17e81866ca82a9905577931947fbd44eMarc Blank            // It appears that these values have to be copied from the parent if they are to appear
8435862a85e17e81866ca82a9905577931947fbd44eMarc Blank            // Note that they can be overridden below
8445862a85e17e81866ca82a9905577931947fbd44eMarc Blank            cv.put(Events.ORGANIZER, parentCv.getAsString(Events.ORGANIZER));
8455862a85e17e81866ca82a9905577931947fbd44eMarc Blank            cv.put(Events.TITLE, parentCv.getAsString(Events.TITLE));
846cbaebd5d2e2be06473b18b00e15235ef36b63c9eMarc Blank            cv.put(Events.DESCRIPTION, parentCv.getAsString(Events.DESCRIPTION));
8475862a85e17e81866ca82a9905577931947fbd44eMarc Blank            cv.put(Events.ORIGINAL_ALL_DAY, parentCv.getAsInteger(Events.ALL_DAY));
848cbaebd5d2e2be06473b18b00e15235ef36b63c9eMarc Blank            cv.put(Events.EVENT_LOCATION, parentCv.getAsString(Events.EVENT_LOCATION));
8499e86eb14c6e1f7d7730f8ca6953fdfd95fe2b143RoboErik            cv.put(Events.ACCESS_LEVEL, parentCv.getAsString(Events.ACCESS_LEVEL));
8506953cc6a49880da3c5de8d6786d71e2101a9bec1Marc Blank            cv.put(Events.EVENT_TIMEZONE, parentCv.getAsString(Events.EVENT_TIMEZONE));
8518aa6c699706428efa918ffc6b5ec84f2a4c085afMarc Blank            // Exceptions should always have this set to zero, since EAS has no concept of
8528aa6c699706428efa918ffc6b5ec84f2a4c085afMarc Blank            // separate attendee lists for exceptions; if we fail to do this, then the UI will
8538aa6c699706428efa918ffc6b5ec84f2a4c085afMarc Blank            // allow the user to change attendee data, and this change would never get reflected
8548aa6c699706428efa918ffc6b5ec84f2a4c085afMarc Blank            // on the server.
8558aa6c699706428efa918ffc6b5ec84f2a4c085afMarc Blank            cv.put(Events.HAS_ATTENDEE_DATA, 0);
8565862a85e17e81866ca82a9905577931947fbd44eMarc Blank
85720d5e152abafc87b9840fdc36cc33c3ca243cd1bMarc Blank            int allDayEvent = 0;
85820d5e152abafc87b9840fdc36cc33c3ca243cd1bMarc Blank
8595862a85e17e81866ca82a9905577931947fbd44eMarc Blank            // This column is the key that links the exception to the serverId
8609e86eb14c6e1f7d7730f8ca6953fdfd95fe2b143RoboErik            cv.put(Events.ORIGINAL_SYNC_ID, parentCv.getAsString(Events._SYNC_ID));
8615862a85e17e81866ca82a9905577931947fbd44eMarc Blank
8622f5a2f7bf5b95c1b4ff8b4b43af2d20cdafdcc89Marc Blank            String exceptionStartTime = "_noStartTime";
8635862a85e17e81866ca82a9905577931947fbd44eMarc Blank            while (nextTag(Tags.SYNC_APPLICATION_DATA) != END) {
8645862a85e17e81866ca82a9905577931947fbd44eMarc Blank                switch (tag) {
86577186bb1a174432ef272584374942d8b9228e39cMarc Blank                    case Tags.CALENDAR_ATTACHMENTS:
86677186bb1a174432ef272584374942d8b9228e39cMarc Blank                        attachmentsParser();
86777186bb1a174432ef272584374942d8b9228e39cMarc Blank                        break;
8685862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    case Tags.CALENDAR_EXCEPTION_START_TIME:
8692f5a2f7bf5b95c1b4ff8b4b43af2d20cdafdcc89Marc Blank                        exceptionStartTime = getValue();
8705862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        cv.put(Events.ORIGINAL_INSTANCE_TIME,
8718e26c42accbaf72eff6694173496aba0e6aa37f6Mihai Preda                                Utility.parseDateTimeToMillis(exceptionStartTime));
8725862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        break;
8735862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    case Tags.CALENDAR_EXCEPTION_IS_DELETED:
8745862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        if (getValueInt() == 1) {
8755862a85e17e81866ca82a9905577931947fbd44eMarc Blank                            cv.put(Events.STATUS, Events.STATUS_CANCELED);
8765862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        }
8775862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        break;
8785862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    case Tags.CALENDAR_ALL_DAY_EVENT:
87920d5e152abafc87b9840fdc36cc33c3ca243cd1bMarc Blank                        allDayEvent = getValueInt();
88020d5e152abafc87b9840fdc36cc33c3ca243cd1bMarc Blank                        cv.put(Events.ALL_DAY, allDayEvent);
8815862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        break;
8825862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    case Tags.BASE_BODY:
8835862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        cv.put(Events.DESCRIPTION, bodyParser());
8845862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        break;
8855862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    case Tags.CALENDAR_BODY:
8865862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        cv.put(Events.DESCRIPTION, getValue());
8875862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        break;
8885862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    case Tags.CALENDAR_START_TIME:
88920d5e152abafc87b9840fdc36cc33c3ca243cd1bMarc Blank                        startTime = Utility.parseDateTimeToMillis(getValue());
8905862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        break;
8915862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    case Tags.CALENDAR_END_TIME:
89220d5e152abafc87b9840fdc36cc33c3ca243cd1bMarc Blank                        endTime = Utility.parseDateTimeToMillis(getValue());
8935862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        break;
8945862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    case Tags.CALENDAR_LOCATION:
8955862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        cv.put(Events.EVENT_LOCATION, getValue());
8965862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        break;
8975862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    case Tags.CALENDAR_RECURRENCE:
898f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank                        String rrule = recurrenceParser();
8995862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        if (rrule != null) {
9005862a85e17e81866ca82a9905577931947fbd44eMarc Blank                            cv.put(Events.RRULE, rrule);
9015862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        }
9025862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        break;
9035862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    case Tags.CALENDAR_SUBJECT:
9045862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        cv.put(Events.TITLE, getValue());
9055862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        break;
9065862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    case Tags.CALENDAR_SENSITIVITY:
9079e86eb14c6e1f7d7730f8ca6953fdfd95fe2b143RoboErik                        cv.put(Events.ACCESS_LEVEL, encodeVisibility(getValueInt()));
9085862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        break;
909dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank                    case Tags.CALENDAR_BUSY_STATUS:
910edcfd554728a27a7678306ea7432bd8676ec6990Marc Blank                        busyStatus = getValueInt();
911edcfd554728a27a7678306ea7432bd8676ec6990Marc Blank                        // Don't set selfAttendeeStatus or CalendarProvider will create a duplicate
912edcfd554728a27a7678306ea7432bd8676ec6990Marc Blank                        // attendee!
913dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank                        break;
9145862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        // TODO How to handle these items that are linked to event id!
9155862a85e17e81866ca82a9905577931947fbd44eMarc Blank//                    case Tags.CALENDAR_DTSTAMP:
9165862a85e17e81866ca82a9905577931947fbd44eMarc Blank//                        ops.newExtendedProperty("dtstamp", getValue());
9175862a85e17e81866ca82a9905577931947fbd44eMarc Blank//                        break;
9185862a85e17e81866ca82a9905577931947fbd44eMarc Blank//                    case Tags.CALENDAR_REMINDER_MINS_BEFORE:
9195862a85e17e81866ca82a9905577931947fbd44eMarc Blank//                        ops.newReminder(getValueInt());
9205862a85e17e81866ca82a9905577931947fbd44eMarc Blank//                        break;
9215862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    default:
9225862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        skipTag();
9235862a85e17e81866ca82a9905577931947fbd44eMarc Blank                }
9245862a85e17e81866ca82a9905577931947fbd44eMarc Blank            }
9255862a85e17e81866ca82a9905577931947fbd44eMarc Blank
9262f5a2f7bf5b95c1b4ff8b4b43af2d20cdafdcc89Marc Blank            // We need a _sync_id, but it can't be the parent's id, so we generate one
9272f5a2f7bf5b95c1b4ff8b4b43af2d20cdafdcc89Marc Blank            cv.put(Events._SYNC_ID, parentCv.getAsString(Events._SYNC_ID) + '_' +
9282f5a2f7bf5b95c1b4ff8b4b43af2d20cdafdcc89Marc Blank                    exceptionStartTime);
9292f5a2f7bf5b95c1b4ff8b4b43af2d20cdafdcc89Marc Blank
930e0d5f11dde90b79cb4704647d71d07ca0faebf6fMarc Blank            // Enforce CalendarProvider required properties
931e0d5f11dde90b79cb4704647d71d07ca0faebf6fMarc Blank            setTimeRelatedValues(cv, startTime, endTime, allDayEvent);
93220d5e152abafc87b9840fdc36cc33c3ca243cd1bMarc Blank
93320d5e152abafc87b9840fdc36cc33c3ca243cd1bMarc Blank            // Don't insert an invalid exception event
934e0d5f11dde90b79cb4704647d71d07ca0faebf6fMarc Blank            if (!isValidEventValues(cv)) return;
9355862a85e17e81866ca82a9905577931947fbd44eMarc Blank
936cbaebd5d2e2be06473b18b00e15235ef36b63c9eMarc Blank            // Add the exception insert
937cbaebd5d2e2be06473b18b00e15235ef36b63c9eMarc Blank            int exceptionStart = ops.mCount;
9385862a85e17e81866ca82a9905577931947fbd44eMarc Blank            ops.newException(cv);
939cbaebd5d2e2be06473b18b00e15235ef36b63c9eMarc Blank            // Also add the attendees, because they need to be copied over from the parent event
940450dd050b8c23e2bd5c4288490d24d7ca69a7269Marc Blank            boolean attendeesRedacted = false;
941b76a67d3034e65fb6d5d2de2ed3728e44180e651Marc Blank            if (attendeeValues != null) {
942b76a67d3034e65fb6d5d2de2ed3728e44180e651Marc Blank                for (ContentValues attValues: attendeeValues) {
943edcfd554728a27a7678306ea7432bd8676ec6990Marc Blank                    // If this is the user, use his busy status for attendee status
944edcfd554728a27a7678306ea7432bd8676ec6990Marc Blank                    String attendeeEmail = attValues.getAsString(Attendees.ATTENDEE_EMAIL);
945450dd050b8c23e2bd5c4288490d24d7ca69a7269Marc Blank                    // Note that the exception at which we surpass the redaction limit might have
946450dd050b8c23e2bd5c4288490d24d7ca69a7269Marc Blank                    // any number of attendees shown; since this is an edge case and a workaround,
947450dd050b8c23e2bd5c4288490d24d7ca69a7269Marc Blank                    // it seems to be an acceptable implementation
948edcfd554728a27a7678306ea7432bd8676ec6990Marc Blank                    if (mEmailAddress.equalsIgnoreCase(attendeeEmail)) {
949edcfd554728a27a7678306ea7432bd8676ec6990Marc Blank                        attValues.put(Attendees.ATTENDEE_STATUS,
950edcfd554728a27a7678306ea7432bd8676ec6990Marc Blank                                CalendarUtilities.attendeeStatusFromBusyStatus(busyStatus));
951450dd050b8c23e2bd5c4288490d24d7ca69a7269Marc Blank                        ops.newAttendee(attValues, exceptionStart);
952450dd050b8c23e2bd5c4288490d24d7ca69a7269Marc Blank                    } else if (ops.size() < MAX_OPS_BEFORE_EXCEPTION_ATTENDEE_REDACTION) {
953450dd050b8c23e2bd5c4288490d24d7ca69a7269Marc Blank                        ops.newAttendee(attValues, exceptionStart);
954450dd050b8c23e2bd5c4288490d24d7ca69a7269Marc Blank                    } else {
955450dd050b8c23e2bd5c4288490d24d7ca69a7269Marc Blank                        attendeesRedacted = true;
956edcfd554728a27a7678306ea7432bd8676ec6990Marc Blank                    }
957b76a67d3034e65fb6d5d2de2ed3728e44180e651Marc Blank                }
958cbaebd5d2e2be06473b18b00e15235ef36b63c9eMarc Blank            }
959cbaebd5d2e2be06473b18b00e15235ef36b63c9eMarc Blank            // And add the parent's reminder value
960cbaebd5d2e2be06473b18b00e15235ef36b63c9eMarc Blank            if (reminderMins > 0) {
961cbaebd5d2e2be06473b18b00e15235ef36b63c9eMarc Blank                ops.newReminder(reminderMins, exceptionStart);
962cbaebd5d2e2be06473b18b00e15235ef36b63c9eMarc Blank            }
963450dd050b8c23e2bd5c4288490d24d7ca69a7269Marc Blank            if (attendeesRedacted) {
964450dd050b8c23e2bd5c4288490d24d7ca69a7269Marc Blank                mService.userLog("Attendees redacted in this exception");
965450dd050b8c23e2bd5c4288490d24d7ca69a7269Marc Blank            }
9665862a85e17e81866ca82a9905577931947fbd44eMarc Blank        }
9675862a85e17e81866ca82a9905577931947fbd44eMarc Blank
9685862a85e17e81866ca82a9905577931947fbd44eMarc Blank        private int encodeVisibility(int easVisibility) {
9695862a85e17e81866ca82a9905577931947fbd44eMarc Blank            int visibility = 0;
9705862a85e17e81866ca82a9905577931947fbd44eMarc Blank            switch(easVisibility) {
9715862a85e17e81866ca82a9905577931947fbd44eMarc Blank                case 0:
9729e86eb14c6e1f7d7730f8ca6953fdfd95fe2b143RoboErik                    visibility = Events.ACCESS_DEFAULT;
9735862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    break;
9745862a85e17e81866ca82a9905577931947fbd44eMarc Blank                case 1:
9759e86eb14c6e1f7d7730f8ca6953fdfd95fe2b143RoboErik                    visibility = Events.ACCESS_PUBLIC;
9765862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    break;
9775862a85e17e81866ca82a9905577931947fbd44eMarc Blank                case 2:
9789e86eb14c6e1f7d7730f8ca6953fdfd95fe2b143RoboErik                    visibility = Events.ACCESS_PRIVATE;
9795862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    break;
9805862a85e17e81866ca82a9905577931947fbd44eMarc Blank                case 3:
9819e86eb14c6e1f7d7730f8ca6953fdfd95fe2b143RoboErik                    visibility = Events.ACCESS_CONFIDENTIAL;
9825862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    break;
9835862a85e17e81866ca82a9905577931947fbd44eMarc Blank            }
9845862a85e17e81866ca82a9905577931947fbd44eMarc Blank            return visibility;
9855862a85e17e81866ca82a9905577931947fbd44eMarc Blank        }
9865862a85e17e81866ca82a9905577931947fbd44eMarc Blank
987cbaebd5d2e2be06473b18b00e15235ef36b63c9eMarc Blank        private void exceptionsParser(CalendarOperations ops, ContentValues cv,
988e0d5f11dde90b79cb4704647d71d07ca0faebf6fMarc Blank                ArrayList<ContentValues> attendeeValues, int reminderMins, int busyStatus,
989e0d5f11dde90b79cb4704647d71d07ca0faebf6fMarc Blank                long startTime, long endTime) throws IOException {
9905862a85e17e81866ca82a9905577931947fbd44eMarc Blank            while (nextTag(Tags.CALENDAR_EXCEPTIONS) != END) {
9915862a85e17e81866ca82a9905577931947fbd44eMarc Blank                switch (tag) {
9925862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    case Tags.CALENDAR_EXCEPTION:
993e0d5f11dde90b79cb4704647d71d07ca0faebf6fMarc Blank                        exceptionParser(ops, cv, attendeeValues, reminderMins, busyStatus,
994e0d5f11dde90b79cb4704647d71d07ca0faebf6fMarc Blank                                startTime, endTime);
9955862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        break;
9965862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    default:
9975862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        skipTag();
9985862a85e17e81866ca82a9905577931947fbd44eMarc Blank                }
9995862a85e17e81866ca82a9905577931947fbd44eMarc Blank            }
10005862a85e17e81866ca82a9905577931947fbd44eMarc Blank        }
10015862a85e17e81866ca82a9905577931947fbd44eMarc Blank
100214045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank        private String categoriesParser(CalendarOperations ops) throws IOException {
100314045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank            StringBuilder categories = new StringBuilder();
10045862a85e17e81866ca82a9905577931947fbd44eMarc Blank            while (nextTag(Tags.CALENDAR_CATEGORIES) != END) {
10055862a85e17e81866ca82a9905577931947fbd44eMarc Blank                switch (tag) {
10065862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    case Tags.CALENDAR_CATEGORY:
100714045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank                        // TODO Handle categories (there's no similar concept for gdata AFAIK)
100814045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank                        // We need to save them and spit them back when we update the event
100914045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank                        categories.append(getValue());
101014045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank                        categories.append(CATEGORY_TOKENIZER_DELIMITER);
1011450b493c40b7292d3a2ad96793042c3aaf349758Marc Blank                        break;
10125862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    default:
10135862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        skipTag();
10145862a85e17e81866ca82a9905577931947fbd44eMarc Blank                }
10155862a85e17e81866ca82a9905577931947fbd44eMarc Blank            }
101614045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank            return categories.toString();
10175862a85e17e81866ca82a9905577931947fbd44eMarc Blank        }
10185862a85e17e81866ca82a9905577931947fbd44eMarc Blank
101977186bb1a174432ef272584374942d8b9228e39cMarc Blank        /**
102077186bb1a174432ef272584374942d8b9228e39cMarc Blank         * For now, we ignore (but still have to parse) event attachments; these are new in EAS 14
102177186bb1a174432ef272584374942d8b9228e39cMarc Blank         */
102277186bb1a174432ef272584374942d8b9228e39cMarc Blank        private void attachmentsParser() throws IOException {
102377186bb1a174432ef272584374942d8b9228e39cMarc Blank            while (nextTag(Tags.CALENDAR_ATTACHMENTS) != END) {
102477186bb1a174432ef272584374942d8b9228e39cMarc Blank                switch (tag) {
102577186bb1a174432ef272584374942d8b9228e39cMarc Blank                    case Tags.CALENDAR_ATTACHMENT:
102677186bb1a174432ef272584374942d8b9228e39cMarc Blank                        skipParser(Tags.CALENDAR_ATTACHMENT);
102777186bb1a174432ef272584374942d8b9228e39cMarc Blank                        break;
102877186bb1a174432ef272584374942d8b9228e39cMarc Blank                    default:
102977186bb1a174432ef272584374942d8b9228e39cMarc Blank                        skipTag();
103077186bb1a174432ef272584374942d8b9228e39cMarc Blank                }
103177186bb1a174432ef272584374942d8b9228e39cMarc Blank            }
103277186bb1a174432ef272584374942d8b9228e39cMarc Blank        }
103377186bb1a174432ef272584374942d8b9228e39cMarc Blank
1034cbaebd5d2e2be06473b18b00e15235ef36b63c9eMarc Blank        private ArrayList<ContentValues> attendeesParser(CalendarOperations ops, long eventId)
1035cbaebd5d2e2be06473b18b00e15235ef36b63c9eMarc Blank                throws IOException {
1036e588dbc379872f431426dbd3a2d5fe578e37e0b4Marc Blank            int attendeeCount = 0;
1037cbaebd5d2e2be06473b18b00e15235ef36b63c9eMarc Blank            ArrayList<ContentValues> attendeeValues = new ArrayList<ContentValues>();
10385862a85e17e81866ca82a9905577931947fbd44eMarc Blank            while (nextTag(Tags.CALENDAR_ATTENDEES) != END) {
10395862a85e17e81866ca82a9905577931947fbd44eMarc Blank                switch (tag) {
10405862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    case Tags.CALENDAR_ATTENDEE:
1041e588dbc379872f431426dbd3a2d5fe578e37e0b4Marc Blank                        ContentValues cv = attendeeParser(ops, eventId);
1042e588dbc379872f431426dbd3a2d5fe578e37e0b4Marc Blank                        // If we're going to redact these attendees anyway, let's avoid unnecessary
1043e588dbc379872f431426dbd3a2d5fe578e37e0b4Marc Blank                        // memory pressure, and not keep them around
1044e588dbc379872f431426dbd3a2d5fe578e37e0b4Marc Blank                        // We still need to parse them all, however
1045e588dbc379872f431426dbd3a2d5fe578e37e0b4Marc Blank                        attendeeCount++;
1046e588dbc379872f431426dbd3a2d5fe578e37e0b4Marc Blank                        // Allow one more than MAX_ATTENDEES, so that the check for "too many" will
1047e588dbc379872f431426dbd3a2d5fe578e37e0b4Marc Blank                        // succeed in addEvent
1048450dd050b8c23e2bd5c4288490d24d7ca69a7269Marc Blank                        if (attendeeCount <= (MAX_SYNCED_ATTENDEES+1)) {
1049e588dbc379872f431426dbd3a2d5fe578e37e0b4Marc Blank                            attendeeValues.add(cv);
1050e588dbc379872f431426dbd3a2d5fe578e37e0b4Marc Blank                        }
10515862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        break;
10525862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    default:
10535862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        skipTag();
10545862a85e17e81866ca82a9905577931947fbd44eMarc Blank                }
10555862a85e17e81866ca82a9905577931947fbd44eMarc Blank            }
1056cbaebd5d2e2be06473b18b00e15235ef36b63c9eMarc Blank            return attendeeValues;
10575862a85e17e81866ca82a9905577931947fbd44eMarc Blank        }
10585862a85e17e81866ca82a9905577931947fbd44eMarc Blank
1059cbaebd5d2e2be06473b18b00e15235ef36b63c9eMarc Blank        private ContentValues attendeeParser(CalendarOperations ops, long eventId)
1060cbaebd5d2e2be06473b18b00e15235ef36b63c9eMarc Blank                throws IOException {
10615862a85e17e81866ca82a9905577931947fbd44eMarc Blank            ContentValues cv = new ContentValues();
10625862a85e17e81866ca82a9905577931947fbd44eMarc Blank            while (nextTag(Tags.CALENDAR_ATTENDEE) != END) {
10635862a85e17e81866ca82a9905577931947fbd44eMarc Blank                switch (tag) {
10645862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    case Tags.CALENDAR_ATTENDEE_EMAIL:
10655862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        cv.put(Attendees.ATTENDEE_EMAIL, getValue());
10665862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        break;
10675862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    case Tags.CALENDAR_ATTENDEE_NAME:
10685862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        cv.put(Attendees.ATTENDEE_NAME, getValue());
10695862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        break;
107040dde4349f0f24260ffeba9dd8768e6d82e0188bMarc Blank                    case Tags.CALENDAR_ATTENDEE_STATUS:
107140dde4349f0f24260ffeba9dd8768e6d82e0188bMarc Blank                        int status = getValueInt();
107240dde4349f0f24260ffeba9dd8768e6d82e0188bMarc Blank                        cv.put(Attendees.ATTENDEE_STATUS,
107340dde4349f0f24260ffeba9dd8768e6d82e0188bMarc Blank                                (status == 2) ? Attendees.ATTENDEE_STATUS_TENTATIVE :
107440dde4349f0f24260ffeba9dd8768e6d82e0188bMarc Blank                                (status == 3) ? Attendees.ATTENDEE_STATUS_ACCEPTED :
107540dde4349f0f24260ffeba9dd8768e6d82e0188bMarc Blank                                (status == 4) ? Attendees.ATTENDEE_STATUS_DECLINED :
107640dde4349f0f24260ffeba9dd8768e6d82e0188bMarc Blank                                (status == 5) ? Attendees.ATTENDEE_STATUS_INVITED :
107740dde4349f0f24260ffeba9dd8768e6d82e0188bMarc Blank                                    Attendees.ATTENDEE_STATUS_NONE);
107840dde4349f0f24260ffeba9dd8768e6d82e0188bMarc Blank                        break;
10795862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    case Tags.CALENDAR_ATTENDEE_TYPE:
10805862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        int type = Attendees.TYPE_NONE;
10815862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        // EAS types: 1 = req'd, 2 = opt, 3 = resource
10825862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        switch (getValueInt()) {
10835862a85e17e81866ca82a9905577931947fbd44eMarc Blank                            case 1:
10845862a85e17e81866ca82a9905577931947fbd44eMarc Blank                                type = Attendees.TYPE_REQUIRED;
10855862a85e17e81866ca82a9905577931947fbd44eMarc Blank                                break;
10865862a85e17e81866ca82a9905577931947fbd44eMarc Blank                            case 2:
10875862a85e17e81866ca82a9905577931947fbd44eMarc Blank                                type = Attendees.TYPE_OPTIONAL;
10885862a85e17e81866ca82a9905577931947fbd44eMarc Blank                                break;
10895862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        }
10905862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        cv.put(Attendees.ATTENDEE_TYPE, type);
10915862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        break;
10925862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    default:
10935862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        skipTag();
10945862a85e17e81866ca82a9905577931947fbd44eMarc Blank                }
10955862a85e17e81866ca82a9905577931947fbd44eMarc Blank            }
10965862a85e17e81866ca82a9905577931947fbd44eMarc Blank            cv.put(Attendees.ATTENDEE_RELATIONSHIP, Attendees.RELATIONSHIP_ATTENDEE);
1097cbaebd5d2e2be06473b18b00e15235ef36b63c9eMarc Blank            return cv;
10985862a85e17e81866ca82a9905577931947fbd44eMarc Blank        }
10995862a85e17e81866ca82a9905577931947fbd44eMarc Blank
11005862a85e17e81866ca82a9905577931947fbd44eMarc Blank        private String bodyParser() throws IOException {
11015862a85e17e81866ca82a9905577931947fbd44eMarc Blank            String body = null;
11025862a85e17e81866ca82a9905577931947fbd44eMarc Blank            while (nextTag(Tags.BASE_BODY) != END) {
11035862a85e17e81866ca82a9905577931947fbd44eMarc Blank                switch (tag) {
11045862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    case Tags.BASE_DATA:
11055862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        body = getValue();
11065862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        break;
11075862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    default:
11085862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        skipTag();
11095862a85e17e81866ca82a9905577931947fbd44eMarc Blank                }
11105862a85e17e81866ca82a9905577931947fbd44eMarc Blank            }
11115862a85e17e81866ca82a9905577931947fbd44eMarc Blank
1112377230593dca7cb01483bfaf93959e5821f5f028Marc Blank            // Handle null data without error
1113377230593dca7cb01483bfaf93959e5821f5f028Marc Blank            if (body == null) return "";
11145862a85e17e81866ca82a9905577931947fbd44eMarc Blank            // Remove \r's from any body text
11155862a85e17e81866ca82a9905577931947fbd44eMarc Blank            return body.replace("\r\n", "\n");
11165862a85e17e81866ca82a9905577931947fbd44eMarc Blank        }
11175862a85e17e81866ca82a9905577931947fbd44eMarc Blank
11185862a85e17e81866ca82a9905577931947fbd44eMarc Blank        public void addParser(CalendarOperations ops) throws IOException {
11195862a85e17e81866ca82a9905577931947fbd44eMarc Blank            String serverId = null;
11205862a85e17e81866ca82a9905577931947fbd44eMarc Blank            while (nextTag(Tags.SYNC_ADD) != END) {
11215862a85e17e81866ca82a9905577931947fbd44eMarc Blank                switch (tag) {
11225862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    case Tags.SYNC_SERVER_ID: // same as
11235862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        serverId = getValue();
11245862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        break;
11255862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    case Tags.SYNC_APPLICATION_DATA:
11265862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        addEvent(ops, serverId, false);
11275862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        break;
11285862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    default:
11295862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        skipTag();
11305862a85e17e81866ca82a9905577931947fbd44eMarc Blank                }
11315862a85e17e81866ca82a9905577931947fbd44eMarc Blank            }
11325862a85e17e81866ca82a9905577931947fbd44eMarc Blank        }
11335862a85e17e81866ca82a9905577931947fbd44eMarc Blank
11345862a85e17e81866ca82a9905577931947fbd44eMarc Blank        private Cursor getServerIdCursor(String serverId) {
113594ceb38eefbc7cd5c62250197d99ea171a8f2bfbMarc Blank            return mContentResolver.query(mAccountUri, ID_PROJECTION, SERVER_ID_AND_CALENDAR_ID,
113694ceb38eefbc7cd5c62250197d99ea171a8f2bfbMarc Blank                    new String[] {serverId, mCalendarIdString}, null);
11375862a85e17e81866ca82a9905577931947fbd44eMarc Blank        }
11385862a85e17e81866ca82a9905577931947fbd44eMarc Blank
11395862a85e17e81866ca82a9905577931947fbd44eMarc Blank        private Cursor getClientIdCursor(String clientId) {
11405862a85e17e81866ca82a9905577931947fbd44eMarc Blank            mBindArgument[0] = clientId;
11415862a85e17e81866ca82a9905577931947fbd44eMarc Blank            return mContentResolver.query(mAccountUri, ID_PROJECTION, CLIENT_ID_SELECTION,
11425862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    mBindArgument, null);
11435862a85e17e81866ca82a9905577931947fbd44eMarc Blank        }
11445862a85e17e81866ca82a9905577931947fbd44eMarc Blank
11455862a85e17e81866ca82a9905577931947fbd44eMarc Blank        public void deleteParser(CalendarOperations ops) throws IOException {
11465862a85e17e81866ca82a9905577931947fbd44eMarc Blank            while (nextTag(Tags.SYNC_DELETE) != END) {
11475862a85e17e81866ca82a9905577931947fbd44eMarc Blank                switch (tag) {
11485862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    case Tags.SYNC_SERVER_ID:
11495862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        String serverId = getValue();
11505862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        // Find the event with the given serverId
11515862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        Cursor c = getServerIdCursor(serverId);
11525862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        try {
11535862a85e17e81866ca82a9905577931947fbd44eMarc Blank                            if (c.moveToFirst()) {
11545862a85e17e81866ca82a9905577931947fbd44eMarc Blank                                userLog("Deleting ", serverId);
11553105fd9567d7894b71be5cb3acfdae7fb0c66c9aMarc Blank                                ops.delete(c.getLong(0), serverId);
11565862a85e17e81866ca82a9905577931947fbd44eMarc Blank                            }
11575862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        } finally {
11585862a85e17e81866ca82a9905577931947fbd44eMarc Blank                            c.close();
11595862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        }
11605862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        break;
11615862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    default:
11625862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        skipTag();
11635862a85e17e81866ca82a9905577931947fbd44eMarc Blank                }
11645862a85e17e81866ca82a9905577931947fbd44eMarc Blank            }
11655862a85e17e81866ca82a9905577931947fbd44eMarc Blank        }
11665862a85e17e81866ca82a9905577931947fbd44eMarc Blank
11675862a85e17e81866ca82a9905577931947fbd44eMarc Blank        /**
11685862a85e17e81866ca82a9905577931947fbd44eMarc Blank         * A change is handled as a delete (including all exceptions) and an add
11695862a85e17e81866ca82a9905577931947fbd44eMarc Blank         * This isn't as efficient as attempting to traverse the original and all of its exceptions,
11705862a85e17e81866ca82a9905577931947fbd44eMarc Blank         * but changes happen infrequently and this code is both simpler and easier to maintain
11715862a85e17e81866ca82a9905577931947fbd44eMarc Blank         * @param ops the array of pending ContactProviderOperations.
11725862a85e17e81866ca82a9905577931947fbd44eMarc Blank         * @throws IOException
11735862a85e17e81866ca82a9905577931947fbd44eMarc Blank         */
11745862a85e17e81866ca82a9905577931947fbd44eMarc Blank        public void changeParser(CalendarOperations ops) throws IOException {
11755862a85e17e81866ca82a9905577931947fbd44eMarc Blank            String serverId = null;
11765862a85e17e81866ca82a9905577931947fbd44eMarc Blank            while (nextTag(Tags.SYNC_CHANGE) != END) {
11775862a85e17e81866ca82a9905577931947fbd44eMarc Blank                switch (tag) {
11785862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    case Tags.SYNC_SERVER_ID:
11795862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        serverId = getValue();
11805862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        break;
11815862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    case Tags.SYNC_APPLICATION_DATA:
11829a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank                        userLog("Changing " + serverId);
11835862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        addEvent(ops, serverId, true);
11845862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        break;
11855862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    default:
11865862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        skipTag();
11875862a85e17e81866ca82a9905577931947fbd44eMarc Blank                }
11885862a85e17e81866ca82a9905577931947fbd44eMarc Blank            }
11895862a85e17e81866ca82a9905577931947fbd44eMarc Blank        }
11905862a85e17e81866ca82a9905577931947fbd44eMarc Blank
11915862a85e17e81866ca82a9905577931947fbd44eMarc Blank        @Override
11925862a85e17e81866ca82a9905577931947fbd44eMarc Blank        public void commandsParser() throws IOException {
11935862a85e17e81866ca82a9905577931947fbd44eMarc Blank            while (nextTag(Tags.SYNC_COMMANDS) != END) {
11945862a85e17e81866ca82a9905577931947fbd44eMarc Blank                if (tag == Tags.SYNC_ADD) {
11955862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    addParser(mOps);
11965862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    incrementChangeCount();
11975862a85e17e81866ca82a9905577931947fbd44eMarc Blank                } else if (tag == Tags.SYNC_DELETE) {
11985862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    deleteParser(mOps);
11995862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    incrementChangeCount();
12005862a85e17e81866ca82a9905577931947fbd44eMarc Blank                } else if (tag == Tags.SYNC_CHANGE) {
12015862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    changeParser(mOps);
12025862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    incrementChangeCount();
12035862a85e17e81866ca82a9905577931947fbd44eMarc Blank                } else
12045862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    skipTag();
12055862a85e17e81866ca82a9905577931947fbd44eMarc Blank            }
12065862a85e17e81866ca82a9905577931947fbd44eMarc Blank        }
12075862a85e17e81866ca82a9905577931947fbd44eMarc Blank
12085862a85e17e81866ca82a9905577931947fbd44eMarc Blank        @Override
12095862a85e17e81866ca82a9905577931947fbd44eMarc Blank        public void commit() throws IOException {
12105862a85e17e81866ca82a9905577931947fbd44eMarc Blank            userLog("Calendar SyncKey saved as: ", mMailbox.mSyncKey);
12119a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank            // Save the syncKey here, using the Helper provider by Calendar provider
1212151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank            mOps.add(new Operation(SyncStateContract.Helpers.newSetOperation(
1213670478b801a5e229da39c198f4cb84f95a0da3efMarc Blank                    asSyncAdapter(SyncState.CONTENT_URI, mEmailAddress,
1214670478b801a5e229da39c198f4cb84f95a0da3efMarc Blank                            Eas.EXCHANGE_ACCOUNT_MANAGER_TYPE),
1215670478b801a5e229da39c198f4cb84f95a0da3efMarc Blank                    mAccountManagerAccount,
1216151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank                    mMailbox.mSyncKey.getBytes())));
12175862a85e17e81866ca82a9905577931947fbd44eMarc Blank
121830d2d4ea74cfca9d27dfd495cebc8387b8f2454dMarc Blank            // We need to send cancellations now, because the Event won't exist after the commit
121930d2d4ea74cfca9d27dfd495cebc8387b8f2454dMarc Blank            for (long eventId: mSendCancelIdList) {
122030d2d4ea74cfca9d27dfd495cebc8387b8f2454dMarc Blank                EmailContent.Message msg;
122130d2d4ea74cfca9d27dfd495cebc8387b8f2454dMarc Blank                try {
122230d2d4ea74cfca9d27dfd495cebc8387b8f2454dMarc Blank                    msg = CalendarUtilities.createMessageForEventId(mContext, eventId,
122330d2d4ea74cfca9d27dfd495cebc8387b8f2454dMarc Blank                            EmailContent.Message.FLAG_OUTGOING_MEETING_CANCEL, null,
122430d2d4ea74cfca9d27dfd495cebc8387b8f2454dMarc Blank                            mAccount);
122530d2d4ea74cfca9d27dfd495cebc8387b8f2454dMarc Blank                } catch (RemoteException e) {
122630d2d4ea74cfca9d27dfd495cebc8387b8f2454dMarc Blank                    // Nothing to do here; the Event may no longer exist
122730d2d4ea74cfca9d27dfd495cebc8387b8f2454dMarc Blank                    continue;
122830d2d4ea74cfca9d27dfd495cebc8387b8f2454dMarc Blank                }
122930d2d4ea74cfca9d27dfd495cebc8387b8f2454dMarc Blank                if (msg != null) {
123030d2d4ea74cfca9d27dfd495cebc8387b8f2454dMarc Blank                    EasOutboxService.sendMessage(mContext, mAccount.mId, msg);
123130d2d4ea74cfca9d27dfd495cebc8387b8f2454dMarc Blank                }
123230d2d4ea74cfca9d27dfd495cebc8387b8f2454dMarc Blank            }
123330d2d4ea74cfca9d27dfd495cebc8387b8f2454dMarc Blank
1234151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank            // Execute our CPO's safely
1235151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank            try {
1236151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank                mOps.mResults = safeExecute(CalendarContract.AUTHORITY, mOps);
1237151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank            } catch (RemoteException e) {
1238151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank                throw new IOException("Remote exception caught; will retry");
1239151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank            }
12405862a85e17e81866ca82a9905577931947fbd44eMarc Blank
12415862a85e17e81866ca82a9905577931947fbd44eMarc Blank            if (mOps.mResults != null) {
1242dafc866120dac68fabbddcc9943e3995894c5244Marc Blank                // Clear dirty and mark flags for updates sent to server
1243e96bd20257c47db94f9166fd37ca00e0c73788afMarc Blank                if (!mUploadedIdList.isEmpty())  {
1244e96bd20257c47db94f9166fd37ca00e0c73788afMarc Blank                    ContentValues cv = new ContentValues();
12459e86eb14c6e1f7d7730f8ca6953fdfd95fe2b143RoboErik                    cv.put(Events.DIRTY, 0);
124604c880a6b5ad041f172d4b1eeecc06d6a06e4141RoboErik                    cv.put(EVENT_SYNC_MARK, "0");
12476989716b639d274a98141674556ac9402be32ebeRoboErik                    for (long eventId : mUploadedIdList) {
12486989716b639d274a98141674556ac9402be32ebeRoboErik                        mContentResolver.update(
12496989716b639d274a98141674556ac9402be32ebeRoboErik                                asSyncAdapter(
12506989716b639d274a98141674556ac9402be32ebeRoboErik                                        ContentUris.withAppendedId(Events.CONTENT_URI, eventId),
12516989716b639d274a98141674556ac9402be32ebeRoboErik                                        mEmailAddress, Eas.EXCHANGE_ACCOUNT_MANAGER_TYPE), cv,
1252e96bd20257c47db94f9166fd37ca00e0c73788afMarc Blank                                null, null);
1253e96bd20257c47db94f9166fd37ca00e0c73788afMarc Blank                    }
1254e96bd20257c47db94f9166fd37ca00e0c73788afMarc Blank                }
1255e96bd20257c47db94f9166fd37ca00e0c73788afMarc Blank                // Delete events marked for deletion
1256e96bd20257c47db94f9166fd37ca00e0c73788afMarc Blank                if (!mDeletedIdList.isEmpty()) {
12576989716b639d274a98141674556ac9402be32ebeRoboErik                    for (long eventId : mDeletedIdList) {
12586989716b639d274a98141674556ac9402be32ebeRoboErik                        mContentResolver.delete(
12596989716b639d274a98141674556ac9402be32ebeRoboErik                                asSyncAdapter(
12606989716b639d274a98141674556ac9402be32ebeRoboErik                                        ContentUris.withAppendedId(Events.CONTENT_URI, eventId),
12616989716b639d274a98141674556ac9402be32ebeRoboErik                                        mEmailAddress, Eas.EXCHANGE_ACCOUNT_MANAGER_TYPE), null,
12626989716b639d274a98141674556ac9402be32ebeRoboErik                                null);
1263e96bd20257c47db94f9166fd37ca00e0c73788afMarc Blank                    }
1264125f1c07b605ec798e81c9fb0f153b2b5200fe1fMarc Blank                }
1265125f1c07b605ec798e81c9fb0f153b2b5200fe1fMarc Blank                // Send any queued up email (invitations replies, etc.)
1266125f1c07b605ec798e81c9fb0f153b2b5200fe1fMarc Blank                for (Message msg: mOutgoingMailList) {
1267125f1c07b605ec798e81c9fb0f153b2b5200fe1fMarc Blank                    EasOutboxService.sendMessage(mContext, mAccount.mId, msg);
126830d2d4ea74cfca9d27dfd495cebc8387b8f2454dMarc Blank                }
12695862a85e17e81866ca82a9905577931947fbd44eMarc Blank            }
12705862a85e17e81866ca82a9905577931947fbd44eMarc Blank        }
12715862a85e17e81866ca82a9905577931947fbd44eMarc Blank
12725862a85e17e81866ca82a9905577931947fbd44eMarc Blank        public void addResponsesParser() throws IOException {
12735862a85e17e81866ca82a9905577931947fbd44eMarc Blank            String serverId = null;
12745862a85e17e81866ca82a9905577931947fbd44eMarc Blank            String clientId = null;
12755862a85e17e81866ca82a9905577931947fbd44eMarc Blank            int status = -1;
12765862a85e17e81866ca82a9905577931947fbd44eMarc Blank            ContentValues cv = new ContentValues();
12775862a85e17e81866ca82a9905577931947fbd44eMarc Blank            while (nextTag(Tags.SYNC_ADD) != END) {
12785862a85e17e81866ca82a9905577931947fbd44eMarc Blank                switch (tag) {
12795862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    case Tags.SYNC_SERVER_ID:
12805862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        serverId = getValue();
12815862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        break;
12825862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    case Tags.SYNC_CLIENT_ID:
12835862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        clientId = getValue();
12845862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        break;
12855862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    case Tags.SYNC_STATUS:
12865862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        status = getValueInt();
12875862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        if (status != 1) {
12885862a85e17e81866ca82a9905577931947fbd44eMarc Blank                            userLog("Attempt to add event failed with status: " + status);
12895862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        }
12905862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        break;
12915862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    default:
12925862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        skipTag();
12935862a85e17e81866ca82a9905577931947fbd44eMarc Blank                }
12945862a85e17e81866ca82a9905577931947fbd44eMarc Blank            }
12955862a85e17e81866ca82a9905577931947fbd44eMarc Blank
12965862a85e17e81866ca82a9905577931947fbd44eMarc Blank            if (clientId == null) return;
12975862a85e17e81866ca82a9905577931947fbd44eMarc Blank            if (serverId == null) {
12985862a85e17e81866ca82a9905577931947fbd44eMarc Blank                // TODO Reconsider how to handle this
12995862a85e17e81866ca82a9905577931947fbd44eMarc Blank                serverId = "FAIL:" + status;
13005862a85e17e81866ca82a9905577931947fbd44eMarc Blank            }
13015862a85e17e81866ca82a9905577931947fbd44eMarc Blank
13025862a85e17e81866ca82a9905577931947fbd44eMarc Blank            Cursor c = getClientIdCursor(clientId);
13035862a85e17e81866ca82a9905577931947fbd44eMarc Blank            try {
13045862a85e17e81866ca82a9905577931947fbd44eMarc Blank                if (c.moveToFirst()) {
13055862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    cv.put(Events._SYNC_ID, serverId);
130604c880a6b5ad041f172d4b1eeecc06d6a06e4141RoboErik                    cv.put(Events.SYNC_DATA2, clientId);
1307c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank                    long id = c.getLong(0);
1308c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank                    // Write the serverId into the Event
1309151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank                    mOps.add(new Operation(ContentProviderOperation
1310151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank                            .newUpdate(ContentUris.withAppendedId(mAsSyncAdapterEvents, id))
1311151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank                            .withValues(cv)));
13125862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    userLog("New event " + clientId + " was given serverId: " + serverId);
13135862a85e17e81866ca82a9905577931947fbd44eMarc Blank                }
13145862a85e17e81866ca82a9905577931947fbd44eMarc Blank            } finally {
13155862a85e17e81866ca82a9905577931947fbd44eMarc Blank                c.close();
13165862a85e17e81866ca82a9905577931947fbd44eMarc Blank            }
13175862a85e17e81866ca82a9905577931947fbd44eMarc Blank        }
13185862a85e17e81866ca82a9905577931947fbd44eMarc Blank
13195862a85e17e81866ca82a9905577931947fbd44eMarc Blank        public void changeResponsesParser() throws IOException {
13205862a85e17e81866ca82a9905577931947fbd44eMarc Blank            String serverId = null;
13215862a85e17e81866ca82a9905577931947fbd44eMarc Blank            String status = null;
13225862a85e17e81866ca82a9905577931947fbd44eMarc Blank            while (nextTag(Tags.SYNC_CHANGE) != END) {
13235862a85e17e81866ca82a9905577931947fbd44eMarc Blank                switch (tag) {
13245862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    case Tags.SYNC_SERVER_ID:
13255862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        serverId = getValue();
13265862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        break;
13275862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    case Tags.SYNC_STATUS:
13285862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        status = getValue();
13295862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        break;
13305862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    default:
13315862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        skipTag();
13325862a85e17e81866ca82a9905577931947fbd44eMarc Blank                }
13335862a85e17e81866ca82a9905577931947fbd44eMarc Blank            }
13345862a85e17e81866ca82a9905577931947fbd44eMarc Blank            if (serverId != null && status != null) {
13355862a85e17e81866ca82a9905577931947fbd44eMarc Blank                userLog("Changed event " + serverId + " failed with status: " + status);
13365862a85e17e81866ca82a9905577931947fbd44eMarc Blank            }
13375862a85e17e81866ca82a9905577931947fbd44eMarc Blank        }
13385862a85e17e81866ca82a9905577931947fbd44eMarc Blank
13395862a85e17e81866ca82a9905577931947fbd44eMarc Blank
13405862a85e17e81866ca82a9905577931947fbd44eMarc Blank        @Override
13415862a85e17e81866ca82a9905577931947fbd44eMarc Blank        public void responsesParser() throws IOException {
13425862a85e17e81866ca82a9905577931947fbd44eMarc Blank            // Handle server responses here (for Add and Change)
13435862a85e17e81866ca82a9905577931947fbd44eMarc Blank            while (nextTag(Tags.SYNC_RESPONSES) != END) {
13445862a85e17e81866ca82a9905577931947fbd44eMarc Blank                if (tag == Tags.SYNC_ADD) {
13455862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    addResponsesParser();
13465862a85e17e81866ca82a9905577931947fbd44eMarc Blank                } else if (tag == Tags.SYNC_CHANGE) {
13475862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    changeResponsesParser();
13485862a85e17e81866ca82a9905577931947fbd44eMarc Blank                } else
13495862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    skipTag();
13505862a85e17e81866ca82a9905577931947fbd44eMarc Blank            }
13515862a85e17e81866ca82a9905577931947fbd44eMarc Blank        }
13525862a85e17e81866ca82a9905577931947fbd44eMarc Blank    }
13535862a85e17e81866ca82a9905577931947fbd44eMarc Blank
1354151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank    protected class CalendarOperations extends ArrayList<Operation> {
13555862a85e17e81866ca82a9905577931947fbd44eMarc Blank        private static final long serialVersionUID = 1L;
13566eaea8ad13360b1a1b4eafbfcc104a8813bf8855Marc Blank        public int mCount = 0;
13575862a85e17e81866ca82a9905577931947fbd44eMarc Blank        private ContentProviderResult[] mResults = null;
13585862a85e17e81866ca82a9905577931947fbd44eMarc Blank        private int mEventStart = 0;
13595862a85e17e81866ca82a9905577931947fbd44eMarc Blank
13605862a85e17e81866ca82a9905577931947fbd44eMarc Blank        @Override
1361151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank        public boolean add(Operation op) {
13625862a85e17e81866ca82a9905577931947fbd44eMarc Blank            super.add(op);
13635862a85e17e81866ca82a9905577931947fbd44eMarc Blank            mCount++;
13645862a85e17e81866ca82a9905577931947fbd44eMarc Blank            return true;
13655862a85e17e81866ca82a9905577931947fbd44eMarc Blank        }
13665862a85e17e81866ca82a9905577931947fbd44eMarc Blank
1367151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank        public int newEvent(Operation op) {
13685862a85e17e81866ca82a9905577931947fbd44eMarc Blank            mEventStart = mCount;
13695862a85e17e81866ca82a9905577931947fbd44eMarc Blank            add(op);
13705862a85e17e81866ca82a9905577931947fbd44eMarc Blank            return mEventStart;
13715862a85e17e81866ca82a9905577931947fbd44eMarc Blank        }
13725862a85e17e81866ca82a9905577931947fbd44eMarc Blank
13733105fd9567d7894b71be5cb3acfdae7fb0c66c9aMarc Blank        public int newDelete(long id, String serverId) {
13746eaea8ad13360b1a1b4eafbfcc104a8813bf8855Marc Blank            int offset = mCount;
13753105fd9567d7894b71be5cb3acfdae7fb0c66c9aMarc Blank            delete(id, serverId);
13766eaea8ad13360b1a1b4eafbfcc104a8813bf8855Marc Blank            return offset;
13776eaea8ad13360b1a1b4eafbfcc104a8813bf8855Marc Blank        }
13786eaea8ad13360b1a1b4eafbfcc104a8813bf8855Marc Blank
13795862a85e17e81866ca82a9905577931947fbd44eMarc Blank        public void newAttendee(ContentValues cv) {
1380cbaebd5d2e2be06473b18b00e15235ef36b63c9eMarc Blank            newAttendee(cv, mEventStart);
1381cbaebd5d2e2be06473b18b00e15235ef36b63c9eMarc Blank        }
1382cbaebd5d2e2be06473b18b00e15235ef36b63c9eMarc Blank
1383cbaebd5d2e2be06473b18b00e15235ef36b63c9eMarc Blank        public void newAttendee(ContentValues cv, int eventStart) {
1384151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank            add(new Operation(ContentProviderOperation.newInsert(mAsSyncAdapterAttendees)
1385151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank                    .withValues(cv),
1386151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank                    Attendees.EVENT_ID,
1387151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank                    eventStart));
13885862a85e17e81866ca82a9905577931947fbd44eMarc Blank        }
13895862a85e17e81866ca82a9905577931947fbd44eMarc Blank
13905862a85e17e81866ca82a9905577931947fbd44eMarc Blank        public void updatedAttendee(ContentValues cv, long id) {
13915862a85e17e81866ca82a9905577931947fbd44eMarc Blank            cv.put(Attendees.EVENT_ID, id);
1392151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank            add(new Operation(ContentProviderOperation.newInsert(mAsSyncAdapterAttendees)
1393151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank                    .withValues(cv)));
13945862a85e17e81866ca82a9905577931947fbd44eMarc Blank        }
13955862a85e17e81866ca82a9905577931947fbd44eMarc Blank
13965862a85e17e81866ca82a9905577931947fbd44eMarc Blank        public void newException(ContentValues cv) {
1397151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank            add(new Operation(ContentProviderOperation.newInsert(mAsSyncAdapterEvents)
1398151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank                    .withValues(cv)));
13995862a85e17e81866ca82a9905577931947fbd44eMarc Blank        }
14005862a85e17e81866ca82a9905577931947fbd44eMarc Blank
14015862a85e17e81866ca82a9905577931947fbd44eMarc Blank        public void newExtendedProperty(String name, String value) {
1402151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank            add(new Operation(ContentProviderOperation.newInsert(mAsSyncAdapterExtendedProperties)
14035862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    .withValue(ExtendedProperties.NAME, name)
1404151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank                    .withValue(ExtendedProperties.VALUE, value),
1405151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank                    ExtendedProperties.EVENT_ID,
1406151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank                    mEventStart));
14075862a85e17e81866ca82a9905577931947fbd44eMarc Blank        }
14085862a85e17e81866ca82a9905577931947fbd44eMarc Blank
1409ac3283a6b46bf27a5b418638c95a13121ea180e1Marc Blank        public void updatedExtendedProperty(String name, String value, long id) {
1410ac3283a6b46bf27a5b418638c95a13121ea180e1Marc Blank            // Find an existing ExtendedProperties row for this event and property name
1411ac3283a6b46bf27a5b418638c95a13121ea180e1Marc Blank            Cursor c = mService.mContentResolver.query(ExtendedProperties.CONTENT_URI,
1412ac3283a6b46bf27a5b418638c95a13121ea180e1Marc Blank                    EXTENDED_PROPERTY_PROJECTION, EVENT_ID_AND_NAME,
1413ac3283a6b46bf27a5b418638c95a13121ea180e1Marc Blank                    new String[] {Long.toString(id), name}, null);
1414ac3283a6b46bf27a5b418638c95a13121ea180e1Marc Blank            long extendedPropertyId = -1;
1415ac3283a6b46bf27a5b418638c95a13121ea180e1Marc Blank            // If there is one, capture its _id
1416ac3283a6b46bf27a5b418638c95a13121ea180e1Marc Blank            if (c != null) {
1417ac3283a6b46bf27a5b418638c95a13121ea180e1Marc Blank                try {
1418ac3283a6b46bf27a5b418638c95a13121ea180e1Marc Blank                    if (c.moveToFirst()) {
1419ac3283a6b46bf27a5b418638c95a13121ea180e1Marc Blank                        extendedPropertyId = c.getLong(EXTENDED_PROPERTY_ID);
1420ac3283a6b46bf27a5b418638c95a13121ea180e1Marc Blank                    }
1421ac3283a6b46bf27a5b418638c95a13121ea180e1Marc Blank                } finally {
1422ac3283a6b46bf27a5b418638c95a13121ea180e1Marc Blank                    c.close();
1423ac3283a6b46bf27a5b418638c95a13121ea180e1Marc Blank                }
1424ac3283a6b46bf27a5b418638c95a13121ea180e1Marc Blank            }
14256989716b639d274a98141674556ac9402be32ebeRoboErik            // Either do an update or an insert, depending on whether one
14266989716b639d274a98141674556ac9402be32ebeRoboErik            // already exists
1427ac3283a6b46bf27a5b418638c95a13121ea180e1Marc Blank            if (extendedPropertyId >= 0) {
1428151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank                add(new Operation(ContentProviderOperation
14296989716b639d274a98141674556ac9402be32ebeRoboErik                        .newUpdate(
1430151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank                                ContentUris.withAppendedId(mAsSyncAdapterExtendedProperties,
14316989716b639d274a98141674556ac9402be32ebeRoboErik                                        extendedPropertyId))
1432151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank                        .withValue(ExtendedProperties.VALUE, value)));
1433ac3283a6b46bf27a5b418638c95a13121ea180e1Marc Blank            } else {
1434ac3283a6b46bf27a5b418638c95a13121ea180e1Marc Blank                newExtendedProperty(name, value);
1435ac3283a6b46bf27a5b418638c95a13121ea180e1Marc Blank            }
1436ac3283a6b46bf27a5b418638c95a13121ea180e1Marc Blank        }
1437ac3283a6b46bf27a5b418638c95a13121ea180e1Marc Blank
1438cbaebd5d2e2be06473b18b00e15235ef36b63c9eMarc Blank        public void newReminder(int mins, int eventStart) {
1439151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank            add(new Operation(ContentProviderOperation.newInsert(mAsSyncAdapterReminders)
14405862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    .withValue(Reminders.MINUTES, mins)
1441151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank                    .withValue(Reminders.METHOD, Reminders.METHOD_ALERT),
1442151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank                    ExtendedProperties.EVENT_ID,
1443151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank                    eventStart));
14445862a85e17e81866ca82a9905577931947fbd44eMarc Blank        }
14455862a85e17e81866ca82a9905577931947fbd44eMarc Blank
1446cbaebd5d2e2be06473b18b00e15235ef36b63c9eMarc Blank        public void newReminder(int mins) {
1447cbaebd5d2e2be06473b18b00e15235ef36b63c9eMarc Blank            newReminder(mins, mEventStart);
1448cbaebd5d2e2be06473b18b00e15235ef36b63c9eMarc Blank        }
1449cbaebd5d2e2be06473b18b00e15235ef36b63c9eMarc Blank
14503105fd9567d7894b71be5cb3acfdae7fb0c66c9aMarc Blank        public void delete(long id, String syncId) {
1451151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank            add(new Operation(ContentProviderOperation.newDelete(
1452151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank                    ContentUris.withAppendedId(mAsSyncAdapterEvents, id))));
1453151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank            // Delete the exceptions for this Event (CalendarProvider doesn't do this)
1454151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank            add(new Operation(ContentProviderOperation
1455151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank                    .newDelete(mAsSyncAdapterEvents)
1456151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank                    .withSelection(Events.ORIGINAL_SYNC_ID + "=?", new String[] {syncId})));
14575862a85e17e81866ca82a9905577931947fbd44eMarc Blank        }
14585862a85e17e81866ca82a9905577931947fbd44eMarc Blank    }
14595862a85e17e81866ca82a9905577931947fbd44eMarc Blank
14605862a85e17e81866ca82a9905577931947fbd44eMarc Blank    private String decodeVisibility(int visibility) {
14615862a85e17e81866ca82a9905577931947fbd44eMarc Blank        int easVisibility = 0;
14625862a85e17e81866ca82a9905577931947fbd44eMarc Blank        switch(visibility) {
14639e86eb14c6e1f7d7730f8ca6953fdfd95fe2b143RoboErik            case Events.ACCESS_DEFAULT:
14645862a85e17e81866ca82a9905577931947fbd44eMarc Blank                easVisibility = 0;
14655862a85e17e81866ca82a9905577931947fbd44eMarc Blank                break;
14669e86eb14c6e1f7d7730f8ca6953fdfd95fe2b143RoboErik            case Events.ACCESS_PUBLIC:
14675862a85e17e81866ca82a9905577931947fbd44eMarc Blank                easVisibility = 1;
14685862a85e17e81866ca82a9905577931947fbd44eMarc Blank                break;
14699e86eb14c6e1f7d7730f8ca6953fdfd95fe2b143RoboErik            case Events.ACCESS_PRIVATE:
14705862a85e17e81866ca82a9905577931947fbd44eMarc Blank                easVisibility = 2;
14715862a85e17e81866ca82a9905577931947fbd44eMarc Blank                break;
14729e86eb14c6e1f7d7730f8ca6953fdfd95fe2b143RoboErik            case Events.ACCESS_CONFIDENTIAL:
14735862a85e17e81866ca82a9905577931947fbd44eMarc Blank                easVisibility = 3;
14745862a85e17e81866ca82a9905577931947fbd44eMarc Blank                break;
14755862a85e17e81866ca82a9905577931947fbd44eMarc Blank        }
14765862a85e17e81866ca82a9905577931947fbd44eMarc Blank        return Integer.toString(easVisibility);
14775862a85e17e81866ca82a9905577931947fbd44eMarc Blank    }
14785862a85e17e81866ca82a9905577931947fbd44eMarc Blank
1479dafc866120dac68fabbddcc9943e3995894c5244Marc Blank    private int getInt(ContentValues cv, String column) {
1480dafc866120dac68fabbddcc9943e3995894c5244Marc Blank        Integer i = cv.getAsInteger(column);
1481dafc866120dac68fabbddcc9943e3995894c5244Marc Blank        if (i == null) return 0;
1482dafc866120dac68fabbddcc9943e3995894c5244Marc Blank        return i;
1483dafc866120dac68fabbddcc9943e3995894c5244Marc Blank    }
1484dafc866120dac68fabbddcc9943e3995894c5244Marc Blank
1485332d08cc2fc37d9936a73e3a120125c60b0587d1Marc Blank    private void sendEvent(Entity entity, String clientId, Serializer s)
14869a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank            throws IOException {
14879a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank        // Serialize for EAS here
14889a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank        // Set uid with the client id we created
14899a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank        // 1) Serialize the top-level event
14909a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank        // 2) Serialize attendees and reminders from subvalues
14919a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank        // 3) Look for exceptions and serialize with the top-level event
14929a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank        ContentValues entityValues = entity.getEntityValues();
1493393208ab154d18a876842777781ab153d34a0281Marc Blank        final boolean isException = (clientId == null);
1494c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank        boolean hasAttendees = false;
1495393208ab154d18a876842777781ab153d34a0281Marc Blank        final boolean isChange = entityValues.containsKey(Events._SYNC_ID);
1496393208ab154d18a876842777781ab153d34a0281Marc Blank        final Double version = mService.mProtocolVersionDouble;
1497393208ab154d18a876842777781ab153d34a0281Marc Blank        final boolean allDay =
1498393208ab154d18a876842777781ab153d34a0281Marc Blank            CalendarUtilities.getIntegerValueAsBoolean(entityValues, Events.ALL_DAY);
1499270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank
15009aabf6ca8be1a9208611180442c2755a2c10bb7cMarc Blank        // NOTE: Exchange 2003 (EAS 2.5) seems to require the "exception deleted" and "exception
15019aabf6ca8be1a9208611180442c2755a2c10bb7cMarc Blank        // start time" data before other data in exceptions.  Failure to do so results in a
15029aabf6ca8be1a9208611180442c2755a2c10bb7cMarc Blank        // status 6 error during sync
15039aabf6ca8be1a9208611180442c2755a2c10bb7cMarc Blank        if (isException) {
15049aabf6ca8be1a9208611180442c2755a2c10bb7cMarc Blank           // Send exception deleted flag if necessary
1505670478b801a5e229da39c198f4cb84f95a0da3efMarc Blank            Integer deleted = entityValues.getAsInteger(Events.DELETED);
15069aabf6ca8be1a9208611180442c2755a2c10bb7cMarc Blank            boolean isDeleted = deleted != null && deleted == 1;
15079aabf6ca8be1a9208611180442c2755a2c10bb7cMarc Blank            Integer eventStatus = entityValues.getAsInteger(Events.STATUS);
15089aabf6ca8be1a9208611180442c2755a2c10bb7cMarc Blank            boolean isCanceled = eventStatus != null && eventStatus.equals(Events.STATUS_CANCELED);
15099aabf6ca8be1a9208611180442c2755a2c10bb7cMarc Blank            if (isDeleted || isCanceled) {
15109aabf6ca8be1a9208611180442c2755a2c10bb7cMarc Blank                s.data(Tags.CALENDAR_EXCEPTION_IS_DELETED, "1");
15119aabf6ca8be1a9208611180442c2755a2c10bb7cMarc Blank                // If we're deleted, the UI will continue to show this exception until we mark
15129aabf6ca8be1a9208611180442c2755a2c10bb7cMarc Blank                // it canceled, so we'll do that here...
15139aabf6ca8be1a9208611180442c2755a2c10bb7cMarc Blank                if (isDeleted && !isCanceled) {
1514393208ab154d18a876842777781ab153d34a0281Marc Blank                    final long eventId = entityValues.getAsLong(Events._ID);
15159aabf6ca8be1a9208611180442c2755a2c10bb7cMarc Blank                    ContentValues cv = new ContentValues();
15169aabf6ca8be1a9208611180442c2755a2c10bb7cMarc Blank                    cv.put(Events.STATUS, Events.STATUS_CANCELED);
15179aabf6ca8be1a9208611180442c2755a2c10bb7cMarc Blank                    mService.mContentResolver.update(
15186989716b639d274a98141674556ac9402be32ebeRoboErik                            asSyncAdapter(ContentUris.withAppendedId(Events.CONTENT_URI, eventId),
15196989716b639d274a98141674556ac9402be32ebeRoboErik                                    mEmailAddress, Eas.EXCHANGE_ACCOUNT_MANAGER_TYPE), cv, null,
15206989716b639d274a98141674556ac9402be32ebeRoboErik                            null);
15219aabf6ca8be1a9208611180442c2755a2c10bb7cMarc Blank                }
15229aabf6ca8be1a9208611180442c2755a2c10bb7cMarc Blank            } else {
15239aabf6ca8be1a9208611180442c2755a2c10bb7cMarc Blank                s.data(Tags.CALENDAR_EXCEPTION_IS_DELETED, "0");
15249aabf6ca8be1a9208611180442c2755a2c10bb7cMarc Blank            }
15259aabf6ca8be1a9208611180442c2755a2c10bb7cMarc Blank
15269aabf6ca8be1a9208611180442c2755a2c10bb7cMarc Blank            // TODO Add reminders to exceptions (allow them to be specified!)
15279aabf6ca8be1a9208611180442c2755a2c10bb7cMarc Blank            Long originalTime = entityValues.getAsLong(Events.ORIGINAL_INSTANCE_TIME);
15289aabf6ca8be1a9208611180442c2755a2c10bb7cMarc Blank            if (originalTime != null) {
1529393208ab154d18a876842777781ab153d34a0281Marc Blank                final boolean originalAllDay =
1530393208ab154d18a876842777781ab153d34a0281Marc Blank                    CalendarUtilities.getIntegerValueAsBoolean(entityValues,
1531393208ab154d18a876842777781ab153d34a0281Marc Blank                            Events.ORIGINAL_ALL_DAY);
1532393208ab154d18a876842777781ab153d34a0281Marc Blank                if (originalAllDay) {
15339aabf6ca8be1a9208611180442c2755a2c10bb7cMarc Blank                    // For all day events, we need our local all-day time
15349aabf6ca8be1a9208611180442c2755a2c10bb7cMarc Blank                    originalTime =
15359aabf6ca8be1a9208611180442c2755a2c10bb7cMarc Blank                        CalendarUtilities.getLocalAllDayCalendarTime(originalTime, mLocalTimeZone);
15369aabf6ca8be1a9208611180442c2755a2c10bb7cMarc Blank                }
15379aabf6ca8be1a9208611180442c2755a2c10bb7cMarc Blank                s.data(Tags.CALENDAR_EXCEPTION_START_TIME,
15389aabf6ca8be1a9208611180442c2755a2c10bb7cMarc Blank                        CalendarUtilities.millisToEasDateTime(originalTime));
15399aabf6ca8be1a9208611180442c2755a2c10bb7cMarc Blank            } else {
15409aabf6ca8be1a9208611180442c2755a2c10bb7cMarc Blank                // Illegal; what should we do?
15419aabf6ca8be1a9208611180442c2755a2c10bb7cMarc Blank            }
15429aabf6ca8be1a9208611180442c2755a2c10bb7cMarc Blank        }
15439aabf6ca8be1a9208611180442c2755a2c10bb7cMarc Blank
154406f1c92e15bbd9c83184dd421649d5a650544ac6Marc Blank        // Get the event's time zone
1545270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank        String timeZoneName =
15467b0d3548f47b506357b9549af7c9ea230ba88caeMarc Blank            entityValues.getAsString(allDay ? EVENT_SAVED_TIMEZONE_COLUMN : Events.EVENT_TIMEZONE);
154706f1c92e15bbd9c83184dd421649d5a650544ac6Marc Blank        if (timeZoneName == null) {
1548270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank            timeZoneName = mLocalTimeZone.getID();
154906f1c92e15bbd9c83184dd421649d5a650544ac6Marc Blank        }
155006f1c92e15bbd9c83184dd421649d5a650544ac6Marc Blank        TimeZone eventTimeZone = TimeZone.getTimeZone(timeZoneName);
155106f1c92e15bbd9c83184dd421649d5a650544ac6Marc Blank
15529cf6e79160ee3d11f2aceebadecab2520ee85841Marc Blank        if (!isException) {
15539cf6e79160ee3d11f2aceebadecab2520ee85841Marc Blank            // A time zone is required in all EAS events; we'll use the default if none is set
15549cf6e79160ee3d11f2aceebadecab2520ee85841Marc Blank            // Exchange 2003 seems to require this first... :-)
155506f1c92e15bbd9c83184dd421649d5a650544ac6Marc Blank            String timeZone = CalendarUtilities.timeZoneToTziString(eventTimeZone);
15569cf6e79160ee3d11f2aceebadecab2520ee85841Marc Blank            s.data(Tags.CALENDAR_TIME_ZONE, timeZone);
15579cf6e79160ee3d11f2aceebadecab2520ee85841Marc Blank        }
15589cf6e79160ee3d11f2aceebadecab2520ee85841Marc Blank
1559270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank        s.data(Tags.CALENDAR_ALL_DAY_EVENT, allDay ? "1" : "0");
15609a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank
1561cfbbe6bf8cec39204a00d31ee4277b54b0262ba6Marc Blank        // DTSTART is always supplied
15629a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank        long startTime = entityValues.getAsLong(Events.DTSTART);
1563cfbbe6bf8cec39204a00d31ee4277b54b0262ba6Marc Blank        // Determine endTime; it's either provided as DTEND or we calculate using DURATION
1564cfbbe6bf8cec39204a00d31ee4277b54b0262ba6Marc Blank        // If no DURATION is provided, we default to one hour
1565cfbbe6bf8cec39204a00d31ee4277b54b0262ba6Marc Blank        long endTime;
1566cfbbe6bf8cec39204a00d31ee4277b54b0262ba6Marc Blank        if (entityValues.containsKey(Events.DTEND)) {
1567cfbbe6bf8cec39204a00d31ee4277b54b0262ba6Marc Blank            endTime = entityValues.getAsLong(Events.DTEND);
1568cfbbe6bf8cec39204a00d31ee4277b54b0262ba6Marc Blank        } else {
1569cfbbe6bf8cec39204a00d31ee4277b54b0262ba6Marc Blank            long durationMillis = HOURS;
1570cfbbe6bf8cec39204a00d31ee4277b54b0262ba6Marc Blank            if (entityValues.containsKey(Events.DURATION)) {
1571cfbbe6bf8cec39204a00d31ee4277b54b0262ba6Marc Blank                Duration duration = new Duration();
1572cfbbe6bf8cec39204a00d31ee4277b54b0262ba6Marc Blank                try {
1573cfbbe6bf8cec39204a00d31ee4277b54b0262ba6Marc Blank                    duration.parse(entityValues.getAsString(Events.DURATION));
1574270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank                    durationMillis = duration.getMillis();
1575e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank                } catch (DateException e) {
1576cfbbe6bf8cec39204a00d31ee4277b54b0262ba6Marc Blank                    // Can't do much about this; use the default (1 hour)
157706f1c92e15bbd9c83184dd421649d5a650544ac6Marc Blank                }
157806f1c92e15bbd9c83184dd421649d5a650544ac6Marc Blank            }
1579cfbbe6bf8cec39204a00d31ee4277b54b0262ba6Marc Blank            endTime = startTime + durationMillis;
1580cfbbe6bf8cec39204a00d31ee4277b54b0262ba6Marc Blank        }
1581cfbbe6bf8cec39204a00d31ee4277b54b0262ba6Marc Blank        if (allDay) {
1582270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank            TimeZone tz = mLocalTimeZone;
1583270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank            startTime = CalendarUtilities.getLocalAllDayCalendarTime(startTime, tz);
1584270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank            endTime = CalendarUtilities.getLocalAllDayCalendarTime(endTime, tz);
15859a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank        }
1586cfbbe6bf8cec39204a00d31ee4277b54b0262ba6Marc Blank        s.data(Tags.CALENDAR_START_TIME, CalendarUtilities.millisToEasDateTime(startTime));
1587cfbbe6bf8cec39204a00d31ee4277b54b0262ba6Marc Blank        s.data(Tags.CALENDAR_END_TIME, CalendarUtilities.millisToEasDateTime(endTime));
15889a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank
15899a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank        s.data(Tags.CALENDAR_DTSTAMP,
15909a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank                CalendarUtilities.millisToEasDateTime(System.currentTimeMillis()));
15919a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank
1592d8161bff94bbaf513601243d7697f140d76deffeMarc Blank        String loc = entityValues.getAsString(Events.EVENT_LOCATION);
1593d8161bff94bbaf513601243d7697f140d76deffeMarc Blank        if (!TextUtils.isEmpty(loc)) {
1594d872a1cc547f83b1945f4e566a24313b51f4835dAndrew Stadler            if (version < Eas.SUPPORTED_PROTOCOL_EX2007_DOUBLE) {
1595d8161bff94bbaf513601243d7697f140d76deffeMarc Blank                // EAS 2.5 doesn't like bare line feeds
1596d8161bff94bbaf513601243d7697f140d76deffeMarc Blank                loc = Utility.replaceBareLfWithCrlf(loc);
1597d8161bff94bbaf513601243d7697f140d76deffeMarc Blank            }
1598d8161bff94bbaf513601243d7697f140d76deffeMarc Blank            s.data(Tags.CALENDAR_LOCATION, loc);
1599d8161bff94bbaf513601243d7697f140d76deffeMarc Blank        }
16002d0654d3e4f87a072fa667441cc92b2fc7e1d37fMarc Blank        s.writeStringValue(entityValues, Events.TITLE, Tags.CALENDAR_SUBJECT);
16019a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank
1602cb4d1b9a2b55a986fb5eef7397b13c4cdb812f88Marc Blank        if (version >= Eas.SUPPORTED_PROTOCOL_EX2007_DOUBLE) {
1603cb4d1b9a2b55a986fb5eef7397b13c4cdb812f88Marc Blank            s.start(Tags.BASE_BODY);
1604cb4d1b9a2b55a986fb5eef7397b13c4cdb812f88Marc Blank            s.data(Tags.BASE_TYPE, "1");
1605cb4d1b9a2b55a986fb5eef7397b13c4cdb812f88Marc Blank            s.writeStringValue(entityValues, Events.DESCRIPTION, Tags.BASE_DATA);
1606cb4d1b9a2b55a986fb5eef7397b13c4cdb812f88Marc Blank            s.end();
1607cb4d1b9a2b55a986fb5eef7397b13c4cdb812f88Marc Blank        } else {
1608cb4d1b9a2b55a986fb5eef7397b13c4cdb812f88Marc Blank            // EAS 2.5 doesn't like bare line feeds
1609cb4d1b9a2b55a986fb5eef7397b13c4cdb812f88Marc Blank            s.writeStringValue(entityValues, Events.DESCRIPTION, Tags.CALENDAR_BODY);
161080a330e76aafeb47edaa7dd2c4de4e5ce945a8c0Marc Blank        }
16119a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank
161280a330e76aafeb47edaa7dd2c4de4e5ce945a8c0Marc Blank        if (!isException) {
1613d872a1cc547f83b1945f4e566a24313b51f4835dAndrew Stadler            // For Exchange 2003, only upsync if the event is new
1614d872a1cc547f83b1945f4e566a24313b51f4835dAndrew Stadler            if ((version >= Eas.SUPPORTED_PROTOCOL_EX2007_DOUBLE) || !isChange) {
1615c0acdaa3b9df9f6aa5a5c63168cfa62dfcdba640Marc Blank                s.writeStringValue(entityValues, Events.ORGANIZER, Tags.CALENDAR_ORGANIZER_EMAIL);
1616c0acdaa3b9df9f6aa5a5c63168cfa62dfcdba640Marc Blank            }
16179a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank
16182d0654d3e4f87a072fa667441cc92b2fc7e1d37fMarc Blank            String rrule = entityValues.getAsString(Events.RRULE);
16192d0654d3e4f87a072fa667441cc92b2fc7e1d37fMarc Blank            if (rrule != null) {
16202d0654d3e4f87a072fa667441cc92b2fc7e1d37fMarc Blank                CalendarUtilities.recurrenceFromRrule(rrule, startTime, s);
16219a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank            }
16229a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank
16239a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank            // Handle associated data EXCEPT for attendees, which have to be grouped
16249a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank            ArrayList<NamedContentValues> subValues = entity.getSubValues();
162526b612ee7fc0db4ad8a963f8a76e6b7d8f82a3edMarc Blank            // The earliest of the reminders for this Event; we can only send one reminder...
162626b612ee7fc0db4ad8a963f8a76e6b7d8f82a3edMarc Blank            int earliestReminder = -1;
16279a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank            for (NamedContentValues ncv: subValues) {
16289a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank                Uri ncvUri = ncv.uri;
16299a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank                ContentValues ncvValues = ncv.values;
16309a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank                if (ncvUri.equals(ExtendedProperties.CONTENT_URI)) {
1631bb47b21664858f18ef5a32857aa95f82beb65006Andrew Stadler                    String propertyName =
1632bb47b21664858f18ef5a32857aa95f82beb65006Andrew Stadler                        ncvValues.getAsString(ExtendedProperties.NAME);
1633bb47b21664858f18ef5a32857aa95f82beb65006Andrew Stadler                    String propertyValue =
1634bb47b21664858f18ef5a32857aa95f82beb65006Andrew Stadler                        ncvValues.getAsString(ExtendedProperties.VALUE);
1635bb47b21664858f18ef5a32857aa95f82beb65006Andrew Stadler                    if (TextUtils.isEmpty(propertyValue)) {
1636bb47b21664858f18ef5a32857aa95f82beb65006Andrew Stadler                        continue;
1637bb47b21664858f18ef5a32857aa95f82beb65006Andrew Stadler                    }
1638ac3283a6b46bf27a5b418638c95a13121ea180e1Marc Blank                    if (propertyName.equals(EXTENDED_PROPERTY_CATEGORIES)) {
16399a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank                        // Send all the categories back to the server
16409a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank                        // We've saved them as a String of delimited tokens
16419a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank                        StringTokenizer st =
1642bb47b21664858f18ef5a32857aa95f82beb65006Andrew Stadler                            new StringTokenizer(propertyValue, CATEGORY_TOKENIZER_DELIMITER);
16439a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank                        if (st.countTokens() > 0) {
16449a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank                            s.start(Tags.CALENDAR_CATEGORIES);
16459a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank                            while (st.hasMoreTokens()) {
16469a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank                                String category = st.nextToken();
16479a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank                                s.data(Tags.CALENDAR_CATEGORY, category);
16489a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank                            }
16499a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank                            s.end();
16509a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank                        }
16519a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank                    }
16529a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank                } else if (ncvUri.equals(Reminders.CONTENT_URI)) {
165326b612ee7fc0db4ad8a963f8a76e6b7d8f82a3edMarc Blank                    Integer mins = ncvValues.getAsInteger(Reminders.MINUTES);
165426b612ee7fc0db4ad8a963f8a76e6b7d8f82a3edMarc Blank                    if (mins != null) {
165526b612ee7fc0db4ad8a963f8a76e6b7d8f82a3edMarc Blank                        // -1 means "default", which for Exchange, is 30
165626b612ee7fc0db4ad8a963f8a76e6b7d8f82a3edMarc Blank                        if (mins < 0) {
165726b612ee7fc0db4ad8a963f8a76e6b7d8f82a3edMarc Blank                            mins = 30;
165826b612ee7fc0db4ad8a963f8a76e6b7d8f82a3edMarc Blank                        }
165926b612ee7fc0db4ad8a963f8a76e6b7d8f82a3edMarc Blank                        // Save this away if it's the earliest reminder (greatest minutes)
166026b612ee7fc0db4ad8a963f8a76e6b7d8f82a3edMarc Blank                        if (mins > earliestReminder) {
166126b612ee7fc0db4ad8a963f8a76e6b7d8f82a3edMarc Blank                            earliestReminder = mins;
166226b612ee7fc0db4ad8a963f8a76e6b7d8f82a3edMarc Blank                        }
166326b612ee7fc0db4ad8a963f8a76e6b7d8f82a3edMarc Blank                    }
16649a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank                }
16659a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank            }
16669a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank
166726b612ee7fc0db4ad8a963f8a76e6b7d8f82a3edMarc Blank            // If we have a reminder, send it to the server
166826b612ee7fc0db4ad8a963f8a76e6b7d8f82a3edMarc Blank            if (earliestReminder >= 0) {
166926b612ee7fc0db4ad8a963f8a76e6b7d8f82a3edMarc Blank                s.data(Tags.CALENDAR_REMINDER_MINS_BEFORE, Integer.toString(earliestReminder));
167026b612ee7fc0db4ad8a963f8a76e6b7d8f82a3edMarc Blank            }
167126b612ee7fc0db4ad8a963f8a76e6b7d8f82a3edMarc Blank
1672516941ad621dda8c20dc477b6ab8bdaee305f83aMarc Blank            // We've got to send a UID, unless this is an exception.  If the event is new, we've
1673516941ad621dda8c20dc477b6ab8bdaee305f83aMarc Blank            // generated one; if not, we should have gotten one from extended properties.
1674516941ad621dda8c20dc477b6ab8bdaee305f83aMarc Blank            if (clientId != null) {
1675516941ad621dda8c20dc477b6ab8bdaee305f83aMarc Blank                s.data(Tags.CALENDAR_UID, clientId);
1676516941ad621dda8c20dc477b6ab8bdaee305f83aMarc Blank            }
16779a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank
16789a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank            // Handle attendee data here; keep track of organizer and stream it afterward
16799a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank            String organizerName = null;
1680bb47b21664858f18ef5a32857aa95f82beb65006Andrew Stadler            String organizerEmail = null;
16819a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank            for (NamedContentValues ncv: subValues) {
16829a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank                Uri ncvUri = ncv.uri;
16839a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank                ContentValues ncvValues = ncv.values;
16849a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank                if (ncvUri.equals(Attendees.CONTENT_URI)) {
16852d0654d3e4f87a072fa667441cc92b2fc7e1d37fMarc Blank                    Integer relationship = ncvValues.getAsInteger(Attendees.ATTENDEE_RELATIONSHIP);
168616110d7721ce436b127cf3106e94344290b0e80aMarc Blank                    // If there's no relationship, we can't create this for EAS
168716110d7721ce436b127cf3106e94344290b0e80aMarc Blank                    // Similarly, we need an attendee email for each invitee
168816110d7721ce436b127cf3106e94344290b0e80aMarc Blank                    if (relationship != null && ncvValues.containsKey(Attendees.ATTENDEE_EMAIL)) {
16899a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank                        // Organizer isn't among attendees in EAS
16909a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank                        if (relationship == Attendees.RELATIONSHIP_ORGANIZER) {
16912d0654d3e4f87a072fa667441cc92b2fc7e1d37fMarc Blank                            organizerName = ncvValues.getAsString(Attendees.ATTENDEE_NAME);
1692bb47b21664858f18ef5a32857aa95f82beb65006Andrew Stadler                            organizerEmail = ncvValues.getAsString(Attendees.ATTENDEE_EMAIL);
16939a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank                            continue;
16949a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank                        }
16959a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank                        if (!hasAttendees) {
16969a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank                            s.start(Tags.CALENDAR_ATTENDEES);
16979a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank                            hasAttendees = true;
16989a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank                        }
16999a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank                        s.start(Tags.CALENDAR_ATTENDEE);
1700c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank                        String attendeeEmail = ncvValues.getAsString(Attendees.ATTENDEE_EMAIL);
170116110d7721ce436b127cf3106e94344290b0e80aMarc Blank                        String attendeeName = ncvValues.getAsString(Attendees.ATTENDEE_NAME);
170216110d7721ce436b127cf3106e94344290b0e80aMarc Blank                        if (attendeeName == null) {
1703c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank                            attendeeName = attendeeEmail;
170416110d7721ce436b127cf3106e94344290b0e80aMarc Blank                        }
170516110d7721ce436b127cf3106e94344290b0e80aMarc Blank                        s.data(Tags.CALENDAR_ATTENDEE_NAME, attendeeName);
1706c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank                        s.data(Tags.CALENDAR_ATTENDEE_EMAIL, attendeeEmail);
1707d872a1cc547f83b1945f4e566a24313b51f4835dAndrew Stadler                        if (version >= Eas.SUPPORTED_PROTOCOL_EX2007_DOUBLE) {
17089cf6e79160ee3d11f2aceebadecab2520ee85841Marc Blank                            s.data(Tags.CALENDAR_ATTENDEE_TYPE, "1"); // Required
17099cf6e79160ee3d11f2aceebadecab2520ee85841Marc Blank                        }
17109a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank                        s.end(); // Attendee
1711c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank                     }
17129a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank                }
17139a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank            }
17149a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank            if (hasAttendees) {
17159a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank                s.end();  // Attendees
17169a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank            }
1717c0acdaa3b9df9f6aa5a5c63168cfa62dfcdba640Marc Blank
1718e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank            // Get busy status from availability
1719e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank            int availability = entityValues.getAsInteger(Events.AVAILABILITY);
1720e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank            int busyStatus = CalendarUtilities.busyStatusFromAvailability(availability);
1721edcfd554728a27a7678306ea7432bd8676ec6990Marc Blank            s.data(Tags.CALENDAR_BUSY_STATUS, Integer.toString(busyStatus));
1722edcfd554728a27a7678306ea7432bd8676ec6990Marc Blank
1723bb47b21664858f18ef5a32857aa95f82beb65006Andrew Stadler            // Meeting status, 0 = appointment, 1 = meeting, 3 = attendee
172418bb75cdd66c14113e179e472a92b21b37de4644Marc Blank            // In JB, organizer won't be an attendee
172518bb75cdd66c14113e179e472a92b21b37de4644Marc Blank            if (organizerEmail == null && entityValues.containsKey(Events.ORGANIZER)) {
172618bb75cdd66c14113e179e472a92b21b37de4644Marc Blank                organizerEmail = entityValues.getAsString(Events.ORGANIZER);
172718bb75cdd66c14113e179e472a92b21b37de4644Marc Blank            }
1728edcfd554728a27a7678306ea7432bd8676ec6990Marc Blank            if (mEmailAddress.equalsIgnoreCase(organizerEmail)) {
1729bb47b21664858f18ef5a32857aa95f82beb65006Andrew Stadler                s.data(Tags.CALENDAR_MEETING_STATUS, hasAttendees ? "1" : "0");
1730bb47b21664858f18ef5a32857aa95f82beb65006Andrew Stadler            } else {
1731bb47b21664858f18ef5a32857aa95f82beb65006Andrew Stadler                s.data(Tags.CALENDAR_MEETING_STATUS, "3");
1732bb47b21664858f18ef5a32857aa95f82beb65006Andrew Stadler            }
1733bb47b21664858f18ef5a32857aa95f82beb65006Andrew Stadler
1734d872a1cc547f83b1945f4e566a24313b51f4835dAndrew Stadler            // For Exchange 2003, only upsync if the event is new
1735d872a1cc547f83b1945f4e566a24313b51f4835dAndrew Stadler            if (((version >= Eas.SUPPORTED_PROTOCOL_EX2007_DOUBLE) || !isChange) &&
1736d872a1cc547f83b1945f4e566a24313b51f4835dAndrew Stadler                    organizerName != null) {
17379a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank                s.data(Tags.CALENDAR_ORGANIZER_NAME, organizerName);
17389a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank            }
1739ad14e4179f58ec05a5accc6bcfef66cd61d50d44Marc Blank
17409aabf6ca8be1a9208611180442c2755a2c10bb7cMarc Blank            // NOTE: Sensitivity must NOT be sent to the server for exceptions in Exchange 2003
17419aabf6ca8be1a9208611180442c2755a2c10bb7cMarc Blank            // The result will be a status 6 failure during sync
17429e86eb14c6e1f7d7730f8ca6953fdfd95fe2b143RoboErik            Integer visibility = entityValues.getAsInteger(Events.ACCESS_LEVEL);
17439aabf6ca8be1a9208611180442c2755a2c10bb7cMarc Blank            if (visibility != null) {
17449aabf6ca8be1a9208611180442c2755a2c10bb7cMarc Blank                s.data(Tags.CALENDAR_SENSITIVITY, decodeVisibility(visibility));
17459aabf6ca8be1a9208611180442c2755a2c10bb7cMarc Blank            } else {
17469aabf6ca8be1a9208611180442c2755a2c10bb7cMarc Blank                // Default to private if not set
17479aabf6ca8be1a9208611180442c2755a2c10bb7cMarc Blank                s.data(Tags.CALENDAR_SENSITIVITY, "1");
1748ad14e4179f58ec05a5accc6bcfef66cd61d50d44Marc Blank            }
17499a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank        }
17509a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank    }
17519a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank
17523bdf62324b9eb8ff2ab9333349591fb77c151bf7Marc Blank    /**
17533bdf62324b9eb8ff2ab9333349591fb77c151bf7Marc Blank     * Convenience method for sending an email to the organizer declining the meeting
17543bdf62324b9eb8ff2ab9333349591fb77c151bf7Marc Blank     * @param entity
17553bdf62324b9eb8ff2ab9333349591fb77c151bf7Marc Blank     * @param clientId
17563bdf62324b9eb8ff2ab9333349591fb77c151bf7Marc Blank     */
17573bdf62324b9eb8ff2ab9333349591fb77c151bf7Marc Blank    private void sendDeclinedEmail(Entity entity, String clientId) {
17583bdf62324b9eb8ff2ab9333349591fb77c151bf7Marc Blank        Message msg =
17593bdf62324b9eb8ff2ab9333349591fb77c151bf7Marc Blank            CalendarUtilities.createMessageForEntity(mContext, entity,
17603bdf62324b9eb8ff2ab9333349591fb77c151bf7Marc Blank                    Message.FLAG_OUTGOING_MEETING_DECLINE, clientId, mAccount);
17613bdf62324b9eb8ff2ab9333349591fb77c151bf7Marc Blank        if (msg != null) {
17623bdf62324b9eb8ff2ab9333349591fb77c151bf7Marc Blank            userLog("Queueing declined response to " + msg.mTo);
17633bdf62324b9eb8ff2ab9333349591fb77c151bf7Marc Blank            mOutgoingMailList.add(msg);
17643bdf62324b9eb8ff2ab9333349591fb77c151bf7Marc Blank        }
17653bdf62324b9eb8ff2ab9333349591fb77c151bf7Marc Blank    }
17663bdf62324b9eb8ff2ab9333349591fb77c151bf7Marc Blank
17675862a85e17e81866ca82a9905577931947fbd44eMarc Blank    @Override
17685862a85e17e81866ca82a9905577931947fbd44eMarc Blank    public boolean sendLocalChanges(Serializer s) throws IOException {
17695862a85e17e81866ca82a9905577931947fbd44eMarc Blank        ContentResolver cr = mService.mContentResolver;
17705862a85e17e81866ca82a9905577931947fbd44eMarc Blank
17715862a85e17e81866ca82a9905577931947fbd44eMarc Blank        if (getSyncKey().equals("0")) {
17725862a85e17e81866ca82a9905577931947fbd44eMarc Blank            return false;
17735862a85e17e81866ca82a9905577931947fbd44eMarc Blank        }
17745862a85e17e81866ca82a9905577931947fbd44eMarc Blank
17755862a85e17e81866ca82a9905577931947fbd44eMarc Blank        try {
17769a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank            // We've got to handle exceptions as part of the parent when changes occur, so we need
17779a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank            // to find new/changed exceptions and mark the parent dirty
1778dafc866120dac68fabbddcc9943e3995894c5244Marc Blank            ArrayList<Long> orphanedExceptions = new ArrayList<Long>();
17799cf6e79160ee3d11f2aceebadecab2520ee85841Marc Blank            Cursor c = cr.query(Events.CONTENT_URI, ORIGINAL_EVENT_PROJECTION,
17809cf6e79160ee3d11f2aceebadecab2520ee85841Marc Blank                    DIRTY_EXCEPTION_IN_CALENDAR, mCalendarIdArgument, null);
17819a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank            try {
17829a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank                ContentValues cv = new ContentValues();
1783dafc866120dac68fabbddcc9943e3995894c5244Marc Blank                // We use _sync_mark here to distinguish dirty parents from parents with dirty
1784dafc866120dac68fabbddcc9943e3995894c5244Marc Blank                // exceptions
178504c880a6b5ad041f172d4b1eeecc06d6a06e4141RoboErik                cv.put(EVENT_SYNC_MARK, "1");
17869a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank                while (c.moveToNext()) {
1787dafc866120dac68fabbddcc9943e3995894c5244Marc Blank                    // Mark the parents of dirty exceptions
1788c4ff4569299e8df6da7812af61dbff6775097018RoboErik                    long parentId = c.getLong(0);
17896989716b639d274a98141674556ac9402be32ebeRoboErik                    int cnt = cr.update(
17906989716b639d274a98141674556ac9402be32ebeRoboErik                            asSyncAdapter(Events.CONTENT_URI, mEmailAddress,
17916989716b639d274a98141674556ac9402be32ebeRoboErik                                    Eas.EXCHANGE_ACCOUNT_MANAGER_TYPE), cv,
1792c4ff4569299e8df6da7812af61dbff6775097018RoboErik                            EVENT_ID_AND_CALENDAR_ID, new String[] {
1793c4ff4569299e8df6da7812af61dbff6775097018RoboErik                                    Long.toString(parentId), mCalendarIdString
17946989716b639d274a98141674556ac9402be32ebeRoboErik                            });
1795dafc866120dac68fabbddcc9943e3995894c5244Marc Blank                    // Keep track of any orphaned exceptions
1796dafc866120dac68fabbddcc9943e3995894c5244Marc Blank                    if (cnt == 0) {
1797dafc866120dac68fabbddcc9943e3995894c5244Marc Blank                        orphanedExceptions.add(c.getLong(1));
1798dafc866120dac68fabbddcc9943e3995894c5244Marc Blank                    }
17999a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank                }
18009a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank            } finally {
18019a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank                c.close();
18029a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank            }
18039a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank
18046989716b639d274a98141674556ac9402be32ebeRoboErik            // Delete any orphaned exceptions
18056989716b639d274a98141674556ac9402be32ebeRoboErik            for (long orphan : orphanedExceptions) {
1806dafc866120dac68fabbddcc9943e3995894c5244Marc Blank                userLog(TAG, "Deleted orphaned exception: " + orphan);
18076989716b639d274a98141674556ac9402be32ebeRoboErik                cr.delete(
18086989716b639d274a98141674556ac9402be32ebeRoboErik                        asSyncAdapter(ContentUris.withAppendedId(Events.CONTENT_URI, orphan),
18096989716b639d274a98141674556ac9402be32ebeRoboErik                                mEmailAddress, Eas.EXCHANGE_ACCOUNT_MANAGER_TYPE), null, null);
1810dafc866120dac68fabbddcc9943e3995894c5244Marc Blank            }
1811dafc866120dac68fabbddcc9943e3995894c5244Marc Blank            orphanedExceptions.clear();
1812dafc866120dac68fabbddcc9943e3995894c5244Marc Blank
18136989716b639d274a98141674556ac9402be32ebeRoboErik            // Now we can go through dirty/marked top-level events and send them
18146989716b639d274a98141674556ac9402be32ebeRoboErik            // back to the server
18156989716b639d274a98141674556ac9402be32ebeRoboErik            EntityIterator eventIterator = EventsEntity.newEntityIterator(cr.query(
18166989716b639d274a98141674556ac9402be32ebeRoboErik                    asSyncAdapter(Events.CONTENT_URI, mEmailAddress,
18176989716b639d274a98141674556ac9402be32ebeRoboErik                            Eas.EXCHANGE_ACCOUNT_MANAGER_TYPE), null,
18186989716b639d274a98141674556ac9402be32ebeRoboErik                    DIRTY_OR_MARKED_TOP_LEVEL_IN_CALENDAR, mCalendarIdArgument, null), cr);
18195862a85e17e81866ca82a9905577931947fbd44eMarc Blank            ContentValues cidValues = new ContentValues();
1820b7d36912c028e0eb2e4c01102c9375e980bbe721Marc Blank
18215862a85e17e81866ca82a9905577931947fbd44eMarc Blank            try {
18225862a85e17e81866ca82a9905577931947fbd44eMarc Blank                boolean first = true;
18239a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank                while (eventIterator.hasNext()) {
18249a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank                    Entity entity = eventIterator.next();
1825580d8c26fe7e1de93c5d1a123ba261e4b2a9c852Marc Blank
18265862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    // For each of these entities, create the change commands
18275862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    ContentValues entityValues = entity.getEntityValues();
18285862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    String serverId = entityValues.getAsString(Events._SYNC_ID);
18295862a85e17e81866ca82a9905577931947fbd44eMarc Blank
1830e588dbc379872f431426dbd3a2d5fe578e37e0b4Marc Blank                    // We first need to check whether we can upsync this event; our test for this
1831e588dbc379872f431426dbd3a2d5fe578e37e0b4Marc Blank                    // is currently the value of EXTENDED_PROPERTY_ATTENDEES_REDACTED
1832e588dbc379872f431426dbd3a2d5fe578e37e0b4Marc Blank                    // If this is set to "1", we can't upsync the event
1833e588dbc379872f431426dbd3a2d5fe578e37e0b4Marc Blank                    for (NamedContentValues ncv: entity.getSubValues()) {
1834e588dbc379872f431426dbd3a2d5fe578e37e0b4Marc Blank                        if (ncv.uri.equals(ExtendedProperties.CONTENT_URI)) {
1835e588dbc379872f431426dbd3a2d5fe578e37e0b4Marc Blank                            ContentValues ncvValues = ncv.values;
1836e588dbc379872f431426dbd3a2d5fe578e37e0b4Marc Blank                            if (ncvValues.getAsString(ExtendedProperties.NAME).equals(
1837e588dbc379872f431426dbd3a2d5fe578e37e0b4Marc Blank                                    EXTENDED_PROPERTY_UPSYNC_PROHIBITED)) {
1838e588dbc379872f431426dbd3a2d5fe578e37e0b4Marc Blank                                if ("1".equals(ncvValues.getAsString(ExtendedProperties.VALUE))) {
1839e588dbc379872f431426dbd3a2d5fe578e37e0b4Marc Blank                                    // Make sure we mark this to clear the dirty flag
1840e588dbc379872f431426dbd3a2d5fe578e37e0b4Marc Blank                                    mUploadedIdList.add(entityValues.getAsLong(Events._ID));
1841e588dbc379872f431426dbd3a2d5fe578e37e0b4Marc Blank                                    continue;
1842e588dbc379872f431426dbd3a2d5fe578e37e0b4Marc Blank                                }
1843e588dbc379872f431426dbd3a2d5fe578e37e0b4Marc Blank                            }
1844e588dbc379872f431426dbd3a2d5fe578e37e0b4Marc Blank                        }
1845e588dbc379872f431426dbd3a2d5fe578e37e0b4Marc Blank                    }
1846e588dbc379872f431426dbd3a2d5fe578e37e0b4Marc Blank
1847580d8c26fe7e1de93c5d1a123ba261e4b2a9c852Marc Blank                    // Find our uid in the entity; otherwise create one
184804c880a6b5ad041f172d4b1eeecc06d6a06e4141RoboErik                    String clientId = entityValues.getAsString(Events.SYNC_DATA2);
1849580d8c26fe7e1de93c5d1a123ba261e4b2a9c852Marc Blank                    if (clientId == null) {
1850580d8c26fe7e1de93c5d1a123ba261e4b2a9c852Marc Blank                        clientId = UUID.randomUUID().toString();
1851580d8c26fe7e1de93c5d1a123ba261e4b2a9c852Marc Blank                    }
1852580d8c26fe7e1de93c5d1a123ba261e4b2a9c852Marc Blank
185314045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank                    // EAS 2.5 needs: BusyStatus DtStamp EndTime Sensitivity StartTime TimeZone UID
185414045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank                    // We can generate all but what we're testing for below
1855c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank                    String organizerEmail = entityValues.getAsString(Events.ORGANIZER);
1856edcfd554728a27a7678306ea7432bd8676ec6990Marc Blank                    boolean selfOrganizer = organizerEmail.equalsIgnoreCase(mEmailAddress);
1857b7d36912c028e0eb2e4c01102c9375e980bbe721Marc Blank
185814045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank                    if (!entityValues.containsKey(Events.DTSTART)
1859377230593dca7cb01483bfaf93959e5821f5f028Marc Blank                            || (!entityValues.containsKey(Events.DURATION) &&
1860c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank                                    !entityValues.containsKey(Events.DTEND))
1861c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank                                    || organizerEmail == null) {
186214045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank                        continue;
186314045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank                    }
186414045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank
18655862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    if (first) {
18665862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        s.start(Tags.SYNC_COMMANDS);
18675862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        userLog("Sending Calendar changes to the server");
18685862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        first = false;
18695862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    }
1870c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank                    long eventId = entityValues.getAsLong(Events._ID);
18715862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    if (serverId == null) {
18725862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        // This is a new event; create a clientId
18735862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        userLog("Creating new event with clientId: ", clientId);
18745862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        s.start(Tags.SYNC_ADD).data(Tags.SYNC_CLIENT_ID, clientId);
18755862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        // And save it in the Event as the local id
187604c880a6b5ad041f172d4b1eeecc06d6a06e4141RoboErik                        cidValues.put(Events.SYNC_DATA2, clientId);
187704c880a6b5ad041f172d4b1eeecc06d6a06e4141RoboErik                        cidValues.put(EVENT_SYNC_VERSION, "0");
18786989716b639d274a98141674556ac9402be32ebeRoboErik                        cr.update(
18796989716b639d274a98141674556ac9402be32ebeRoboErik                                asSyncAdapter(
18806989716b639d274a98141674556ac9402be32ebeRoboErik                                        ContentUris.withAppendedId(Events.CONTENT_URI, eventId),
18816989716b639d274a98141674556ac9402be32ebeRoboErik                                        mEmailAddress, Eas.EXCHANGE_ACCOUNT_MANAGER_TYPE),
18826989716b639d274a98141674556ac9402be32ebeRoboErik                                cidValues, null, null);
18835862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    } else {
1884670478b801a5e229da39c198f4cb84f95a0da3efMarc Blank                        if (entityValues.getAsInteger(Events.DELETED) == 1) {
18855862a85e17e81866ca82a9905577931947fbd44eMarc Blank                            userLog("Deleting event with serverId: ", serverId);
18865862a85e17e81866ca82a9905577931947fbd44eMarc Blank                            s.start(Tags.SYNC_DELETE).data(Tags.SYNC_SERVER_ID, serverId).end();
188730d2d4ea74cfca9d27dfd495cebc8387b8f2454dMarc Blank                            mDeletedIdList.add(eventId);
1888b7d36912c028e0eb2e4c01102c9375e980bbe721Marc Blank                            if (selfOrganizer) {
188930d2d4ea74cfca9d27dfd495cebc8387b8f2454dMarc Blank                                mSendCancelIdList.add(eventId);
18903bdf62324b9eb8ff2ab9333349591fb77c151bf7Marc Blank                            } else {
18913bdf62324b9eb8ff2ab9333349591fb77c151bf7Marc Blank                                sendDeclinedEmail(entity, clientId);
189230d2d4ea74cfca9d27dfd495cebc8387b8f2454dMarc Blank                            }
18935862a85e17e81866ca82a9905577931947fbd44eMarc Blank                            continue;
18945862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        }
18955862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        userLog("Upsync change to event with serverId: " + serverId);
1896820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank                        // Get the current version
189704c880a6b5ad041f172d4b1eeecc06d6a06e4141RoboErik                        String version = entityValues.getAsString(EVENT_SYNC_VERSION);
1898820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank                        // This should never be null, but catch this error anyway
1899820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank                        // Version should be "0" when we create the event, so use that
1900820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank                        if (version == null) {
1901820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank                            version = "0";
1902820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank                        } else {
1903820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank                            // Increment and save
1904820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank                            try {
1905820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank                                version = Integer.toString((Integer.parseInt(version) + 1));
1906820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank                            } catch (Exception e) {
1907820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank                                // Handle the case in which someone writes a non-integer here;
1908820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank                                // shouldn't happen, but we don't want to kill the sync for his
1909820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank                                version = "0";
1910820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank                            }
1911820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank                        }
191204c880a6b5ad041f172d4b1eeecc06d6a06e4141RoboErik                        cidValues.put(EVENT_SYNC_VERSION, version);
1913820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank                        // Also save in entityValues so that we send it this time around
191404c880a6b5ad041f172d4b1eeecc06d6a06e4141RoboErik                        entityValues.put(EVENT_SYNC_VERSION, version);
19156989716b639d274a98141674556ac9402be32ebeRoboErik                        cr.update(
19166989716b639d274a98141674556ac9402be32ebeRoboErik                                asSyncAdapter(
19176989716b639d274a98141674556ac9402be32ebeRoboErik                                        ContentUris.withAppendedId(Events.CONTENT_URI, eventId),
19186989716b639d274a98141674556ac9402be32ebeRoboErik                                        mEmailAddress, Eas.EXCHANGE_ACCOUNT_MANAGER_TYPE),
19196989716b639d274a98141674556ac9402be32ebeRoboErik                                cidValues, null, null);
19205862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        s.start(Tags.SYNC_CHANGE).data(Tags.SYNC_SERVER_ID, serverId);
19215862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    }
19225862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    s.start(Tags.SYNC_APPLICATION_DATA);
1923c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank
1924332d08cc2fc37d9936a73e3a120125c60b0587d1Marc Blank                    sendEvent(entity, clientId, s);
19259a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank
19269a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank                    // Now, the hard part; find exceptions for this event
19279a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank                    if (serverId != null) {
19286989716b639d274a98141674556ac9402be32ebeRoboErik                        EntityIterator exIterator = EventsEntity.newEntityIterator(cr.query(
19296989716b639d274a98141674556ac9402be32ebeRoboErik                                asSyncAdapter(Events.CONTENT_URI, mEmailAddress,
19306989716b639d274a98141674556ac9402be32ebeRoboErik                                        Eas.EXCHANGE_ACCOUNT_MANAGER_TYPE), null,
19316989716b639d274a98141674556ac9402be32ebeRoboErik                                ORIGINAL_EVENT_AND_CALENDAR, new String[] {
19326989716b639d274a98141674556ac9402be32ebeRoboErik                                        serverId, mCalendarIdString
19336989716b639d274a98141674556ac9402be32ebeRoboErik                                }, null), cr);
19349a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank                        boolean exFirst = true;
1935dafc866120dac68fabbddcc9943e3995894c5244Marc Blank                        while (exIterator.hasNext()) {
1936dafc866120dac68fabbddcc9943e3995894c5244Marc Blank                            Entity exEntity = exIterator.next();
19379a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank                            if (exFirst) {
19389a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank                                s.start(Tags.CALENDAR_EXCEPTIONS);
19399a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank                                exFirst = false;
19405862a85e17e81866ca82a9905577931947fbd44eMarc Blank                            }
19419a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank                            s.start(Tags.CALENDAR_EXCEPTION);
1942dafc866120dac68fabbddcc9943e3995894c5244Marc Blank                            sendEvent(exEntity, null, s);
1943dafc866120dac68fabbddcc9943e3995894c5244Marc Blank                            ContentValues exValues = exEntity.getEntityValues();
19449e86eb14c6e1f7d7730f8ca6953fdfd95fe2b143RoboErik                            if (getInt(exValues, Events.DIRTY) == 1) {
1945dafc866120dac68fabbddcc9943e3995894c5244Marc Blank                                // This is a new/updated exception, so we've got to notify our
1946dafc866120dac68fabbddcc9943e3995894c5244Marc Blank                                // attendees about it
1947dafc866120dac68fabbddcc9943e3995894c5244Marc Blank                                long exEventId = exValues.getAsLong(Events._ID);
1948dafc866120dac68fabbddcc9943e3995894c5244Marc Blank                                int flag;
194902ed5e14209cf1b4a43c64ee091e2696760cfe17Marc Blank
19505a75c42d793d1d4234f0fd5cd00ea34bd7c0625fMarc Blank                                // Copy subvalues into the exception; otherwise, we won't see the
19515a75c42d793d1d4234f0fd5cd00ea34bd7c0625fMarc Blank                                // attendees when preparing the message
19525a75c42d793d1d4234f0fd5cd00ea34bd7c0625fMarc Blank                                for (NamedContentValues ncv: entity.getSubValues()) {
19535a75c42d793d1d4234f0fd5cd00ea34bd7c0625fMarc Blank                                    exEntity.addSubValue(ncv.uri, ncv.values);
19545a75c42d793d1d4234f0fd5cd00ea34bd7c0625fMarc Blank                                }
19555a75c42d793d1d4234f0fd5cd00ea34bd7c0625fMarc Blank
1956670478b801a5e229da39c198f4cb84f95a0da3efMarc Blank                                if ((getInt(exValues, Events.DELETED) == 1) ||
1957b7d36912c028e0eb2e4c01102c9375e980bbe721Marc Blank                                        (getInt(exValues, Events.STATUS) ==
1958b7d36912c028e0eb2e4c01102c9375e980bbe721Marc Blank                                            Events.STATUS_CANCELED)) {
1959dafc866120dac68fabbddcc9943e3995894c5244Marc Blank                                    flag = Message.FLAG_OUTGOING_MEETING_CANCEL;
19602741504faf85de35e800c6070c932b4be1564b3fMarc Blank                                    if (!selfOrganizer) {
19612741504faf85de35e800c6070c932b4be1564b3fMarc Blank                                        // Send a cancellation notice to the organizer
19622741504faf85de35e800c6070c932b4be1564b3fMarc Blank                                        // Since CalendarProvider2 sets the organizer of exceptions
19632741504faf85de35e800c6070c932b4be1564b3fMarc Blank                                        // to the user, we have to reset it first to the original
19642741504faf85de35e800c6070c932b4be1564b3fMarc Blank                                        // organizer
19652741504faf85de35e800c6070c932b4be1564b3fMarc Blank                                        exValues.put(Events.ORGANIZER,
19662741504faf85de35e800c6070c932b4be1564b3fMarc Blank                                                entityValues.getAsString(Events.ORGANIZER));
19672741504faf85de35e800c6070c932b4be1564b3fMarc Blank                                        sendDeclinedEmail(exEntity, clientId);
19682741504faf85de35e800c6070c932b4be1564b3fMarc Blank                                    }
1969dafc866120dac68fabbddcc9943e3995894c5244Marc Blank                                } else {
1970dafc866120dac68fabbddcc9943e3995894c5244Marc Blank                                    flag = Message.FLAG_OUTGOING_MEETING_INVITE;
1971dafc866120dac68fabbddcc9943e3995894c5244Marc Blank                                }
197202ed5e14209cf1b4a43c64ee091e2696760cfe17Marc Blank                                // Add the eventId of the exception to the uploaded id list, so that
197302ed5e14209cf1b4a43c64ee091e2696760cfe17Marc Blank                                // the dirty/mark bits are cleared
197402ed5e14209cf1b4a43c64ee091e2696760cfe17Marc Blank                                mUploadedIdList.add(exEventId);
1975dafc866120dac68fabbddcc9943e3995894c5244Marc Blank
1976dafc866120dac68fabbddcc9943e3995894c5244Marc Blank                                // Copy version so the ics attachment shows the proper sequence #
197704c880a6b5ad041f172d4b1eeecc06d6a06e4141RoboErik                                exValues.put(EVENT_SYNC_VERSION,
197804c880a6b5ad041f172d4b1eeecc06d6a06e4141RoboErik                                        entityValues.getAsString(EVENT_SYNC_VERSION));
1979dafc866120dac68fabbddcc9943e3995894c5244Marc Blank                                // Copy location so that it's included in the outgoing email
1980dafc866120dac68fabbddcc9943e3995894c5244Marc Blank                                if (entityValues.containsKey(Events.EVENT_LOCATION)) {
1981dafc866120dac68fabbddcc9943e3995894c5244Marc Blank                                    exValues.put(Events.EVENT_LOCATION,
19823bdf62324b9eb8ff2ab9333349591fb77c151bf7Marc Blank                                            entityValues.getAsString(Events.EVENT_LOCATION));
19833bdf62324b9eb8ff2ab9333349591fb77c151bf7Marc Blank                                }
19843bdf62324b9eb8ff2ab9333349591fb77c151bf7Marc Blank
1985b7d36912c028e0eb2e4c01102c9375e980bbe721Marc Blank                                if (selfOrganizer) {
1986b7d36912c028e0eb2e4c01102c9375e980bbe721Marc Blank                                    Message msg =
1987b7d36912c028e0eb2e4c01102c9375e980bbe721Marc Blank                                        CalendarUtilities.createMessageForEntity(mContext,
1988b7d36912c028e0eb2e4c01102c9375e980bbe721Marc Blank                                                exEntity, flag, clientId, mAccount);
1989b7d36912c028e0eb2e4c01102c9375e980bbe721Marc Blank                                    if (msg != null) {
1990b7d36912c028e0eb2e4c01102c9375e980bbe721Marc Blank                                        userLog("Queueing exception update to " + msg.mTo);
1991b7d36912c028e0eb2e4c01102c9375e980bbe721Marc Blank                                        mOutgoingMailList.add(msg);
1992b7d36912c028e0eb2e4c01102c9375e980bbe721Marc Blank                                    }
1993dafc866120dac68fabbddcc9943e3995894c5244Marc Blank                                }
1994dafc866120dac68fabbddcc9943e3995894c5244Marc Blank                            }
19959a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank                            s.end(); // EXCEPTION
19965862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        }
19979a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank                        if (!exFirst) {
19989a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank                            s.end(); // EXCEPTIONS
19995862a85e17e81866ca82a9905577931947fbd44eMarc Blank                        }
20005862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    }
20019a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank
20025862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    s.end().end(); // ApplicationData & Change
2003e96bd20257c47db94f9166fd37ca00e0c73788afMarc Blank                    mUploadedIdList.add(eventId);
2004c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank
20055a75c42d793d1d4234f0fd5cd00ea34bd7c0625fMarc Blank                    // Go through the extended properties of this Event and pull out our tokenized
20065a75c42d793d1d4234f0fd5cd00ea34bd7c0625fMarc Blank                    // attendees list and the user attendee status; we will need them later
20075a75c42d793d1d4234f0fd5cd00ea34bd7c0625fMarc Blank                    String attendeeString = null;
20085a75c42d793d1d4234f0fd5cd00ea34bd7c0625fMarc Blank                    long attendeeStringId = -1;
20095a75c42d793d1d4234f0fd5cd00ea34bd7c0625fMarc Blank                    String userAttendeeStatus = null;
20105a75c42d793d1d4234f0fd5cd00ea34bd7c0625fMarc Blank                    long userAttendeeStatusId = -1;
20115a75c42d793d1d4234f0fd5cd00ea34bd7c0625fMarc Blank                    for (NamedContentValues ncv: entity.getSubValues()) {
20125a75c42d793d1d4234f0fd5cd00ea34bd7c0625fMarc Blank                        if (ncv.uri.equals(ExtendedProperties.CONTENT_URI)) {
20135a75c42d793d1d4234f0fd5cd00ea34bd7c0625fMarc Blank                            ContentValues ncvValues = ncv.values;
20145a75c42d793d1d4234f0fd5cd00ea34bd7c0625fMarc Blank                            String propertyName =
20155a75c42d793d1d4234f0fd5cd00ea34bd7c0625fMarc Blank                                ncvValues.getAsString(ExtendedProperties.NAME);
2016ac3283a6b46bf27a5b418638c95a13121ea180e1Marc Blank                            if (propertyName.equals(EXTENDED_PROPERTY_ATTENDEES)) {
20175a75c42d793d1d4234f0fd5cd00ea34bd7c0625fMarc Blank                                attendeeString =
20185a75c42d793d1d4234f0fd5cd00ea34bd7c0625fMarc Blank                                    ncvValues.getAsString(ExtendedProperties.VALUE);
20195a75c42d793d1d4234f0fd5cd00ea34bd7c0625fMarc Blank                                attendeeStringId =
20205a75c42d793d1d4234f0fd5cd00ea34bd7c0625fMarc Blank                                    ncvValues.getAsLong(ExtendedProperties._ID);
2021ac3283a6b46bf27a5b418638c95a13121ea180e1Marc Blank                            } else if (propertyName.equals(
2022ac3283a6b46bf27a5b418638c95a13121ea180e1Marc Blank                                    EXTENDED_PROPERTY_USER_ATTENDEE_STATUS)) {
20235a75c42d793d1d4234f0fd5cd00ea34bd7c0625fMarc Blank                                userAttendeeStatus =
20245a75c42d793d1d4234f0fd5cd00ea34bd7c0625fMarc Blank                                    ncvValues.getAsString(ExtendedProperties.VALUE);
20255a75c42d793d1d4234f0fd5cd00ea34bd7c0625fMarc Blank                                userAttendeeStatusId =
20265a75c42d793d1d4234f0fd5cd00ea34bd7c0625fMarc Blank                                    ncvValues.getAsLong(ExtendedProperties._ID);
20275a75c42d793d1d4234f0fd5cd00ea34bd7c0625fMarc Blank                            }
20285a75c42d793d1d4234f0fd5cd00ea34bd7c0625fMarc Blank                        }
20295a75c42d793d1d4234f0fd5cd00ea34bd7c0625fMarc Blank                    }
20305a75c42d793d1d4234f0fd5cd00ea34bd7c0625fMarc Blank
2031dafc866120dac68fabbddcc9943e3995894c5244Marc Blank                    // Send the meeting invite if there are attendees and we're the organizer AND
2032dafc866120dac68fabbddcc9943e3995894c5244Marc Blank                    // if the Event itself is dirty (we might be syncing only because an exception
2033332d08cc2fc37d9936a73e3a120125c60b0587d1Marc Blank                    // is dirty, in which case we DON'T send email about the Event)
2034332d08cc2fc37d9936a73e3a120125c60b0587d1Marc Blank                    if (selfOrganizer &&
20359e86eb14c6e1f7d7730f8ca6953fdfd95fe2b143RoboErik                            (getInt(entityValues, Events.DIRTY) == 1)) {
2036c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank                        EmailContent.Message msg =
20375c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank                            CalendarUtilities.createMessageForEventId(mContext, eventId,
2038c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank                                    EmailContent.Message.FLAG_OUTGOING_MEETING_INVITE, clientId,
2039c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank                                    mAccount);
2040c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank                        if (msg != null) {
2041125f1c07b605ec798e81c9fb0f153b2b5200fe1fMarc Blank                            userLog("Queueing invitation to ", msg.mTo);
2042125f1c07b605ec798e81c9fb0f153b2b5200fe1fMarc Blank                            mOutgoingMailList.add(msg);
2043c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank                        }
20445a75c42d793d1d4234f0fd5cd00ea34bd7c0625fMarc Blank                        // Make a list out of our tokenized attendees, if we have any
2045332d08cc2fc37d9936a73e3a120125c60b0587d1Marc Blank                        ArrayList<String> originalAttendeeList = new ArrayList<String>();
2046332d08cc2fc37d9936a73e3a120125c60b0587d1Marc Blank                        if (attendeeString != null) {
2047332d08cc2fc37d9936a73e3a120125c60b0587d1Marc Blank                            StringTokenizer st =
2048332d08cc2fc37d9936a73e3a120125c60b0587d1Marc Blank                                new StringTokenizer(attendeeString, ATTENDEE_TOKENIZER_DELIMITER);
2049332d08cc2fc37d9936a73e3a120125c60b0587d1Marc Blank                            while (st.hasMoreTokens()) {
2050332d08cc2fc37d9936a73e3a120125c60b0587d1Marc Blank                                originalAttendeeList.add(st.nextToken());
2051332d08cc2fc37d9936a73e3a120125c60b0587d1Marc Blank                            }
2052332d08cc2fc37d9936a73e3a120125c60b0587d1Marc Blank                        }
2053332d08cc2fc37d9936a73e3a120125c60b0587d1Marc Blank                        StringBuilder newTokenizedAttendees = new StringBuilder();
2054332d08cc2fc37d9936a73e3a120125c60b0587d1Marc Blank                        // See if any attendees have been dropped and while we're at it, build
2055332d08cc2fc37d9936a73e3a120125c60b0587d1Marc Blank                        // an updated String with tokenized attendee addresses
2056332d08cc2fc37d9936a73e3a120125c60b0587d1Marc Blank                        for (NamedContentValues ncv: entity.getSubValues()) {
2057332d08cc2fc37d9936a73e3a120125c60b0587d1Marc Blank                            if (ncv.uri.equals(Attendees.CONTENT_URI)) {
2058332d08cc2fc37d9936a73e3a120125c60b0587d1Marc Blank                                String attendeeEmail =
2059332d08cc2fc37d9936a73e3a120125c60b0587d1Marc Blank                                    ncv.values.getAsString(Attendees.ATTENDEE_EMAIL);
2060332d08cc2fc37d9936a73e3a120125c60b0587d1Marc Blank                                // Remove all found attendees
2061332d08cc2fc37d9936a73e3a120125c60b0587d1Marc Blank                                originalAttendeeList.remove(attendeeEmail);
2062332d08cc2fc37d9936a73e3a120125c60b0587d1Marc Blank                                newTokenizedAttendees.append(attendeeEmail);
2063332d08cc2fc37d9936a73e3a120125c60b0587d1Marc Blank                                newTokenizedAttendees.append(ATTENDEE_TOKENIZER_DELIMITER);
2064332d08cc2fc37d9936a73e3a120125c60b0587d1Marc Blank                            }
2065332d08cc2fc37d9936a73e3a120125c60b0587d1Marc Blank                        }
2066332d08cc2fc37d9936a73e3a120125c60b0587d1Marc Blank                        // Update extended properties with the new attendee list, if we have one
2067332d08cc2fc37d9936a73e3a120125c60b0587d1Marc Blank                        // Otherwise, create one (this would be the case for Events created on
2068332d08cc2fc37d9936a73e3a120125c60b0587d1Marc Blank                        // device or "legacy" events (before this code was added)
2069332d08cc2fc37d9936a73e3a120125c60b0587d1Marc Blank                        ContentValues cv = new ContentValues();
2070332d08cc2fc37d9936a73e3a120125c60b0587d1Marc Blank                        cv.put(ExtendedProperties.VALUE, newTokenizedAttendees.toString());
2071332d08cc2fc37d9936a73e3a120125c60b0587d1Marc Blank                        if (attendeeString != null) {
20725527d276538443c6d735bd401be7241de9c51c72Marc Blank                            cr.update(asSyncAdapter(ContentUris.withAppendedId(
20735527d276538443c6d735bd401be7241de9c51c72Marc Blank                                    ExtendedProperties.CONTENT_URI, attendeeStringId),
20745527d276538443c6d735bd401be7241de9c51c72Marc Blank                                    mEmailAddress, Eas.EXCHANGE_ACCOUNT_MANAGER_TYPE),
20755527d276538443c6d735bd401be7241de9c51c72Marc Blank                                    cv, null, null);
2076332d08cc2fc37d9936a73e3a120125c60b0587d1Marc Blank                        } else {
2077332d08cc2fc37d9936a73e3a120125c60b0587d1Marc Blank                            // If there wasn't an "attendees" property, insert one
2078ac3283a6b46bf27a5b418638c95a13121ea180e1Marc Blank                            cv.put(ExtendedProperties.NAME, EXTENDED_PROPERTY_ATTENDEES);
2079332d08cc2fc37d9936a73e3a120125c60b0587d1Marc Blank                            cv.put(ExtendedProperties.EVENT_ID, eventId);
20805527d276538443c6d735bd401be7241de9c51c72Marc Blank                            cr.insert(asSyncAdapter(ExtendedProperties.CONTENT_URI,
20815527d276538443c6d735bd401be7241de9c51c72Marc Blank                                    mEmailAddress, Eas.EXCHANGE_ACCOUNT_MANAGER_TYPE), cv);
2082332d08cc2fc37d9936a73e3a120125c60b0587d1Marc Blank                        }
2083332d08cc2fc37d9936a73e3a120125c60b0587d1Marc Blank                        // Whoever is left has been removed from the attendee list; send them
2084332d08cc2fc37d9936a73e3a120125c60b0587d1Marc Blank                        // a cancellation
2085332d08cc2fc37d9936a73e3a120125c60b0587d1Marc Blank                        for (String removedAttendee: originalAttendeeList) {
2086332d08cc2fc37d9936a73e3a120125c60b0587d1Marc Blank                            // Send a cancellation message to each of them
2087332d08cc2fc37d9936a73e3a120125c60b0587d1Marc Blank                            msg = CalendarUtilities.createMessageForEventId(mContext, eventId,
2088601273ad3ff202f50c17061bd2a8fe9492850802Marc Blank                                    Message.FLAG_OUTGOING_MEETING_CANCEL, clientId, mAccount,
208924cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank                                    removedAttendee);
2090332d08cc2fc37d9936a73e3a120125c60b0587d1Marc Blank                            if (msg != null) {
2091332d08cc2fc37d9936a73e3a120125c60b0587d1Marc Blank                                // Just send it to the removed attendee
2092125f1c07b605ec798e81c9fb0f153b2b5200fe1fMarc Blank                                userLog("Queueing cancellation to removed attendee " + msg.mTo);
2093125f1c07b605ec798e81c9fb0f153b2b5200fe1fMarc Blank                                mOutgoingMailList.add(msg);
2094332d08cc2fc37d9936a73e3a120125c60b0587d1Marc Blank                            }
2095332d08cc2fc37d9936a73e3a120125c60b0587d1Marc Blank                        }
2096580d8c26fe7e1de93c5d1a123ba261e4b2a9c852Marc Blank                    } else if (!selfOrganizer) {
2097580d8c26fe7e1de93c5d1a123ba261e4b2a9c852Marc Blank                        // If we're not the organizer, see if we've changed our attendee status
20985a75c42d793d1d4234f0fd5cd00ea34bd7c0625fMarc Blank                        // Our last synced attendee status is in ExtendedProperties, and we've
20995a75c42d793d1d4234f0fd5cd00ea34bd7c0625fMarc Blank                        // retrieved it above as userAttendeeStatus
2100580d8c26fe7e1de93c5d1a123ba261e4b2a9c852Marc Blank                        int currentStatus = entityValues.getAsInteger(Events.SELF_ATTENDEE_STATUS);
2101580d8c26fe7e1de93c5d1a123ba261e4b2a9c852Marc Blank                        int syncStatus = Attendees.ATTENDEE_STATUS_NONE;
21025a75c42d793d1d4234f0fd5cd00ea34bd7c0625fMarc Blank                        if (userAttendeeStatus != null) {
21035a75c42d793d1d4234f0fd5cd00ea34bd7c0625fMarc Blank                            try {
21045a75c42d793d1d4234f0fd5cd00ea34bd7c0625fMarc Blank                                syncStatus = Integer.parseInt(userAttendeeStatus);
21055a75c42d793d1d4234f0fd5cd00ea34bd7c0625fMarc Blank                            } catch (NumberFormatException e) {
21065a75c42d793d1d4234f0fd5cd00ea34bd7c0625fMarc Blank                                // Just in case somebody else mucked with this and it's not Integer
21075a75c42d793d1d4234f0fd5cd00ea34bd7c0625fMarc Blank                            }
2108580d8c26fe7e1de93c5d1a123ba261e4b2a9c852Marc Blank                        }
2109580d8c26fe7e1de93c5d1a123ba261e4b2a9c852Marc Blank                        if ((currentStatus != syncStatus) &&
2110580d8c26fe7e1de93c5d1a123ba261e4b2a9c852Marc Blank                                (currentStatus != Attendees.ATTENDEE_STATUS_NONE)) {
2111580d8c26fe7e1de93c5d1a123ba261e4b2a9c852Marc Blank                            // If so, send a meeting reply
2112580d8c26fe7e1de93c5d1a123ba261e4b2a9c852Marc Blank                            int messageFlag = 0;
2113580d8c26fe7e1de93c5d1a123ba261e4b2a9c852Marc Blank                            switch (currentStatus) {
2114580d8c26fe7e1de93c5d1a123ba261e4b2a9c852Marc Blank                                case Attendees.ATTENDEE_STATUS_ACCEPTED:
2115580d8c26fe7e1de93c5d1a123ba261e4b2a9c852Marc Blank                                    messageFlag = Message.FLAG_OUTGOING_MEETING_ACCEPT;
2116580d8c26fe7e1de93c5d1a123ba261e4b2a9c852Marc Blank                                    break;
2117580d8c26fe7e1de93c5d1a123ba261e4b2a9c852Marc Blank                                case Attendees.ATTENDEE_STATUS_DECLINED:
2118580d8c26fe7e1de93c5d1a123ba261e4b2a9c852Marc Blank                                    messageFlag = Message.FLAG_OUTGOING_MEETING_DECLINE;
2119580d8c26fe7e1de93c5d1a123ba261e4b2a9c852Marc Blank                                    break;
2120580d8c26fe7e1de93c5d1a123ba261e4b2a9c852Marc Blank                                case Attendees.ATTENDEE_STATUS_TENTATIVE:
2121580d8c26fe7e1de93c5d1a123ba261e4b2a9c852Marc Blank                                    messageFlag = Message.FLAG_OUTGOING_MEETING_TENTATIVE;
2122580d8c26fe7e1de93c5d1a123ba261e4b2a9c852Marc Blank                                    break;
2123580d8c26fe7e1de93c5d1a123ba261e4b2a9c852Marc Blank                            }
2124580d8c26fe7e1de93c5d1a123ba261e4b2a9c852Marc Blank                            // Make sure we have a valid status (messageFlag should never be zero)
21255a75c42d793d1d4234f0fd5cd00ea34bd7c0625fMarc Blank                            if (messageFlag != 0 && userAttendeeStatusId >= 0) {
2126580d8c26fe7e1de93c5d1a123ba261e4b2a9c852Marc Blank                                // Save away the new status
2127580d8c26fe7e1de93c5d1a123ba261e4b2a9c852Marc Blank                                cidValues.clear();
21285a75c42d793d1d4234f0fd5cd00ea34bd7c0625fMarc Blank                                cidValues.put(ExtendedProperties.VALUE,
2129580d8c26fe7e1de93c5d1a123ba261e4b2a9c852Marc Blank                                        Integer.toString(currentStatus));
21308b646b9a24e0262110be6da07ade9511730e2008Marc Blank                                cr.update(asSyncAdapter(ContentUris.withAppendedId(
21318b646b9a24e0262110be6da07ade9511730e2008Marc Blank                                        ExtendedProperties.CONTENT_URI, userAttendeeStatusId),
21328b646b9a24e0262110be6da07ade9511730e2008Marc Blank                                        mEmailAddress, Eas.EXCHANGE_ACCOUNT_MANAGER_TYPE),
21338b646b9a24e0262110be6da07ade9511730e2008Marc Blank                                        cidValues, null, null);
2134580d8c26fe7e1de93c5d1a123ba261e4b2a9c852Marc Blank                                // Send mail to the organizer advising of the new status
2135580d8c26fe7e1de93c5d1a123ba261e4b2a9c852Marc Blank                                EmailContent.Message msg =
2136580d8c26fe7e1de93c5d1a123ba261e4b2a9c852Marc Blank                                    CalendarUtilities.createMessageForEventId(mContext, eventId,
2137580d8c26fe7e1de93c5d1a123ba261e4b2a9c852Marc Blank                                            messageFlag, clientId, mAccount);
2138580d8c26fe7e1de93c5d1a123ba261e4b2a9c852Marc Blank                                if (msg != null) {
2139125f1c07b605ec798e81c9fb0f153b2b5200fe1fMarc Blank                                    userLog("Queueing invitation reply to " + msg.mTo);
2140125f1c07b605ec798e81c9fb0f153b2b5200fe1fMarc Blank                                    mOutgoingMailList.add(msg);
2141580d8c26fe7e1de93c5d1a123ba261e4b2a9c852Marc Blank                                }
2142580d8c26fe7e1de93c5d1a123ba261e4b2a9c852Marc Blank                            }
2143580d8c26fe7e1de93c5d1a123ba261e4b2a9c852Marc Blank                        }
2144c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank                    }
21455862a85e17e81866ca82a9905577931947fbd44eMarc Blank                }
21465862a85e17e81866ca82a9905577931947fbd44eMarc Blank                if (!first) {
21475862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    s.end(); // Commands
21485862a85e17e81866ca82a9905577931947fbd44eMarc Blank                }
21495862a85e17e81866ca82a9905577931947fbd44eMarc Blank            } finally {
21509a6cf3719e2e887605f9ebef37fb86b834ee734bMarc Blank                eventIterator.close();
21515862a85e17e81866ca82a9905577931947fbd44eMarc Blank            }
21525862a85e17e81866ca82a9905577931947fbd44eMarc Blank        } catch (RemoteException e) {
21535862a85e17e81866ca82a9905577931947fbd44eMarc Blank            Log.e(TAG, "Could not read dirty events.");
21545862a85e17e81866ca82a9905577931947fbd44eMarc Blank        }
21555862a85e17e81866ca82a9905577931947fbd44eMarc Blank
21568047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank        return false;
21578047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank    }
2158ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank}
2159