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