173714169d34a55fc01e14b30897a1ebbacf9934eErik/* 273714169d34a55fc01e14b30897a1ebbacf9934eErik * Copyright (C) 2010 The Android Open Source Project 373714169d34a55fc01e14b30897a1ebbacf9934eErik * 473714169d34a55fc01e14b30897a1ebbacf9934eErik * Licensed under the Apache License, Version 2.0 (the "License"); 573714169d34a55fc01e14b30897a1ebbacf9934eErik * you may not use this file except in compliance with the License. 673714169d34a55fc01e14b30897a1ebbacf9934eErik * You may obtain a copy of the License at 773714169d34a55fc01e14b30897a1ebbacf9934eErik * 873714169d34a55fc01e14b30897a1ebbacf9934eErik * http://www.apache.org/licenses/LICENSE-2.0 973714169d34a55fc01e14b30897a1ebbacf9934eErik * 1073714169d34a55fc01e14b30897a1ebbacf9934eErik * Unless required by applicable law or agreed to in writing, software 1173714169d34a55fc01e14b30897a1ebbacf9934eErik * distributed under the License is distributed on an "AS IS" BASIS, 1273714169d34a55fc01e14b30897a1ebbacf9934eErik * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1373714169d34a55fc01e14b30897a1ebbacf9934eErik * See the License for the specific language governing permissions and 1473714169d34a55fc01e14b30897a1ebbacf9934eErik * limitations under the License. 1573714169d34a55fc01e14b30897a1ebbacf9934eErik */ 1673714169d34a55fc01e14b30897a1ebbacf9934eErik 17fbce65e53c7a111955f638db5bf8bee35381e5b7Erikpackage com.android.calendar.event; 1873714169d34a55fc01e14b30897a1ebbacf9934eErik 19eca82e9468c002590045338f98588ad3ce1a0bb3Erikimport android.content.ContentProviderOperation; 20eca82e9468c002590045338f98588ad3ce1a0bb3Erikimport android.content.ContentUris; 21eca82e9468c002590045338f98588ad3ce1a0bb3Erikimport android.content.ContentValues; 22dd95df57c8c5a58a85c4c0effad5652dec14f621Erikimport android.content.Context; 2373714169d34a55fc01e14b30897a1ebbacf9934eErikimport android.database.Cursor; 24d845fbe558d5229102b58cce70a1b29fe6cb6967Erikimport android.graphics.drawable.Drawable; 2573714169d34a55fc01e14b30897a1ebbacf9934eErikimport android.net.Uri; 26a7c0390d9c5dd4ff730de505682687fae5f5ced0RoboErikimport android.provider.CalendarContract.Attendees; 27a7c0390d9c5dd4ff730de505682687fae5f5ced0RoboErikimport android.provider.CalendarContract.Calendars; 284afba187f8990ae2b3afaf8fcdb6039f231f4914James Kungimport android.provider.CalendarContract.Colors; 29a7c0390d9c5dd4ff730de505682687fae5f5ced0RoboErikimport android.provider.CalendarContract.Events; 30a7c0390d9c5dd4ff730de505682687fae5f5ced0RoboErikimport android.provider.CalendarContract.Reminders; 3173714169d34a55fc01e14b30897a1ebbacf9934eErikimport android.text.TextUtils; 3273714169d34a55fc01e14b30897a1ebbacf9934eErikimport android.text.format.DateUtils; 3373714169d34a55fc01e14b30897a1ebbacf9934eErikimport android.text.format.Time; 3473714169d34a55fc01e14b30897a1ebbacf9934eErikimport android.text.util.Rfc822Token; 3573714169d34a55fc01e14b30897a1ebbacf9934eErikimport android.text.util.Rfc822Tokenizer; 3673714169d34a55fc01e14b30897a1ebbacf9934eErikimport android.util.Log; 37352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chanimport android.view.View; 389e196e46b238e6bca1ef86c7d673807b78ab7016Michael Chan 395cde0374a28f91dd02e515d2956c6028144d0a9cMichael Chanimport com.android.calendar.AbstractCalendarActivity; 409e196e46b238e6bca1ef86c7d673807b78ab7016Michael Chanimport com.android.calendar.AsyncQueryService; 419e196e46b238e6bca1ef86c7d673807b78ab7016Michael Chanimport com.android.calendar.CalendarEventModel; 429e196e46b238e6bca1ef86c7d673807b78ab7016Michael Chanimport com.android.calendar.CalendarEventModel.Attendee; 439e196e46b238e6bca1ef86c7d673807b78ab7016Michael Chanimport com.android.calendar.CalendarEventModel.ReminderEntry; 449e196e46b238e6bca1ef86c7d673807b78ab7016Michael Chanimport com.android.calendar.Utils; 458a528adbba5d46fe0d566f474bc3be190511198eMichael Chanimport com.android.calendarcommon2.DateException; 468a528adbba5d46fe0d566f474bc3be190511198eMichael Chanimport com.android.calendarcommon2.EventRecurrence; 478a528adbba5d46fe0d566f474bc3be190511198eMichael Chanimport com.android.calendarcommon2.RecurrenceProcessor; 488a528adbba5d46fe0d566f474bc3be190511198eMichael Chanimport com.android.calendarcommon2.RecurrenceSet; 499e196e46b238e6bca1ef86c7d673807b78ab7016Michael Chanimport com.android.common.Rfc822Validator; 5073714169d34a55fc01e14b30897a1ebbacf9934eErik 51eca82e9468c002590045338f98588ad3ce1a0bb3Erikimport java.util.ArrayList; 52d845fbe558d5229102b58cce70a1b29fe6cb6967Erikimport java.util.HashMap; 53eca82e9468c002590045338f98588ad3ce1a0bb3Erikimport java.util.Iterator; 54eca82e9468c002590045338f98588ad3ce1a0bb3Erikimport java.util.LinkedHashSet; 55d845fbe558d5229102b58cce70a1b29fe6cb6967Erikimport java.util.LinkedList; 56eca82e9468c002590045338f98588ad3ce1a0bb3Erikimport java.util.TimeZone; 5773714169d34a55fc01e14b30897a1ebbacf9934eErik 5873714169d34a55fc01e14b30897a1ebbacf9934eErikpublic class EditEventHelper { 5973714169d34a55fc01e14b30897a1ebbacf9934eErik private static final String TAG = "EditEventHelper"; 60eca82e9468c002590045338f98588ad3ce1a0bb3Erik 6173714169d34a55fc01e14b30897a1ebbacf9934eErik private static final boolean DEBUG = false; 6273714169d34a55fc01e14b30897a1ebbacf9934eErik 636f3073ce88b9d120c86e9cd4d8be306add4363c9Sam Blitzstein // Used for parsing rrules for special cases. 646f3073ce88b9d120c86e9cd4d8be306add4363c9Sam Blitzstein private EventRecurrence mEventRecurrence = new EventRecurrence(); 656f3073ce88b9d120c86e9cd4d8be306add4363c9Sam Blitzstein 664afba187f8990ae2b3afaf8fcdb6039f231f4914James Kung private static final String NO_EVENT_COLOR = ""; 674afba187f8990ae2b3afaf8fcdb6039f231f4914James Kung 68fbce65e53c7a111955f638db5bf8bee35381e5b7Erik public static final String[] EVENT_PROJECTION = new String[] { 6973714169d34a55fc01e14b30897a1ebbacf9934eErik Events._ID, // 0 7073714169d34a55fc01e14b30897a1ebbacf9934eErik Events.TITLE, // 1 7173714169d34a55fc01e14b30897a1ebbacf9934eErik Events.DESCRIPTION, // 2 7273714169d34a55fc01e14b30897a1ebbacf9934eErik Events.EVENT_LOCATION, // 3 7373714169d34a55fc01e14b30897a1ebbacf9934eErik Events.ALL_DAY, // 4 7473714169d34a55fc01e14b30897a1ebbacf9934eErik Events.HAS_ALARM, // 5 7573714169d34a55fc01e14b30897a1ebbacf9934eErik Events.CALENDAR_ID, // 6 7673714169d34a55fc01e14b30897a1ebbacf9934eErik Events.DTSTART, // 7 7773714169d34a55fc01e14b30897a1ebbacf9934eErik Events.DTEND, // 8 7873714169d34a55fc01e14b30897a1ebbacf9934eErik Events.DURATION, // 9 7973714169d34a55fc01e14b30897a1ebbacf9934eErik Events.EVENT_TIMEZONE, // 10 8073714169d34a55fc01e14b30897a1ebbacf9934eErik Events.RRULE, // 11 8173714169d34a55fc01e14b30897a1ebbacf9934eErik Events._SYNC_ID, // 12 820e1e62408b96e1532eb6f6a609ae4c817751aaf3RoboErik Events.AVAILABILITY, // 13 830e1e62408b96e1532eb6f6a609ae4c817751aaf3RoboErik Events.ACCESS_LEVEL, // 14 8473714169d34a55fc01e14b30897a1ebbacf9934eErik Events.OWNER_ACCOUNT, // 15 8573714169d34a55fc01e14b30897a1ebbacf9934eErik Events.HAS_ATTENDEE_DATA, // 16 860e1e62408b96e1532eb6f6a609ae4c817751aaf3RoboErik Events.ORIGINAL_SYNC_ID, // 17 87352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan Events.ORGANIZER, // 18 88352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan Events.GUESTS_CAN_MODIFY, // 19 8928dab653f55caccbed32f700274f5274abaee089RoboErik Events.ORIGINAL_ID, // 20 90e3f85db16cfc50b25b97c64d8ab99820d579e5deMichael Chan Events.STATUS, // 21 914afba187f8990ae2b3afaf8fcdb6039f231f4914James Kung Events.CALENDAR_COLOR, // 22 924afba187f8990ae2b3afaf8fcdb6039f231f4914James Kung Events.EVENT_COLOR, // 23 934afba187f8990ae2b3afaf8fcdb6039f231f4914James Kung Events.EVENT_COLOR_KEY // 24 9473714169d34a55fc01e14b30897a1ebbacf9934eErik }; 9573714169d34a55fc01e14b30897a1ebbacf9934eErik protected static final int EVENT_INDEX_ID = 0; 9673714169d34a55fc01e14b30897a1ebbacf9934eErik protected static final int EVENT_INDEX_TITLE = 1; 9773714169d34a55fc01e14b30897a1ebbacf9934eErik protected static final int EVENT_INDEX_DESCRIPTION = 2; 9873714169d34a55fc01e14b30897a1ebbacf9934eErik protected static final int EVENT_INDEX_EVENT_LOCATION = 3; 9973714169d34a55fc01e14b30897a1ebbacf9934eErik protected static final int EVENT_INDEX_ALL_DAY = 4; 10073714169d34a55fc01e14b30897a1ebbacf9934eErik protected static final int EVENT_INDEX_HAS_ALARM = 5; 10173714169d34a55fc01e14b30897a1ebbacf9934eErik protected static final int EVENT_INDEX_CALENDAR_ID = 6; 10273714169d34a55fc01e14b30897a1ebbacf9934eErik protected static final int EVENT_INDEX_DTSTART = 7; 10373714169d34a55fc01e14b30897a1ebbacf9934eErik protected static final int EVENT_INDEX_DTEND = 8; 10473714169d34a55fc01e14b30897a1ebbacf9934eErik protected static final int EVENT_INDEX_DURATION = 9; 10573714169d34a55fc01e14b30897a1ebbacf9934eErik protected static final int EVENT_INDEX_TIMEZONE = 10; 10673714169d34a55fc01e14b30897a1ebbacf9934eErik protected static final int EVENT_INDEX_RRULE = 11; 10773714169d34a55fc01e14b30897a1ebbacf9934eErik protected static final int EVENT_INDEX_SYNC_ID = 12; 108ced0eb67ef49c3203f72520c296e73d9cb4f654bMichael Chan protected static final int EVENT_INDEX_AVAILABILITY = 13; 109ced0eb67ef49c3203f72520c296e73d9cb4f654bMichael Chan protected static final int EVENT_INDEX_ACCESS_LEVEL = 14; 11073714169d34a55fc01e14b30897a1ebbacf9934eErik protected static final int EVENT_INDEX_OWNER_ACCOUNT = 15; 11173714169d34a55fc01e14b30897a1ebbacf9934eErik protected static final int EVENT_INDEX_HAS_ATTENDEE_DATA = 16; 11228dab653f55caccbed32f700274f5274abaee089RoboErik protected static final int EVENT_INDEX_ORIGINAL_SYNC_ID = 17; 113352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan protected static final int EVENT_INDEX_ORGANIZER = 18; 114352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan protected static final int EVENT_INDEX_GUESTS_CAN_MODIFY = 19; 11528dab653f55caccbed32f700274f5274abaee089RoboErik protected static final int EVENT_INDEX_ORIGINAL_ID = 20; 116e3f85db16cfc50b25b97c64d8ab99820d579e5deMichael Chan protected static final int EVENT_INDEX_EVENT_STATUS = 21; 1174afba187f8990ae2b3afaf8fcdb6039f231f4914James Kung protected static final int EVENT_INDEX_CALENDAR_COLOR = 22; 1184afba187f8990ae2b3afaf8fcdb6039f231f4914James Kung protected static final int EVENT_INDEX_EVENT_COLOR = 23; 1194afba187f8990ae2b3afaf8fcdb6039f231f4914James Kung protected static final int EVENT_INDEX_EVENT_COLOR_KEY = 24; 12073714169d34a55fc01e14b30897a1ebbacf9934eErik 121eca82e9468c002590045338f98588ad3ce1a0bb3Erik public static final String[] REMINDERS_PROJECTION = new String[] { 12273714169d34a55fc01e14b30897a1ebbacf9934eErik Reminders._ID, // 0 12373714169d34a55fc01e14b30897a1ebbacf9934eErik Reminders.MINUTES, // 1 124eca82e9468c002590045338f98588ad3ce1a0bb3Erik Reminders.METHOD, // 2 12573714169d34a55fc01e14b30897a1ebbacf9934eErik }; 126eca82e9468c002590045338f98588ad3ce1a0bb3Erik public static final int REMINDERS_INDEX_MINUTES = 1; 127eca82e9468c002590045338f98588ad3ce1a0bb3Erik public static final int REMINDERS_INDEX_METHOD = 2; 1289645d9c3e9b2cfd35c5fc89f41caf6e99153ba10Andy McFadden public static final String REMINDERS_WHERE = Reminders.EVENT_ID + "=?"; 129eca82e9468c002590045338f98588ad3ce1a0bb3Erik 13073714169d34a55fc01e14b30897a1ebbacf9934eErik // Visible for testing 131eca82e9468c002590045338f98588ad3ce1a0bb3Erik static final String ATTENDEES_DELETE_PREFIX = Attendees.EVENT_ID + "=? AND " 132eca82e9468c002590045338f98588ad3ce1a0bb3Erik + Attendees.ATTENDEE_EMAIL + " IN ("; 13373714169d34a55fc01e14b30897a1ebbacf9934eErik 13473714169d34a55fc01e14b30897a1ebbacf9934eErik public static final int DOES_NOT_REPEAT = 0; 13573714169d34a55fc01e14b30897a1ebbacf9934eErik public static final int REPEATS_DAILY = 1; 13673714169d34a55fc01e14b30897a1ebbacf9934eErik public static final int REPEATS_EVERY_WEEKDAY = 2; 13773714169d34a55fc01e14b30897a1ebbacf9934eErik public static final int REPEATS_WEEKLY_ON_DAY = 3; 13873714169d34a55fc01e14b30897a1ebbacf9934eErik public static final int REPEATS_MONTHLY_ON_DAY_COUNT = 4; 13973714169d34a55fc01e14b30897a1ebbacf9934eErik public static final int REPEATS_MONTHLY_ON_DAY = 5; 14073714169d34a55fc01e14b30897a1ebbacf9934eErik public static final int REPEATS_YEARLY = 6; 14173714169d34a55fc01e14b30897a1ebbacf9934eErik public static final int REPEATS_CUSTOM = 7; 14273714169d34a55fc01e14b30897a1ebbacf9934eErik 14373714169d34a55fc01e14b30897a1ebbacf9934eErik protected static final int MODIFY_UNINITIALIZED = 0; 14473714169d34a55fc01e14b30897a1ebbacf9934eErik protected static final int MODIFY_SELECTED = 1; 1458d69cd014d912e38d2ab37764aaf4aa7700c078bErik protected static final int MODIFY_ALL_FOLLOWING = 2; 1468d69cd014d912e38d2ab37764aaf4aa7700c078bErik protected static final int MODIFY_ALL = 3; 14773714169d34a55fc01e14b30897a1ebbacf9934eErik 14873714169d34a55fc01e14b30897a1ebbacf9934eErik protected static final int DAY_IN_SECONDS = 24 * 60 * 60; 14973714169d34a55fc01e14b30897a1ebbacf9934eErik 1506bcafcf4f28c2d1053547694bd60b3dd9c7fbaa1Isaac Katzenelson private final AsyncQueryService mService; 151eca82e9468c002590045338f98588ad3ce1a0bb3Erik 152eca82e9468c002590045338f98588ad3ce1a0bb3Erik // This allows us to flag the event if something is wrong with it, right now 153eca82e9468c002590045338f98588ad3ce1a0bb3Erik // if an uri is provided for an event that doesn't exist in the db. 15473714169d34a55fc01e14b30897a1ebbacf9934eErik protected boolean mEventOk = true; 15573714169d34a55fc01e14b30897a1ebbacf9934eErik 156d845fbe558d5229102b58cce70a1b29fe6cb6967Erik public static final int ATTENDEE_ID_NONE = -1; 157d845fbe558d5229102b58cce70a1b29fe6cb6967Erik public static final int[] ATTENDEE_VALUES = { 1586bcafcf4f28c2d1053547694bd60b3dd9c7fbaa1Isaac Katzenelson Attendees.ATTENDEE_STATUS_NONE, 1596bcafcf4f28c2d1053547694bd60b3dd9c7fbaa1Isaac Katzenelson Attendees.ATTENDEE_STATUS_ACCEPTED, 1606bcafcf4f28c2d1053547694bd60b3dd9c7fbaa1Isaac Katzenelson Attendees.ATTENDEE_STATUS_TENTATIVE, 1616bcafcf4f28c2d1053547694bd60b3dd9c7fbaa1Isaac Katzenelson Attendees.ATTENDEE_STATUS_DECLINED, 162d845fbe558d5229102b58cce70a1b29fe6cb6967Erik }; 163d845fbe558d5229102b58cce70a1b29fe6cb6967Erik 164eca82e9468c002590045338f98588ad3ce1a0bb3Erik /** 165eca82e9468c002590045338f98588ad3ce1a0bb3Erik * This is the symbolic name for the key used to pass in the boolean for 166eca82e9468c002590045338f98588ad3ce1a0bb3Erik * creating all-day events that is part of the extra data of the intent. 167eca82e9468c002590045338f98588ad3ce1a0bb3Erik * This is used only for creating new events and is set to true if the 168eca82e9468c002590045338f98588ad3ce1a0bb3Erik * default for the new event should be an all-day event. 169eca82e9468c002590045338f98588ad3ce1a0bb3Erik */ 170eca82e9468c002590045338f98588ad3ce1a0bb3Erik public static final String EVENT_ALL_DAY = "allDay"; 171eca82e9468c002590045338f98588ad3ce1a0bb3Erik 172eca82e9468c002590045338f98588ad3ce1a0bb3Erik static final String[] CALENDARS_PROJECTION = new String[] { 173eca82e9468c002590045338f98588ad3ce1a0bb3Erik Calendars._ID, // 0 174ef2add9145f058ea103ade2870a8ba899b9ed1c9RoboErik Calendars.CALENDAR_DISPLAY_NAME, // 1 175eca82e9468c002590045338f98588ad3ce1a0bb3Erik Calendars.OWNER_ACCOUNT, // 2 1760e1e62408b96e1532eb6f6a609ae4c817751aaf3RoboErik Calendars.CALENDAR_COLOR, // 3 177950e8a615f3dcca0b61b2eb8f8de72ecf6825d4bAndy McFadden Calendars.CAN_ORGANIZER_RESPOND, // 4 178ef2add9145f058ea103ade2870a8ba899b9ed1c9RoboErik Calendars.CALENDAR_ACCESS_LEVEL, // 5 179950e8a615f3dcca0b61b2eb8f8de72ecf6825d4bAndy McFadden Calendars.VISIBLE, // 6 1809645d9c3e9b2cfd35c5fc89f41caf6e99153ba10Andy McFadden Calendars.MAX_REMINDERS, // 7 1819645d9c3e9b2cfd35c5fc89f41caf6e99153ba10Andy McFadden Calendars.ALLOWED_REMINDERS, // 8 182c6680c5819b6ad0d1914420902746fe5f86ba403RoboErik Calendars.ALLOWED_ATTENDEE_TYPES, // 9 183c6680c5819b6ad0d1914420902746fe5f86ba403RoboErik Calendars.ALLOWED_AVAILABILITY, // 10 1848286d2f75e9c808f022c8c6b7431f50e7f60372fSara Ting Calendars.ACCOUNT_NAME, // 11 185844cac068835d7d8a80ec118da8019535c0d7450Sara Ting Calendars.ACCOUNT_TYPE, //12 186eca82e9468c002590045338f98588ad3ce1a0bb3Erik }; 187eca82e9468c002590045338f98588ad3ce1a0bb3Erik static final int CALENDARS_INDEX_ID = 0; 188eca82e9468c002590045338f98588ad3ce1a0bb3Erik static final int CALENDARS_INDEX_DISPLAY_NAME = 1; 189eca82e9468c002590045338f98588ad3ce1a0bb3Erik static final int CALENDARS_INDEX_OWNER_ACCOUNT = 2; 190eca82e9468c002590045338f98588ad3ce1a0bb3Erik static final int CALENDARS_INDEX_COLOR = 3; 191950e8a615f3dcca0b61b2eb8f8de72ecf6825d4bAndy McFadden static final int CALENDARS_INDEX_CAN_ORGANIZER_RESPOND = 4; 192352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan static final int CALENDARS_INDEX_ACCESS_LEVEL = 5; 193950e8a615f3dcca0b61b2eb8f8de72ecf6825d4bAndy McFadden static final int CALENDARS_INDEX_VISIBLE = 6; 1949645d9c3e9b2cfd35c5fc89f41caf6e99153ba10Andy McFadden static final int CALENDARS_INDEX_MAX_REMINDERS = 7; 1959645d9c3e9b2cfd35c5fc89f41caf6e99153ba10Andy McFadden static final int CALENDARS_INDEX_ALLOWED_REMINDERS = 8; 196c6680c5819b6ad0d1914420902746fe5f86ba403RoboErik static final int CALENDARS_INDEX_ALLOWED_ATTENDEE_TYPES = 9; 197c6680c5819b6ad0d1914420902746fe5f86ba403RoboErik static final int CALENDARS_INDEX_ALLOWED_AVAILABILITY = 10; 1988286d2f75e9c808f022c8c6b7431f50e7f60372fSara Ting static final int CALENDARS_INDEX_ACCOUNT_NAME = 11; 199844cac068835d7d8a80ec118da8019535c0d7450Sara Ting static final int CALENDARS_INDEX_ACCOUNT_TYPE = 12; 200eca82e9468c002590045338f98588ad3ce1a0bb3Erik 201ef2add9145f058ea103ade2870a8ba899b9ed1c9RoboErik static final String CALENDARS_WHERE_WRITEABLE_VISIBLE = Calendars.CALENDAR_ACCESS_LEVEL + ">=" 202ef2add9145f058ea103ade2870a8ba899b9ed1c9RoboErik + Calendars.CAL_ACCESS_CONTRIBUTOR + " AND " + Calendars.VISIBLE + "=1"; 203eca82e9468c002590045338f98588ad3ce1a0bb3Erik 204352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan static final String CALENDARS_WHERE = Calendars._ID + "=?"; 205352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan 2064afba187f8990ae2b3afaf8fcdb6039f231f4914James Kung static final String[] COLORS_PROJECTION = new String[] { 2074afba187f8990ae2b3afaf8fcdb6039f231f4914James Kung Colors._ID, // 0 2084afba187f8990ae2b3afaf8fcdb6039f231f4914James Kung Colors.ACCOUNT_NAME, 2094afba187f8990ae2b3afaf8fcdb6039f231f4914James Kung Colors.ACCOUNT_TYPE, 2104afba187f8990ae2b3afaf8fcdb6039f231f4914James Kung Colors.COLOR, // 1 2114afba187f8990ae2b3afaf8fcdb6039f231f4914James Kung Colors.COLOR_KEY // 2 2124afba187f8990ae2b3afaf8fcdb6039f231f4914James Kung }; 2134afba187f8990ae2b3afaf8fcdb6039f231f4914James Kung 2144afba187f8990ae2b3afaf8fcdb6039f231f4914James Kung static final String COLORS_WHERE = Colors.ACCOUNT_NAME + "=? AND " + Colors.ACCOUNT_TYPE + 2154afba187f8990ae2b3afaf8fcdb6039f231f4914James Kung "=? AND " + Colors.COLOR_TYPE + "=" + Colors.TYPE_EVENT; 2164afba187f8990ae2b3afaf8fcdb6039f231f4914James Kung 2174afba187f8990ae2b3afaf8fcdb6039f231f4914James Kung static final int COLORS_INDEX_ACCOUNT_NAME = 1; 2184afba187f8990ae2b3afaf8fcdb6039f231f4914James Kung static final int COLORS_INDEX_ACCOUNT_TYPE = 2; 2194afba187f8990ae2b3afaf8fcdb6039f231f4914James Kung static final int COLORS_INDEX_COLOR = 3; 2204afba187f8990ae2b3afaf8fcdb6039f231f4914James Kung static final int COLORS_INDEX_COLOR_KEY = 4; 2214afba187f8990ae2b3afaf8fcdb6039f231f4914James Kung 222eca82e9468c002590045338f98588ad3ce1a0bb3Erik static final String[] ATTENDEES_PROJECTION = new String[] { 223d845fbe558d5229102b58cce70a1b29fe6cb6967Erik Attendees._ID, // 0 224d845fbe558d5229102b58cce70a1b29fe6cb6967Erik Attendees.ATTENDEE_NAME, // 1 225d845fbe558d5229102b58cce70a1b29fe6cb6967Erik Attendees.ATTENDEE_EMAIL, // 2 226d845fbe558d5229102b58cce70a1b29fe6cb6967Erik Attendees.ATTENDEE_RELATIONSHIP, // 3 227d845fbe558d5229102b58cce70a1b29fe6cb6967Erik Attendees.ATTENDEE_STATUS, // 4 228eca82e9468c002590045338f98588ad3ce1a0bb3Erik }; 229d845fbe558d5229102b58cce70a1b29fe6cb6967Erik static final int ATTENDEES_INDEX_ID = 0; 230d845fbe558d5229102b58cce70a1b29fe6cb6967Erik static final int ATTENDEES_INDEX_NAME = 1; 231d845fbe558d5229102b58cce70a1b29fe6cb6967Erik static final int ATTENDEES_INDEX_EMAIL = 2; 232d845fbe558d5229102b58cce70a1b29fe6cb6967Erik static final int ATTENDEES_INDEX_RELATIONSHIP = 3; 233d845fbe558d5229102b58cce70a1b29fe6cb6967Erik static final int ATTENDEES_INDEX_STATUS = 4; 234c077ec06a91a2e5d82557de3c6edd83166ca9174Isaac Katzenelson static final String ATTENDEES_WHERE = Attendees.EVENT_ID + "=? AND attendeeEmail IS NOT NULL"; 235d845fbe558d5229102b58cce70a1b29fe6cb6967Erik 236d845fbe558d5229102b58cce70a1b29fe6cb6967Erik public static class AttendeeItem { 237d845fbe558d5229102b58cce70a1b29fe6cb6967Erik public boolean mRemoved; 238d845fbe558d5229102b58cce70a1b29fe6cb6967Erik public Attendee mAttendee; 239d845fbe558d5229102b58cce70a1b29fe6cb6967Erik public Drawable mBadge; 240d845fbe558d5229102b58cce70a1b29fe6cb6967Erik public int mUpdateCounts; 241bdcb9fcc732d52708fafb068f535a2c93ff2356fDaisuke Miyakawa public View mView; 2428fa3111ceaab2b26a322caa873f352bb4eb3e783Daniel Lehmann public Uri mContactLookupUri; 243bdcb9fcc732d52708fafb068f535a2c93ff2356fDaisuke Miyakawa 2441784d2de8ee81c6759bef7199a902e13de3b300fRoboErik public AttendeeItem(Attendee attendee, Drawable badge) { 245bdcb9fcc732d52708fafb068f535a2c93ff2356fDaisuke Miyakawa mAttendee = attendee; 246bdcb9fcc732d52708fafb068f535a2c93ff2356fDaisuke Miyakawa mBadge = badge; 247bdcb9fcc732d52708fafb068f535a2c93ff2356fDaisuke Miyakawa } 248d845fbe558d5229102b58cce70a1b29fe6cb6967Erik } 249eca82e9468c002590045338f98588ad3ce1a0bb3Erik 25009fbd8e9ef61f667c0f20d36fbf40e5a4479c8d9James Kung public EditEventHelper(Context context) { 2515cde0374a28f91dd02e515d2956c6028144d0a9cMichael Chan mService = ((AbstractCalendarActivity)context).getAsyncQueryService(); 25273714169d34a55fc01e14b30897a1ebbacf9934eErik } 25373714169d34a55fc01e14b30897a1ebbacf9934eErik 25409fbd8e9ef61f667c0f20d36fbf40e5a4479c8d9James Kung public EditEventHelper(Context context, CalendarEventModel model) { 25509fbd8e9ef61f667c0f20d36fbf40e5a4479c8d9James Kung this(context); 25609fbd8e9ef61f667c0f20d36fbf40e5a4479c8d9James Kung // TODO: Remove unnecessary constructor. 25709fbd8e9ef61f667c0f20d36fbf40e5a4479c8d9James Kung } 25809fbd8e9ef61f667c0f20d36fbf40e5a4479c8d9James Kung 25973714169d34a55fc01e14b30897a1ebbacf9934eErik /** 260eca82e9468c002590045338f98588ad3ce1a0bb3Erik * Saves the event. Returns true if the event was successfully saved, false 261eca82e9468c002590045338f98588ad3ce1a0bb3Erik * otherwise. 26273714169d34a55fc01e14b30897a1ebbacf9934eErik * 26373714169d34a55fc01e14b30897a1ebbacf9934eErik * @param model The event model to save 26473714169d34a55fc01e14b30897a1ebbacf9934eErik * @param originalModel A model of the original event if it exists 26573714169d34a55fc01e14b30897a1ebbacf9934eErik * @param modifyWhich For recurring events which type of series modification to use 26673714169d34a55fc01e14b30897a1ebbacf9934eErik * @return true if the event was successfully queued for saving 26773714169d34a55fc01e14b30897a1ebbacf9934eErik */ 26873714169d34a55fc01e14b30897a1ebbacf9934eErik public boolean saveEvent(CalendarEventModel model, CalendarEventModel originalModel, 26973714169d34a55fc01e14b30897a1ebbacf9934eErik int modifyWhich) { 27073714169d34a55fc01e14b30897a1ebbacf9934eErik boolean forceSaveReminders = false; 27173714169d34a55fc01e14b30897a1ebbacf9934eErik 27273714169d34a55fc01e14b30897a1ebbacf9934eErik if (DEBUG) { 27373714169d34a55fc01e14b30897a1ebbacf9934eErik Log.d(TAG, "Saving event model: " + model); 27473714169d34a55fc01e14b30897a1ebbacf9934eErik } 27573714169d34a55fc01e14b30897a1ebbacf9934eErik 27673714169d34a55fc01e14b30897a1ebbacf9934eErik if (!mEventOk) { 27773714169d34a55fc01e14b30897a1ebbacf9934eErik if (DEBUG) { 27873714169d34a55fc01e14b30897a1ebbacf9934eErik Log.w(TAG, "Event no longer exists. Event was not saved."); 27973714169d34a55fc01e14b30897a1ebbacf9934eErik } 28073714169d34a55fc01e14b30897a1ebbacf9934eErik return false; 28173714169d34a55fc01e14b30897a1ebbacf9934eErik } 28273714169d34a55fc01e14b30897a1ebbacf9934eErik 28373714169d34a55fc01e14b30897a1ebbacf9934eErik // It's a problem if we try to save a non-existent or invalid model or if we're 28473714169d34a55fc01e14b30897a1ebbacf9934eErik // modifying an existing event and we have the wrong original model 28573714169d34a55fc01e14b30897a1ebbacf9934eErik if (model == null) { 28673714169d34a55fc01e14b30897a1ebbacf9934eErik Log.e(TAG, "Attempted to save null model."); 28773714169d34a55fc01e14b30897a1ebbacf9934eErik return false; 28873714169d34a55fc01e14b30897a1ebbacf9934eErik } 28973714169d34a55fc01e14b30897a1ebbacf9934eErik if (!model.isValid()) { 29073714169d34a55fc01e14b30897a1ebbacf9934eErik Log.e(TAG, "Attempted to save invalid model."); 29173714169d34a55fc01e14b30897a1ebbacf9934eErik return false; 29273714169d34a55fc01e14b30897a1ebbacf9934eErik } 29373714169d34a55fc01e14b30897a1ebbacf9934eErik if (originalModel != null && !isSameEvent(model, originalModel)) { 294eca82e9468c002590045338f98588ad3ce1a0bb3Erik Log.e(TAG, "Attempted to update existing event but models didn't refer to the same " 295eca82e9468c002590045338f98588ad3ce1a0bb3Erik + "event."); 29673714169d34a55fc01e14b30897a1ebbacf9934eErik return false; 29773714169d34a55fc01e14b30897a1ebbacf9934eErik } 29828dab653f55caccbed32f700274f5274abaee089RoboErik if (originalModel != null && model.isUnchanged(originalModel)) { 29928dab653f55caccbed32f700274f5274abaee089RoboErik return false; 30028dab653f55caccbed32f700274f5274abaee089RoboErik } 30173714169d34a55fc01e14b30897a1ebbacf9934eErik 30273714169d34a55fc01e14b30897a1ebbacf9934eErik ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(); 30373714169d34a55fc01e14b30897a1ebbacf9934eErik int eventIdIndex = -1; 30473714169d34a55fc01e14b30897a1ebbacf9934eErik 30573714169d34a55fc01e14b30897a1ebbacf9934eErik ContentValues values = getContentValuesFromModel(model); 30673714169d34a55fc01e14b30897a1ebbacf9934eErik 3074ebfbd26b1bc2f351d7e1657ccef261b1504b88cErik if (model.mUri != null && originalModel == null) { 30873714169d34a55fc01e14b30897a1ebbacf9934eErik Log.e(TAG, "Existing event but no originalModel provided. Aborting save."); 30973714169d34a55fc01e14b30897a1ebbacf9934eErik return false; 31073714169d34a55fc01e14b30897a1ebbacf9934eErik } 3114ebfbd26b1bc2f351d7e1657ccef261b1504b88cErik Uri uri = null; 3124ebfbd26b1bc2f351d7e1657ccef261b1504b88cErik if (model.mUri != null) { 3134ebfbd26b1bc2f351d7e1657ccef261b1504b88cErik uri = Uri.parse(model.mUri); 3144ebfbd26b1bc2f351d7e1657ccef261b1504b88cErik } 31573714169d34a55fc01e14b30897a1ebbacf9934eErik 31673714169d34a55fc01e14b30897a1ebbacf9934eErik // Update the "hasAlarm" field for the event 317fea9af5d69ad3e5687a4efd72d7d3adfb8d056c8Andy McFadden ArrayList<ReminderEntry> reminders = model.mReminders; 318fea9af5d69ad3e5687a4efd72d7d3adfb8d056c8Andy McFadden int len = reminders.size(); 31973714169d34a55fc01e14b30897a1ebbacf9934eErik values.put(Events.HAS_ALARM, (len > 0) ? 1 : 0); 32073714169d34a55fc01e14b30897a1ebbacf9934eErik 32173714169d34a55fc01e14b30897a1ebbacf9934eErik if (uri == null) { 32273714169d34a55fc01e14b30897a1ebbacf9934eErik // Add hasAttendeeData for a new event 32373714169d34a55fc01e14b30897a1ebbacf9934eErik values.put(Events.HAS_ATTENDEE_DATA, 1); 324e3f85db16cfc50b25b97c64d8ab99820d579e5deMichael Chan values.put(Events.STATUS, Events.STATUS_CONFIRMED); 32573714169d34a55fc01e14b30897a1ebbacf9934eErik eventIdIndex = ops.size(); 326eca82e9468c002590045338f98588ad3ce1a0bb3Erik ContentProviderOperation.Builder b = ContentProviderOperation.newInsert( 327eca82e9468c002590045338f98588ad3ce1a0bb3Erik Events.CONTENT_URI).withValues(values); 32873714169d34a55fc01e14b30897a1ebbacf9934eErik ops.add(b.build()); 32973714169d34a55fc01e14b30897a1ebbacf9934eErik forceSaveReminders = true; 33073714169d34a55fc01e14b30897a1ebbacf9934eErik 33166b2067e0a389180f909eb0a528bea5f6becca23Michael Chan } else if (TextUtils.isEmpty(model.mRrule) && TextUtils.isEmpty(originalModel.mRrule)) { 33273714169d34a55fc01e14b30897a1ebbacf9934eErik // Simple update to a non-recurring event 33373714169d34a55fc01e14b30897a1ebbacf9934eErik checkTimeDependentFields(originalModel, model, values, modifyWhich); 33473714169d34a55fc01e14b30897a1ebbacf9934eErik ops.add(ContentProviderOperation.newUpdate(uri).withValues(values).build()); 33573714169d34a55fc01e14b30897a1ebbacf9934eErik 33666b2067e0a389180f909eb0a528bea5f6becca23Michael Chan } else if (TextUtils.isEmpty(originalModel.mRrule)) { 33773714169d34a55fc01e14b30897a1ebbacf9934eErik // This event was changed from a non-repeating event to a 33873714169d34a55fc01e14b30897a1ebbacf9934eErik // repeating event. 33973714169d34a55fc01e14b30897a1ebbacf9934eErik ops.add(ContentProviderOperation.newUpdate(uri).withValues(values).build()); 34073714169d34a55fc01e14b30897a1ebbacf9934eErik 34173714169d34a55fc01e14b30897a1ebbacf9934eErik } else if (modifyWhich == MODIFY_SELECTED) { 34273714169d34a55fc01e14b30897a1ebbacf9934eErik // Modify contents of the current instance of repeating event 34373714169d34a55fc01e14b30897a1ebbacf9934eErik // Create a recurrence exception 34473714169d34a55fc01e14b30897a1ebbacf9934eErik long begin = model.mOriginalStart; 3450e1e62408b96e1532eb6f6a609ae4c817751aaf3RoboErik values.put(Events.ORIGINAL_SYNC_ID, originalModel.mSyncId); 34673714169d34a55fc01e14b30897a1ebbacf9934eErik values.put(Events.ORIGINAL_INSTANCE_TIME, begin); 34773714169d34a55fc01e14b30897a1ebbacf9934eErik boolean allDay = originalModel.mAllDay; 34873714169d34a55fc01e14b30897a1ebbacf9934eErik values.put(Events.ORIGINAL_ALL_DAY, allDay ? 1 : 0); 349e3f85db16cfc50b25b97c64d8ab99820d579e5deMichael Chan values.put(Events.STATUS, originalModel.mEventStatus); 35073714169d34a55fc01e14b30897a1ebbacf9934eErik 35173714169d34a55fc01e14b30897a1ebbacf9934eErik eventIdIndex = ops.size(); 352eca82e9468c002590045338f98588ad3ce1a0bb3Erik ContentProviderOperation.Builder b = ContentProviderOperation.newInsert( 353eca82e9468c002590045338f98588ad3ce1a0bb3Erik Events.CONTENT_URI).withValues(values); 35473714169d34a55fc01e14b30897a1ebbacf9934eErik ops.add(b.build()); 35573714169d34a55fc01e14b30897a1ebbacf9934eErik forceSaveReminders = true; 35673714169d34a55fc01e14b30897a1ebbacf9934eErik 35773714169d34a55fc01e14b30897a1ebbacf9934eErik } else if (modifyWhich == MODIFY_ALL_FOLLOWING) { 35873714169d34a55fc01e14b30897a1ebbacf9934eErik 35966b2067e0a389180f909eb0a528bea5f6becca23Michael Chan if (TextUtils.isEmpty(model.mRrule)) { 36073714169d34a55fc01e14b30897a1ebbacf9934eErik // We've changed a recurring event to a non-recurring event. 36173714169d34a55fc01e14b30897a1ebbacf9934eErik // If the event we are editing is the first in the series, 362eca82e9468c002590045338f98588ad3ce1a0bb3Erik // then delete the whole series. Otherwise, update the series 36373714169d34a55fc01e14b30897a1ebbacf9934eErik // to end at the new start time. 36473714169d34a55fc01e14b30897a1ebbacf9934eErik if (isFirstEventInSeries(model, originalModel)) { 36573714169d34a55fc01e14b30897a1ebbacf9934eErik ops.add(ContentProviderOperation.newDelete(uri).build()); 36673714169d34a55fc01e14b30897a1ebbacf9934eErik } else { 367152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden // Update the current repeating event to end at the new start time. We 368152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden // ignore the RRULE returned because the exception event doesn't want one. 36973714169d34a55fc01e14b30897a1ebbacf9934eErik updatePastEvents(ops, originalModel, model.mOriginalStart); 37073714169d34a55fc01e14b30897a1ebbacf9934eErik } 37173714169d34a55fc01e14b30897a1ebbacf9934eErik eventIdIndex = ops.size(); 372e3f85db16cfc50b25b97c64d8ab99820d579e5deMichael Chan values.put(Events.STATUS, originalModel.mEventStatus); 373eca82e9468c002590045338f98588ad3ce1a0bb3Erik ops.add(ContentProviderOperation.newInsert(Events.CONTENT_URI).withValues(values) 37473714169d34a55fc01e14b30897a1ebbacf9934eErik .build()); 37573714169d34a55fc01e14b30897a1ebbacf9934eErik } else { 37673714169d34a55fc01e14b30897a1ebbacf9934eErik if (isFirstEventInSeries(model, originalModel)) { 37773714169d34a55fc01e14b30897a1ebbacf9934eErik checkTimeDependentFields(originalModel, model, values, modifyWhich); 378eca82e9468c002590045338f98588ad3ce1a0bb3Erik ContentProviderOperation.Builder b = ContentProviderOperation.newUpdate(uri) 37973714169d34a55fc01e14b30897a1ebbacf9934eErik .withValues(values); 38073714169d34a55fc01e14b30897a1ebbacf9934eErik ops.add(b.build()); 38173714169d34a55fc01e14b30897a1ebbacf9934eErik } else { 382152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden // We need to update the existing recurrence to end before the exception 383152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden // event starts. If the recurrence rule has a COUNT, we need to adjust 384152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden // that in the original and in the exception. This call rewrites the 385152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden // original event's recurrence rule (in "ops"), and returns a new rule 386152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden // for the exception. If the exception explicitly set a new rule, however, 387152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden // we don't want to overwrite it. 388152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden String newRrule = updatePastEvents(ops, originalModel, model.mOriginalStart); 389152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden if (model.mRrule.equals(originalModel.mRrule)) { 390152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden values.put(Events.RRULE, newRrule); 391152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden } 39273714169d34a55fc01e14b30897a1ebbacf9934eErik 39373714169d34a55fc01e14b30897a1ebbacf9934eErik // Create a new event with the user-modified fields 39473714169d34a55fc01e14b30897a1ebbacf9934eErik eventIdIndex = ops.size(); 395e3f85db16cfc50b25b97c64d8ab99820d579e5deMichael Chan values.put(Events.STATUS, originalModel.mEventStatus); 396eca82e9468c002590045338f98588ad3ce1a0bb3Erik ops.add(ContentProviderOperation.newInsert(Events.CONTENT_URI).withValues( 397eca82e9468c002590045338f98588ad3ce1a0bb3Erik values).build()); 39873714169d34a55fc01e14b30897a1ebbacf9934eErik } 39973714169d34a55fc01e14b30897a1ebbacf9934eErik } 40073714169d34a55fc01e14b30897a1ebbacf9934eErik forceSaveReminders = true; 40173714169d34a55fc01e14b30897a1ebbacf9934eErik 40273714169d34a55fc01e14b30897a1ebbacf9934eErik } else if (modifyWhich == MODIFY_ALL) { 40373714169d34a55fc01e14b30897a1ebbacf9934eErik 40473714169d34a55fc01e14b30897a1ebbacf9934eErik // Modify all instances of repeating event 40566b2067e0a389180f909eb0a528bea5f6becca23Michael Chan if (TextUtils.isEmpty(model.mRrule)) { 40673714169d34a55fc01e14b30897a1ebbacf9934eErik // We've changed a recurring event to a non-recurring event. 40773714169d34a55fc01e14b30897a1ebbacf9934eErik // Delete the whole series and replace it with a new 40873714169d34a55fc01e14b30897a1ebbacf9934eErik // non-recurring event. 40973714169d34a55fc01e14b30897a1ebbacf9934eErik ops.add(ContentProviderOperation.newDelete(uri).build()); 41073714169d34a55fc01e14b30897a1ebbacf9934eErik 41173714169d34a55fc01e14b30897a1ebbacf9934eErik eventIdIndex = ops.size(); 412eca82e9468c002590045338f98588ad3ce1a0bb3Erik ops.add(ContentProviderOperation.newInsert(Events.CONTENT_URI).withValues(values) 41373714169d34a55fc01e14b30897a1ebbacf9934eErik .build()); 41473714169d34a55fc01e14b30897a1ebbacf9934eErik forceSaveReminders = true; 41573714169d34a55fc01e14b30897a1ebbacf9934eErik } else { 41673714169d34a55fc01e14b30897a1ebbacf9934eErik checkTimeDependentFields(originalModel, model, values, modifyWhich); 417eca82e9468c002590045338f98588ad3ce1a0bb3Erik ops.add(ContentProviderOperation.newUpdate(uri).withValues(values).build()); 41873714169d34a55fc01e14b30897a1ebbacf9934eErik } 41973714169d34a55fc01e14b30897a1ebbacf9934eErik } 42073714169d34a55fc01e14b30897a1ebbacf9934eErik 42173714169d34a55fc01e14b30897a1ebbacf9934eErik // New Event or New Exception to an existing event 42273714169d34a55fc01e14b30897a1ebbacf9934eErik boolean newEvent = (eventIdIndex != -1); 423fea9af5d69ad3e5687a4efd72d7d3adfb8d056c8Andy McFadden ArrayList<ReminderEntry> originalReminders; 42473714169d34a55fc01e14b30897a1ebbacf9934eErik if (originalModel != null) { 425fea9af5d69ad3e5687a4efd72d7d3adfb8d056c8Andy McFadden originalReminders = originalModel.mReminders; 42673714169d34a55fc01e14b30897a1ebbacf9934eErik } else { 427fea9af5d69ad3e5687a4efd72d7d3adfb8d056c8Andy McFadden originalReminders = new ArrayList<ReminderEntry>(); 42873714169d34a55fc01e14b30897a1ebbacf9934eErik } 42973714169d34a55fc01e14b30897a1ebbacf9934eErik 43073714169d34a55fc01e14b30897a1ebbacf9934eErik if (newEvent) { 431fea9af5d69ad3e5687a4efd72d7d3adfb8d056c8Andy McFadden saveRemindersWithBackRef(ops, eventIdIndex, reminders, originalReminders, 432eca82e9468c002590045338f98588ad3ce1a0bb3Erik forceSaveReminders); 43373714169d34a55fc01e14b30897a1ebbacf9934eErik } else if (uri != null) { 43473714169d34a55fc01e14b30897a1ebbacf9934eErik long eventId = ContentUris.parseId(uri); 435fea9af5d69ad3e5687a4efd72d7d3adfb8d056c8Andy McFadden saveReminders(ops, eventId, reminders, originalReminders, forceSaveReminders); 43673714169d34a55fc01e14b30897a1ebbacf9934eErik } 43773714169d34a55fc01e14b30897a1ebbacf9934eErik 43873714169d34a55fc01e14b30897a1ebbacf9934eErik ContentProviderOperation.Builder b; 43973714169d34a55fc01e14b30897a1ebbacf9934eErik boolean hasAttendeeData = model.mHasAttendeeData; 44073714169d34a55fc01e14b30897a1ebbacf9934eErik 441ef4530b42095069916d2bf4945a2581722b7e318Michael Chan if (hasAttendeeData && model.mOwnerAttendeeId == -1) { 442ef4530b42095069916d2bf4945a2581722b7e318Michael Chan // Organizer is not an attendee 44373714169d34a55fc01e14b30897a1ebbacf9934eErik 44473714169d34a55fc01e14b30897a1ebbacf9934eErik String ownerEmail = model.mOwnerAccount; 445ef4530b42095069916d2bf4945a2581722b7e318Michael Chan if (model.mAttendeesList.size() != 0 && Utils.isValidEmail(ownerEmail)) { 446ef4530b42095069916d2bf4945a2581722b7e318Michael Chan // Add organizer as attendee since we got some attendees 447ef4530b42095069916d2bf4945a2581722b7e318Michael Chan 448ef4530b42095069916d2bf4945a2581722b7e318Michael Chan values.clear(); 44973714169d34a55fc01e14b30897a1ebbacf9934eErik values.put(Attendees.ATTENDEE_EMAIL, ownerEmail); 450eca82e9468c002590045338f98588ad3ce1a0bb3Erik values.put(Attendees.ATTENDEE_RELATIONSHIP, Attendees.RELATIONSHIP_ORGANIZER); 451ef4530b42095069916d2bf4945a2581722b7e318Michael Chan values.put(Attendees.ATTENDEE_TYPE, Attendees.TYPE_REQUIRED); 452ef4530b42095069916d2bf4945a2581722b7e318Michael Chan values.put(Attendees.ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_ACCEPTED); 453e5149edad36737783356778819a9c66993a9cf28Michael Chan 454ef4530b42095069916d2bf4945a2581722b7e318Michael Chan if (newEvent) { 455ef4530b42095069916d2bf4945a2581722b7e318Michael Chan b = ContentProviderOperation.newInsert(Attendees.CONTENT_URI) 456ef4530b42095069916d2bf4945a2581722b7e318Michael Chan .withValues(values); 457ef4530b42095069916d2bf4945a2581722b7e318Michael Chan b.withValueBackReference(Attendees.EVENT_ID, eventIdIndex); 458ef4530b42095069916d2bf4945a2581722b7e318Michael Chan } else { 459ef4530b42095069916d2bf4945a2581722b7e318Michael Chan values.put(Attendees.EVENT_ID, model.mId); 460ef4530b42095069916d2bf4945a2581722b7e318Michael Chan b = ContentProviderOperation.newInsert(Attendees.CONTENT_URI) 461ef4530b42095069916d2bf4945a2581722b7e318Michael Chan .withValues(values); 462e5149edad36737783356778819a9c66993a9cf28Michael Chan } 46373714169d34a55fc01e14b30897a1ebbacf9934eErik ops.add(b.build()); 46473714169d34a55fc01e14b30897a1ebbacf9934eErik } 465d845fbe558d5229102b58cce70a1b29fe6cb6967Erik } else if (hasAttendeeData && 466d845fbe558d5229102b58cce70a1b29fe6cb6967Erik model.mSelfAttendeeStatus != originalModel.mSelfAttendeeStatus && 467d845fbe558d5229102b58cce70a1b29fe6cb6967Erik model.mOwnerAttendeeId != -1) { 468d845fbe558d5229102b58cce70a1b29fe6cb6967Erik if (DEBUG) { 469d845fbe558d5229102b58cce70a1b29fe6cb6967Erik Log.d(TAG, "Setting attendee status to " + model.mSelfAttendeeStatus); 470d845fbe558d5229102b58cce70a1b29fe6cb6967Erik } 471d845fbe558d5229102b58cce70a1b29fe6cb6967Erik Uri attUri = ContentUris.withAppendedId(Attendees.CONTENT_URI, model.mOwnerAttendeeId); 472d845fbe558d5229102b58cce70a1b29fe6cb6967Erik 473d845fbe558d5229102b58cce70a1b29fe6cb6967Erik values.clear(); 474d845fbe558d5229102b58cce70a1b29fe6cb6967Erik values.put(Attendees.ATTENDEE_STATUS, model.mSelfAttendeeStatus); 475d845fbe558d5229102b58cce70a1b29fe6cb6967Erik values.put(Attendees.EVENT_ID, model.mId); 476d845fbe558d5229102b58cce70a1b29fe6cb6967Erik b = ContentProviderOperation.newUpdate(attUri).withValues(values); 477d845fbe558d5229102b58cce70a1b29fe6cb6967Erik ops.add(b.build()); 47873714169d34a55fc01e14b30897a1ebbacf9934eErik } 47973714169d34a55fc01e14b30897a1ebbacf9934eErik 480eca82e9468c002590045338f98588ad3ce1a0bb3Erik // TODO: is this the right test? this currently checks if this is 481eca82e9468c002590045338f98588ad3ce1a0bb3Erik // a new event or an existing event. or is this a paranoia check? 48273714169d34a55fc01e14b30897a1ebbacf9934eErik if (hasAttendeeData && (newEvent || uri != null)) { 483d845fbe558d5229102b58cce70a1b29fe6cb6967Erik String attendees = model.getAttendeesString(); 48473714169d34a55fc01e14b30897a1ebbacf9934eErik String originalAttendeesString; 48573714169d34a55fc01e14b30897a1ebbacf9934eErik if (originalModel != null) { 486d845fbe558d5229102b58cce70a1b29fe6cb6967Erik originalAttendeesString = originalModel.getAttendeesString(); 48773714169d34a55fc01e14b30897a1ebbacf9934eErik } else { 48873714169d34a55fc01e14b30897a1ebbacf9934eErik originalAttendeesString = ""; 48973714169d34a55fc01e14b30897a1ebbacf9934eErik } 490eca82e9468c002590045338f98588ad3ce1a0bb3Erik // Hit the content provider only if this is a new event or the user 491eca82e9468c002590045338f98588ad3ce1a0bb3Erik // has changed it 49273714169d34a55fc01e14b30897a1ebbacf9934eErik if (newEvent || !TextUtils.equals(originalAttendeesString, attendees)) { 49373714169d34a55fc01e14b30897a1ebbacf9934eErik // figure out which attendees need to be added and which ones 494eca82e9468c002590045338f98588ad3ce1a0bb3Erik // need to be deleted. use a linked hash set, so we maintain 49573714169d34a55fc01e14b30897a1ebbacf9934eErik // order (but also remove duplicates). 496d845fbe558d5229102b58cce70a1b29fe6cb6967Erik HashMap<String, Attendee> newAttendees = model.mAttendeesList; 497d845fbe558d5229102b58cce70a1b29fe6cb6967Erik LinkedList<String> removedAttendees = new LinkedList<String>(); 49873714169d34a55fc01e14b30897a1ebbacf9934eErik 49973714169d34a55fc01e14b30897a1ebbacf9934eErik // the eventId is only used if eventIdIndex is -1. 50073714169d34a55fc01e14b30897a1ebbacf9934eErik // TODO: clean up this code. 50173714169d34a55fc01e14b30897a1ebbacf9934eErik long eventId = uri != null ? ContentUris.parseId(uri) : -1; 50273714169d34a55fc01e14b30897a1ebbacf9934eErik 50373714169d34a55fc01e14b30897a1ebbacf9934eErik // only compute deltas if this is an existing event. 50473714169d34a55fc01e14b30897a1ebbacf9934eErik // new events (being inserted into the Events table) won't 50573714169d34a55fc01e14b30897a1ebbacf9934eErik // have any existing attendees. 50673714169d34a55fc01e14b30897a1ebbacf9934eErik if (!newEvent) { 507d845fbe558d5229102b58cce70a1b29fe6cb6967Erik removedAttendees.clear(); 508d845fbe558d5229102b58cce70a1b29fe6cb6967Erik HashMap<String, Attendee> originalAttendees = originalModel.mAttendeesList; 509d845fbe558d5229102b58cce70a1b29fe6cb6967Erik for (String originalEmail : originalAttendees.keySet()) { 510d845fbe558d5229102b58cce70a1b29fe6cb6967Erik if (newAttendees.containsKey(originalEmail)) { 511eca82e9468c002590045338f98588ad3ce1a0bb3Erik // existing attendee. remove from new attendees set. 512d845fbe558d5229102b58cce70a1b29fe6cb6967Erik newAttendees.remove(originalEmail); 51373714169d34a55fc01e14b30897a1ebbacf9934eErik } else { 514eca82e9468c002590045338f98588ad3ce1a0bb3Erik // no longer in attendees. mark as removed. 515d845fbe558d5229102b58cce70a1b29fe6cb6967Erik removedAttendees.add(originalEmail); 51673714169d34a55fc01e14b30897a1ebbacf9934eErik } 51773714169d34a55fc01e14b30897a1ebbacf9934eErik } 51873714169d34a55fc01e14b30897a1ebbacf9934eErik 51973714169d34a55fc01e14b30897a1ebbacf9934eErik // delete removed attendees if necessary 52073714169d34a55fc01e14b30897a1ebbacf9934eErik if (removedAttendees.size() > 0) { 52173714169d34a55fc01e14b30897a1ebbacf9934eErik b = ContentProviderOperation.newDelete(Attendees.CONTENT_URI); 52273714169d34a55fc01e14b30897a1ebbacf9934eErik 52373714169d34a55fc01e14b30897a1ebbacf9934eErik String[] args = new String[removedAttendees.size() + 1]; 52473714169d34a55fc01e14b30897a1ebbacf9934eErik args[0] = Long.toString(eventId); 52573714169d34a55fc01e14b30897a1ebbacf9934eErik int i = 1; 52673714169d34a55fc01e14b30897a1ebbacf9934eErik StringBuilder deleteWhere = new StringBuilder(ATTENDEES_DELETE_PREFIX); 527d845fbe558d5229102b58cce70a1b29fe6cb6967Erik for (String removedAttendee : removedAttendees) { 52873714169d34a55fc01e14b30897a1ebbacf9934eErik if (i > 1) { 52973714169d34a55fc01e14b30897a1ebbacf9934eErik deleteWhere.append(","); 53073714169d34a55fc01e14b30897a1ebbacf9934eErik } 53173714169d34a55fc01e14b30897a1ebbacf9934eErik deleteWhere.append("?"); 532d845fbe558d5229102b58cce70a1b29fe6cb6967Erik args[i++] = removedAttendee; 53373714169d34a55fc01e14b30897a1ebbacf9934eErik } 53473714169d34a55fc01e14b30897a1ebbacf9934eErik deleteWhere.append(")"); 53573714169d34a55fc01e14b30897a1ebbacf9934eErik b.withSelection(deleteWhere.toString(), args); 53673714169d34a55fc01e14b30897a1ebbacf9934eErik ops.add(b.build()); 53773714169d34a55fc01e14b30897a1ebbacf9934eErik } 53873714169d34a55fc01e14b30897a1ebbacf9934eErik } 53973714169d34a55fc01e14b30897a1ebbacf9934eErik 54073714169d34a55fc01e14b30897a1ebbacf9934eErik if (newAttendees.size() > 0) { 54173714169d34a55fc01e14b30897a1ebbacf9934eErik // Insert the new attendees 542d845fbe558d5229102b58cce70a1b29fe6cb6967Erik for (Attendee attendee : newAttendees.values()) { 54373714169d34a55fc01e14b30897a1ebbacf9934eErik values.clear(); 544d845fbe558d5229102b58cce70a1b29fe6cb6967Erik values.put(Attendees.ATTENDEE_NAME, attendee.mName); 545d845fbe558d5229102b58cce70a1b29fe6cb6967Erik values.put(Attendees.ATTENDEE_EMAIL, attendee.mEmail); 54673714169d34a55fc01e14b30897a1ebbacf9934eErik values.put(Attendees.ATTENDEE_RELATIONSHIP, 54773714169d34a55fc01e14b30897a1ebbacf9934eErik Attendees.RELATIONSHIP_ATTENDEE); 548ef4530b42095069916d2bf4945a2581722b7e318Michael Chan values.put(Attendees.ATTENDEE_TYPE, Attendees.TYPE_REQUIRED); 549eca82e9468c002590045338f98588ad3ce1a0bb3Erik values.put(Attendees.ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_NONE); 55073714169d34a55fc01e14b30897a1ebbacf9934eErik 55173714169d34a55fc01e14b30897a1ebbacf9934eErik if (newEvent) { 552eca82e9468c002590045338f98588ad3ce1a0bb3Erik b = ContentProviderOperation.newInsert(Attendees.CONTENT_URI) 55373714169d34a55fc01e14b30897a1ebbacf9934eErik .withValues(values); 55473714169d34a55fc01e14b30897a1ebbacf9934eErik b.withValueBackReference(Attendees.EVENT_ID, eventIdIndex); 55573714169d34a55fc01e14b30897a1ebbacf9934eErik } else { 55673714169d34a55fc01e14b30897a1ebbacf9934eErik values.put(Attendees.EVENT_ID, eventId); 557eca82e9468c002590045338f98588ad3ce1a0bb3Erik b = ContentProviderOperation.newInsert(Attendees.CONTENT_URI) 55873714169d34a55fc01e14b30897a1ebbacf9934eErik .withValues(values); 55973714169d34a55fc01e14b30897a1ebbacf9934eErik } 56073714169d34a55fc01e14b30897a1ebbacf9934eErik ops.add(b.build()); 56173714169d34a55fc01e14b30897a1ebbacf9934eErik } 56273714169d34a55fc01e14b30897a1ebbacf9934eErik } 56373714169d34a55fc01e14b30897a1ebbacf9934eErik } 56473714169d34a55fc01e14b30897a1ebbacf9934eErik } 56573714169d34a55fc01e14b30897a1ebbacf9934eErik 56637960c091b5912dd45beb561a85a9064ea42e616Erik 567a7c0390d9c5dd4ff730de505682687fae5f5ced0RoboErik mService.startBatch(mService.getNextToken(), null, android.provider.CalendarContract.AUTHORITY, ops, 56837960c091b5912dd45beb561a85a9064ea42e616Erik Utils.UNDO_DELAY); 56973714169d34a55fc01e14b30897a1ebbacf9934eErik 57073714169d34a55fc01e14b30897a1ebbacf9934eErik return true; 57173714169d34a55fc01e14b30897a1ebbacf9934eErik } 57273714169d34a55fc01e14b30897a1ebbacf9934eErik 573d845fbe558d5229102b58cce70a1b29fe6cb6967Erik public static LinkedHashSet<Rfc822Token> getAddressesFromList(String list, 574d845fbe558d5229102b58cce70a1b29fe6cb6967Erik Rfc822Validator validator) { 57573714169d34a55fc01e14b30897a1ebbacf9934eErik LinkedHashSet<Rfc822Token> addresses = new LinkedHashSet<Rfc822Token>(); 57673714169d34a55fc01e14b30897a1ebbacf9934eErik Rfc822Tokenizer.tokenize(list, addresses); 577d845fbe558d5229102b58cce70a1b29fe6cb6967Erik if (validator == null) { 578d845fbe558d5229102b58cce70a1b29fe6cb6967Erik return addresses; 579d845fbe558d5229102b58cce70a1b29fe6cb6967Erik } 58073714169d34a55fc01e14b30897a1ebbacf9934eErik 581eca82e9468c002590045338f98588ad3ce1a0bb3Erik // validate the emails, out of paranoia. they should already be 58273714169d34a55fc01e14b30897a1ebbacf9934eErik // validated on input, but drop any invalid emails just to be safe. 58373714169d34a55fc01e14b30897a1ebbacf9934eErik Iterator<Rfc822Token> addressIterator = addresses.iterator(); 58473714169d34a55fc01e14b30897a1ebbacf9934eErik while (addressIterator.hasNext()) { 58573714169d34a55fc01e14b30897a1ebbacf9934eErik Rfc822Token address = addressIterator.next(); 586d845fbe558d5229102b58cce70a1b29fe6cb6967Erik if (!validator.isValid(address.getAddress())) { 587d845fbe558d5229102b58cce70a1b29fe6cb6967Erik Log.v(TAG, "Dropping invalid attendee email address: " + address.getAddress()); 58873714169d34a55fc01e14b30897a1ebbacf9934eErik addressIterator.remove(); 58973714169d34a55fc01e14b30897a1ebbacf9934eErik } 59073714169d34a55fc01e14b30897a1ebbacf9934eErik } 59173714169d34a55fc01e14b30897a1ebbacf9934eErik return addresses; 59273714169d34a55fc01e14b30897a1ebbacf9934eErik } 59373714169d34a55fc01e14b30897a1ebbacf9934eErik 594e700c16ec2464cbba86f91f8f757ae59cbed34b0Mason Tang /** 595e700c16ec2464cbba86f91f8f757ae59cbed34b0Mason Tang * When we aren't given an explicit start time, we default to the next 596e700c16ec2464cbba86f91f8f757ae59cbed34b0Mason Tang * upcoming half hour. So, for example, 5:01 -> 5:30, 5:30 -> 6:00, etc. 597e700c16ec2464cbba86f91f8f757ae59cbed34b0Mason Tang * 598e700c16ec2464cbba86f91f8f757ae59cbed34b0Mason Tang * @return a UTC time in milliseconds representing the next upcoming half 599e700c16ec2464cbba86f91f8f757ae59cbed34b0Mason Tang * hour 600e700c16ec2464cbba86f91f8f757ae59cbed34b0Mason Tang */ 601e700c16ec2464cbba86f91f8f757ae59cbed34b0Mason Tang protected long constructDefaultStartTime(long now) { 602e700c16ec2464cbba86f91f8f757ae59cbed34b0Mason Tang Time defaultStart = new Time(); 603e700c16ec2464cbba86f91f8f757ae59cbed34b0Mason Tang defaultStart.set(now); 604e700c16ec2464cbba86f91f8f757ae59cbed34b0Mason Tang defaultStart.second = 0; 605e700c16ec2464cbba86f91f8f757ae59cbed34b0Mason Tang defaultStart.minute = 30; 606e700c16ec2464cbba86f91f8f757ae59cbed34b0Mason Tang long defaultStartMillis = defaultStart.toMillis(false); 607e700c16ec2464cbba86f91f8f757ae59cbed34b0Mason Tang if (now < defaultStartMillis) { 608e700c16ec2464cbba86f91f8f757ae59cbed34b0Mason Tang return defaultStartMillis; 609e700c16ec2464cbba86f91f8f757ae59cbed34b0Mason Tang } else { 610e700c16ec2464cbba86f91f8f757ae59cbed34b0Mason Tang return defaultStartMillis + 30 * DateUtils.MINUTE_IN_MILLIS; 611e700c16ec2464cbba86f91f8f757ae59cbed34b0Mason Tang } 612e700c16ec2464cbba86f91f8f757ae59cbed34b0Mason Tang } 613e700c16ec2464cbba86f91f8f757ae59cbed34b0Mason Tang 614e700c16ec2464cbba86f91f8f757ae59cbed34b0Mason Tang /** 615e700c16ec2464cbba86f91f8f757ae59cbed34b0Mason Tang * When we aren't given an explicit end time, we default to an hour after 616e700c16ec2464cbba86f91f8f757ae59cbed34b0Mason Tang * the start time. 617e700c16ec2464cbba86f91f8f757ae59cbed34b0Mason Tang * @param startTime the start time 618e700c16ec2464cbba86f91f8f757ae59cbed34b0Mason Tang * @return a default end time 619e700c16ec2464cbba86f91f8f757ae59cbed34b0Mason Tang */ 620e700c16ec2464cbba86f91f8f757ae59cbed34b0Mason Tang protected long constructDefaultEndTime(long startTime) { 621e700c16ec2464cbba86f91f8f757ae59cbed34b0Mason Tang return startTime + DateUtils.HOUR_IN_MILLIS; 622e700c16ec2464cbba86f91f8f757ae59cbed34b0Mason Tang } 623e700c16ec2464cbba86f91f8f757ae59cbed34b0Mason Tang 624eca82e9468c002590045338f98588ad3ce1a0bb3Erik // TODO think about how useful this is. Probably check if our event has 625eca82e9468c002590045338f98588ad3ce1a0bb3Erik // changed early on and either update all or nothing. Should still do the if 626eca82e9468c002590045338f98588ad3ce1a0bb3Erik // MODIFY_ALL bit. 627eca82e9468c002590045338f98588ad3ce1a0bb3Erik void checkTimeDependentFields(CalendarEventModel originalModel, CalendarEventModel model, 628eca82e9468c002590045338f98588ad3ce1a0bb3Erik ContentValues values, int modifyWhich) { 62973714169d34a55fc01e14b30897a1ebbacf9934eErik long oldBegin = model.mOriginalStart; 63073714169d34a55fc01e14b30897a1ebbacf9934eErik long oldEnd = model.mOriginalEnd; 63173714169d34a55fc01e14b30897a1ebbacf9934eErik boolean oldAllDay = originalModel.mAllDay; 63273714169d34a55fc01e14b30897a1ebbacf9934eErik String oldRrule = originalModel.mRrule; 63373714169d34a55fc01e14b30897a1ebbacf9934eErik String oldTimezone = originalModel.mTimezone; 63473714169d34a55fc01e14b30897a1ebbacf9934eErik 63573714169d34a55fc01e14b30897a1ebbacf9934eErik long newBegin = model.mStart; 63673714169d34a55fc01e14b30897a1ebbacf9934eErik long newEnd = model.mEnd; 63773714169d34a55fc01e14b30897a1ebbacf9934eErik boolean newAllDay = model.mAllDay; 63873714169d34a55fc01e14b30897a1ebbacf9934eErik String newRrule = model.mRrule; 63973714169d34a55fc01e14b30897a1ebbacf9934eErik String newTimezone = model.mTimezone; 64073714169d34a55fc01e14b30897a1ebbacf9934eErik 64173714169d34a55fc01e14b30897a1ebbacf9934eErik // If none of the time-dependent fields changed, then remove them. 64273714169d34a55fc01e14b30897a1ebbacf9934eErik if (oldBegin == newBegin && oldEnd == newEnd && oldAllDay == newAllDay 64373714169d34a55fc01e14b30897a1ebbacf9934eErik && TextUtils.equals(oldRrule, newRrule) 644eca82e9468c002590045338f98588ad3ce1a0bb3Erik && TextUtils.equals(oldTimezone, newTimezone)) { 64573714169d34a55fc01e14b30897a1ebbacf9934eErik values.remove(Events.DTSTART); 64673714169d34a55fc01e14b30897a1ebbacf9934eErik values.remove(Events.DTEND); 64773714169d34a55fc01e14b30897a1ebbacf9934eErik values.remove(Events.DURATION); 64873714169d34a55fc01e14b30897a1ebbacf9934eErik values.remove(Events.ALL_DAY); 64973714169d34a55fc01e14b30897a1ebbacf9934eErik values.remove(Events.RRULE); 65073714169d34a55fc01e14b30897a1ebbacf9934eErik values.remove(Events.EVENT_TIMEZONE); 65173714169d34a55fc01e14b30897a1ebbacf9934eErik return; 65273714169d34a55fc01e14b30897a1ebbacf9934eErik } 65373714169d34a55fc01e14b30897a1ebbacf9934eErik 65466b2067e0a389180f909eb0a528bea5f6becca23Michael Chan if (TextUtils.isEmpty(oldRrule) || TextUtils.isEmpty(newRrule)) { 65573714169d34a55fc01e14b30897a1ebbacf9934eErik return; 65673714169d34a55fc01e14b30897a1ebbacf9934eErik } 65773714169d34a55fc01e14b30897a1ebbacf9934eErik 65873714169d34a55fc01e14b30897a1ebbacf9934eErik // If we are modifying all events then we need to set DTSTART to the 65973714169d34a55fc01e14b30897a1ebbacf9934eErik // start time of the first event in the series, not the current 660eca82e9468c002590045338f98588ad3ce1a0bb3Erik // date and time. If the start time of the event was changed 66173714169d34a55fc01e14b30897a1ebbacf9934eErik // (from, say, 3pm to 4pm), then we want to add the time difference 66273714169d34a55fc01e14b30897a1ebbacf9934eErik // to the start time of the first event in the series (the DTSTART 663eca82e9468c002590045338f98588ad3ce1a0bb3Erik // value). If we are modifying one instance or all following instances, 66473714169d34a55fc01e14b30897a1ebbacf9934eErik // then we leave the DTSTART field alone. 66573714169d34a55fc01e14b30897a1ebbacf9934eErik if (modifyWhich == MODIFY_ALL) { 66673714169d34a55fc01e14b30897a1ebbacf9934eErik long oldStartMillis = originalModel.mStart; 66773714169d34a55fc01e14b30897a1ebbacf9934eErik if (oldBegin != newBegin) { 66873714169d34a55fc01e14b30897a1ebbacf9934eErik // The user changed the start time of this event 66973714169d34a55fc01e14b30897a1ebbacf9934eErik long offset = newBegin - oldBegin; 67073714169d34a55fc01e14b30897a1ebbacf9934eErik oldStartMillis += offset; 67173714169d34a55fc01e14b30897a1ebbacf9934eErik } 67273714169d34a55fc01e14b30897a1ebbacf9934eErik if (newAllDay) { 67373714169d34a55fc01e14b30897a1ebbacf9934eErik Time time = new Time(Time.TIMEZONE_UTC); 67473714169d34a55fc01e14b30897a1ebbacf9934eErik time.set(oldStartMillis); 67573714169d34a55fc01e14b30897a1ebbacf9934eErik time.hour = 0; 67673714169d34a55fc01e14b30897a1ebbacf9934eErik time.minute = 0; 67773714169d34a55fc01e14b30897a1ebbacf9934eErik time.second = 0; 67873714169d34a55fc01e14b30897a1ebbacf9934eErik oldStartMillis = time.toMillis(false); 67973714169d34a55fc01e14b30897a1ebbacf9934eErik } 68073714169d34a55fc01e14b30897a1ebbacf9934eErik values.put(Events.DTSTART, oldStartMillis); 68173714169d34a55fc01e14b30897a1ebbacf9934eErik } 68273714169d34a55fc01e14b30897a1ebbacf9934eErik } 68373714169d34a55fc01e14b30897a1ebbacf9934eErik 68473714169d34a55fc01e14b30897a1ebbacf9934eErik /** 685eca82e9468c002590045338f98588ad3ce1a0bb3Erik * Prepares an update to the original event so it stops where the new series 686152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden * begins. When we update 'this and all following' events we need to change 687eca82e9468c002590045338f98588ad3ce1a0bb3Erik * the original event to end before a new series starts. This creates an 688eca82e9468c002590045338f98588ad3ce1a0bb3Erik * update to the old event's rrule to do that. 689152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden *<p> 690152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden * If the event's recurrence rule has a COUNT, we also need to reduce the count in the 691152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden * RRULE for the exception event. 69273714169d34a55fc01e14b30897a1ebbacf9934eErik * 69373714169d34a55fc01e14b30897a1ebbacf9934eErik * @param ops The list of operations to add the update to 69473714169d34a55fc01e14b30897a1ebbacf9934eErik * @param originalModel The original event that we're updating 695152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden * @param endTimeMillis The time before which the event must end (i.e. the start time of the 696152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden * exception event instance). 697152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden * @return A replacement exception recurrence rule. 69873714169d34a55fc01e14b30897a1ebbacf9934eErik */ 699152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden public String updatePastEvents(ArrayList<ContentProviderOperation> ops, 700152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden CalendarEventModel originalModel, long endTimeMillis) { 701152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden boolean origAllDay = originalModel.mAllDay; 702152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden String origRrule = originalModel.mRrule; 703152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden String newRrule = origRrule; 704eca82e9468c002590045338f98588ad3ce1a0bb3Erik 705152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden EventRecurrence origRecurrence = new EventRecurrence(); 706152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden origRecurrence.parse(origRrule); 70773714169d34a55fc01e14b30897a1ebbacf9934eErik 708152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden // Get the start time of the first instance in the original recurrence. 709152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden long startTimeMillis = originalModel.mStart; 710eca82e9468c002590045338f98588ad3ce1a0bb3Erik Time dtstart = new Time(); 711eca82e9468c002590045338f98588ad3ce1a0bb3Erik dtstart.timezone = originalModel.mTimezone; 712152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden dtstart.set(startTimeMillis); 713152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden 714152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden ContentValues updateValues = new ContentValues(); 715152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden 716152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden if (origRecurrence.count > 0) { 717152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden /* 718152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden * Generate the full set of instances for this recurrence, from the first to the 719152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden * one just before endTimeMillis. The list should never be empty, because this method 720152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden * should not be called for the first instance. All we're really interested in is 721152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden * the *number* of instances found. 722152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden * 723152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden * TODO: the model assumes RRULE and ignores RDATE, EXRULE, and EXDATE. For the 724152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden * current environment this is reasonable, but that may not hold in the future. 725152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden * 726152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden * TODO: if COUNT is 1, should we convert the event to non-recurring? e.g. we 727152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden * do an "edit this and all future events" on the 2nd instances. 728152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden */ 729152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden RecurrenceSet recurSet = new RecurrenceSet(originalModel.mRrule, null, null, null); 730152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden RecurrenceProcessor recurProc = new RecurrenceProcessor(); 731152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden long[] recurrences; 732152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden try { 733152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden recurrences = recurProc.expand(dtstart, recurSet, startTimeMillis, endTimeMillis); 734152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden } catch (DateException de) { 735152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden throw new RuntimeException(de); 736152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden } 737152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden 738152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden if (recurrences.length == 0) { 739152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden throw new RuntimeException("can't use this method on first instance"); 740152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden } 741152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden 742152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden EventRecurrence excepRecurrence = new EventRecurrence(); 743152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden excepRecurrence.parse(origRrule); // TODO: add+use a copy constructor instead 744152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden excepRecurrence.count -= recurrences.length; 745152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden newRrule = excepRecurrence.toString(); 746152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden 747152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden origRecurrence.count = recurrences.length; 748152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden 749152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden } else { 750152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden // The "until" time must be in UTC time in order for Google calendar 751152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden // to display it properly. For all-day events, the "until" time string 752152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden // must include just the date field, and not the time field. The 753152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden // repeating events repeat up to and including the "until" time. 754152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden Time untilTime = new Time(); 755152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden untilTime.timezone = Time.TIMEZONE_UTC; 756152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden 757152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden // Subtract one second from the old begin time to get the new 758152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden // "until" time. 759152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden untilTime.set(endTimeMillis - 1000); // subtract one second (1000 millis) 760152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden if (origAllDay) { 761152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden untilTime.hour = 0; 762152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden untilTime.minute = 0; 763152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden untilTime.second = 0; 764152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden untilTime.allDay = true; 765152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden untilTime.normalize(false); 766152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden 767152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden // This should no longer be necessary -- DTSTART should already be in the correct 768152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden // format for an all-day event. 769152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden dtstart.hour = 0; 770152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden dtstart.minute = 0; 771152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden dtstart.second = 0; 772152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden dtstart.allDay = true; 773152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden dtstart.timezone = Time.TIMEZONE_UTC; 774152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden } 775152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden origRecurrence.until = untilTime.format2445(); 77673714169d34a55fc01e14b30897a1ebbacf9934eErik } 77773714169d34a55fc01e14b30897a1ebbacf9934eErik 778152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden updateValues.put(Events.RRULE, origRecurrence.toString()); 779152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden updateValues.put(Events.DTSTART, dtstart.normalize(true)); 78059ead671b50514b49749fcd6a1d4186a270239d2Erik ContentProviderOperation.Builder b = 78159ead671b50514b49749fcd6a1d4186a270239d2Erik ContentProviderOperation.newUpdate(Uri.parse(originalModel.mUri)) 782152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden .withValues(updateValues); 78373714169d34a55fc01e14b30897a1ebbacf9934eErik ops.add(b.build()); 784152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden 785152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden return newRrule; 78673714169d34a55fc01e14b30897a1ebbacf9934eErik } 78773714169d34a55fc01e14b30897a1ebbacf9934eErik 78873714169d34a55fc01e14b30897a1ebbacf9934eErik /** 789eca82e9468c002590045338f98588ad3ce1a0bb3Erik * Compares two models to ensure that they refer to the same event. This is 790eca82e9468c002590045338f98588ad3ce1a0bb3Erik * a safety check to make sure an updated event model refers to the same 791eca82e9468c002590045338f98588ad3ce1a0bb3Erik * event as the original model. If the original model is null then this is a 792eca82e9468c002590045338f98588ad3ce1a0bb3Erik * new event or we're forcing an overwrite so we return true in that case. 793eca82e9468c002590045338f98588ad3ce1a0bb3Erik * The important identifiers are the Calendar Id and the Event Id. 79473714169d34a55fc01e14b30897a1ebbacf9934eErik * 79573714169d34a55fc01e14b30897a1ebbacf9934eErik * @return 79673714169d34a55fc01e14b30897a1ebbacf9934eErik */ 797fbce65e53c7a111955f638db5bf8bee35381e5b7Erik public static boolean isSameEvent(CalendarEventModel model, CalendarEventModel originalModel) { 79873714169d34a55fc01e14b30897a1ebbacf9934eErik if (originalModel == null) { 79973714169d34a55fc01e14b30897a1ebbacf9934eErik return true; 80073714169d34a55fc01e14b30897a1ebbacf9934eErik } 80173714169d34a55fc01e14b30897a1ebbacf9934eErik 80273714169d34a55fc01e14b30897a1ebbacf9934eErik if (model.mCalendarId != originalModel.mCalendarId) { 80373714169d34a55fc01e14b30897a1ebbacf9934eErik return false; 80473714169d34a55fc01e14b30897a1ebbacf9934eErik } 80573714169d34a55fc01e14b30897a1ebbacf9934eErik if (model.mId != originalModel.mId) { 80673714169d34a55fc01e14b30897a1ebbacf9934eErik return false; 80773714169d34a55fc01e14b30897a1ebbacf9934eErik } 80873714169d34a55fc01e14b30897a1ebbacf9934eErik 80973714169d34a55fc01e14b30897a1ebbacf9934eErik return true; 81073714169d34a55fc01e14b30897a1ebbacf9934eErik } 81173714169d34a55fc01e14b30897a1ebbacf9934eErik 812eca82e9468c002590045338f98588ad3ce1a0bb3Erik /** 813eca82e9468c002590045338f98588ad3ce1a0bb3Erik * Saves the reminders, if they changed. Returns true if operations to 814eca82e9468c002590045338f98588ad3ce1a0bb3Erik * update the database were added. 81573714169d34a55fc01e14b30897a1ebbacf9934eErik * 81673714169d34a55fc01e14b30897a1ebbacf9934eErik * @param ops the array of ContentProviderOperations 81773714169d34a55fc01e14b30897a1ebbacf9934eErik * @param eventId the id of the event whose reminders are being updated 818fea9af5d69ad3e5687a4efd72d7d3adfb8d056c8Andy McFadden * @param reminders the array of reminders set by the user 819fea9af5d69ad3e5687a4efd72d7d3adfb8d056c8Andy McFadden * @param originalReminders the original array of reminders 820eca82e9468c002590045338f98588ad3ce1a0bb3Erik * @param forceSave if true, then save the reminders even if they didn't change 82173714169d34a55fc01e14b30897a1ebbacf9934eErik * @return true if operations to update the database were added 82273714169d34a55fc01e14b30897a1ebbacf9934eErik */ 823fbce65e53c7a111955f638db5bf8bee35381e5b7Erik public static boolean saveReminders(ArrayList<ContentProviderOperation> ops, long eventId, 824fea9af5d69ad3e5687a4efd72d7d3adfb8d056c8Andy McFadden ArrayList<ReminderEntry> reminders, ArrayList<ReminderEntry> originalReminders, 82573714169d34a55fc01e14b30897a1ebbacf9934eErik boolean forceSave) { 82673714169d34a55fc01e14b30897a1ebbacf9934eErik // If the reminders have not changed, then don't update the database 827fea9af5d69ad3e5687a4efd72d7d3adfb8d056c8Andy McFadden if (reminders.equals(originalReminders) && !forceSave) { 82873714169d34a55fc01e14b30897a1ebbacf9934eErik return false; 82973714169d34a55fc01e14b30897a1ebbacf9934eErik } 83073714169d34a55fc01e14b30897a1ebbacf9934eErik 83173714169d34a55fc01e14b30897a1ebbacf9934eErik // Delete all the existing reminders for this event 83273714169d34a55fc01e14b30897a1ebbacf9934eErik String where = Reminders.EVENT_ID + "=?"; 83373714169d34a55fc01e14b30897a1ebbacf9934eErik String[] args = new String[] {Long.toString(eventId)}; 834eca82e9468c002590045338f98588ad3ce1a0bb3Erik ContentProviderOperation.Builder b = ContentProviderOperation 835eca82e9468c002590045338f98588ad3ce1a0bb3Erik .newDelete(Reminders.CONTENT_URI); 83673714169d34a55fc01e14b30897a1ebbacf9934eErik b.withSelection(where, args); 83773714169d34a55fc01e14b30897a1ebbacf9934eErik ops.add(b.build()); 83873714169d34a55fc01e14b30897a1ebbacf9934eErik 83973714169d34a55fc01e14b30897a1ebbacf9934eErik ContentValues values = new ContentValues(); 840fea9af5d69ad3e5687a4efd72d7d3adfb8d056c8Andy McFadden int len = reminders.size(); 84173714169d34a55fc01e14b30897a1ebbacf9934eErik 84273714169d34a55fc01e14b30897a1ebbacf9934eErik // Insert the new reminders, if any 84373714169d34a55fc01e14b30897a1ebbacf9934eErik for (int i = 0; i < len; i++) { 844fea9af5d69ad3e5687a4efd72d7d3adfb8d056c8Andy McFadden ReminderEntry re = reminders.get(i); 84573714169d34a55fc01e14b30897a1ebbacf9934eErik 84673714169d34a55fc01e14b30897a1ebbacf9934eErik values.clear(); 847fea9af5d69ad3e5687a4efd72d7d3adfb8d056c8Andy McFadden values.put(Reminders.MINUTES, re.getMinutes()); 848fea9af5d69ad3e5687a4efd72d7d3adfb8d056c8Andy McFadden values.put(Reminders.METHOD, re.getMethod()); 84973714169d34a55fc01e14b30897a1ebbacf9934eErik values.put(Reminders.EVENT_ID, eventId); 85073714169d34a55fc01e14b30897a1ebbacf9934eErik b = ContentProviderOperation.newInsert(Reminders.CONTENT_URI).withValues(values); 85173714169d34a55fc01e14b30897a1ebbacf9934eErik ops.add(b.build()); 85273714169d34a55fc01e14b30897a1ebbacf9934eErik } 85373714169d34a55fc01e14b30897a1ebbacf9934eErik return true; 85473714169d34a55fc01e14b30897a1ebbacf9934eErik } 85573714169d34a55fc01e14b30897a1ebbacf9934eErik 85673714169d34a55fc01e14b30897a1ebbacf9934eErik /** 857eca82e9468c002590045338f98588ad3ce1a0bb3Erik * Saves the reminders, if they changed. Returns true if operations to 858eca82e9468c002590045338f98588ad3ce1a0bb3Erik * update the database were added. Uses a reference id since an id isn't 859eca82e9468c002590045338f98588ad3ce1a0bb3Erik * created until the row is added. 86073714169d34a55fc01e14b30897a1ebbacf9934eErik * 86173714169d34a55fc01e14b30897a1ebbacf9934eErik * @param ops the array of ContentProviderOperations 86273714169d34a55fc01e14b30897a1ebbacf9934eErik * @param eventId the id of the event whose reminders are being updated 86373714169d34a55fc01e14b30897a1ebbacf9934eErik * @param reminderMinutes the array of reminders set by the user 86473714169d34a55fc01e14b30897a1ebbacf9934eErik * @param originalMinutes the original array of reminders 865eca82e9468c002590045338f98588ad3ce1a0bb3Erik * @param forceSave if true, then save the reminders even if they didn't change 86673714169d34a55fc01e14b30897a1ebbacf9934eErik * @return true if operations to update the database were added 86773714169d34a55fc01e14b30897a1ebbacf9934eErik */ 868fea9af5d69ad3e5687a4efd72d7d3adfb8d056c8Andy McFadden public static boolean saveRemindersWithBackRef(ArrayList<ContentProviderOperation> ops, 869fea9af5d69ad3e5687a4efd72d7d3adfb8d056c8Andy McFadden int eventIdIndex, ArrayList<ReminderEntry> reminders, 870fea9af5d69ad3e5687a4efd72d7d3adfb8d056c8Andy McFadden ArrayList<ReminderEntry> originalReminders, boolean forceSave) { 87173714169d34a55fc01e14b30897a1ebbacf9934eErik // If the reminders have not changed, then don't update the database 872fea9af5d69ad3e5687a4efd72d7d3adfb8d056c8Andy McFadden if (reminders.equals(originalReminders) && !forceSave) { 87373714169d34a55fc01e14b30897a1ebbacf9934eErik return false; 87473714169d34a55fc01e14b30897a1ebbacf9934eErik } 87573714169d34a55fc01e14b30897a1ebbacf9934eErik 87673714169d34a55fc01e14b30897a1ebbacf9934eErik // Delete all the existing reminders for this event 877eca82e9468c002590045338f98588ad3ce1a0bb3Erik ContentProviderOperation.Builder b = ContentProviderOperation 878eca82e9468c002590045338f98588ad3ce1a0bb3Erik .newDelete(Reminders.CONTENT_URI); 87973714169d34a55fc01e14b30897a1ebbacf9934eErik b.withSelection(Reminders.EVENT_ID + "=?", new String[1]); 88073714169d34a55fc01e14b30897a1ebbacf9934eErik b.withSelectionBackReference(0, eventIdIndex); 88173714169d34a55fc01e14b30897a1ebbacf9934eErik ops.add(b.build()); 88273714169d34a55fc01e14b30897a1ebbacf9934eErik 88373714169d34a55fc01e14b30897a1ebbacf9934eErik ContentValues values = new ContentValues(); 884fea9af5d69ad3e5687a4efd72d7d3adfb8d056c8Andy McFadden int len = reminders.size(); 88573714169d34a55fc01e14b30897a1ebbacf9934eErik 88673714169d34a55fc01e14b30897a1ebbacf9934eErik // Insert the new reminders, if any 88773714169d34a55fc01e14b30897a1ebbacf9934eErik for (int i = 0; i < len; i++) { 888fea9af5d69ad3e5687a4efd72d7d3adfb8d056c8Andy McFadden ReminderEntry re = reminders.get(i); 88973714169d34a55fc01e14b30897a1ebbacf9934eErik 89073714169d34a55fc01e14b30897a1ebbacf9934eErik values.clear(); 891fea9af5d69ad3e5687a4efd72d7d3adfb8d056c8Andy McFadden values.put(Reminders.MINUTES, re.getMinutes()); 892fea9af5d69ad3e5687a4efd72d7d3adfb8d056c8Andy McFadden values.put(Reminders.METHOD, re.getMethod()); 89373714169d34a55fc01e14b30897a1ebbacf9934eErik b = ContentProviderOperation.newInsert(Reminders.CONTENT_URI).withValues(values); 89473714169d34a55fc01e14b30897a1ebbacf9934eErik b.withValueBackReference(Reminders.EVENT_ID, eventIdIndex); 89573714169d34a55fc01e14b30897a1ebbacf9934eErik ops.add(b.build()); 89673714169d34a55fc01e14b30897a1ebbacf9934eErik } 89773714169d34a55fc01e14b30897a1ebbacf9934eErik return true; 89873714169d34a55fc01e14b30897a1ebbacf9934eErik } 89973714169d34a55fc01e14b30897a1ebbacf9934eErik 900eca82e9468c002590045338f98588ad3ce1a0bb3Erik // It's the first event in the series if the start time before being 901eca82e9468c002590045338f98588ad3ce1a0bb3Erik // modified is the same as the original event's start time 90273714169d34a55fc01e14b30897a1ebbacf9934eErik static boolean isFirstEventInSeries(CalendarEventModel model, 90373714169d34a55fc01e14b30897a1ebbacf9934eErik CalendarEventModel originalModel) { 90473714169d34a55fc01e14b30897a1ebbacf9934eErik return model.mOriginalStart == originalModel.mStart; 90573714169d34a55fc01e14b30897a1ebbacf9934eErik } 90673714169d34a55fc01e14b30897a1ebbacf9934eErik 90773714169d34a55fc01e14b30897a1ebbacf9934eErik // Adds an rRule and duration to a set of content values 90873714169d34a55fc01e14b30897a1ebbacf9934eErik void addRecurrenceRule(ContentValues values, CalendarEventModel model) { 90973714169d34a55fc01e14b30897a1ebbacf9934eErik String rrule = model.mRrule; 91073714169d34a55fc01e14b30897a1ebbacf9934eErik 91173714169d34a55fc01e14b30897a1ebbacf9934eErik values.put(Events.RRULE, rrule); 91273714169d34a55fc01e14b30897a1ebbacf9934eErik long end = model.mEnd; 91373714169d34a55fc01e14b30897a1ebbacf9934eErik long start = model.mStart; 91473714169d34a55fc01e14b30897a1ebbacf9934eErik String duration = model.mDuration; 91573714169d34a55fc01e14b30897a1ebbacf9934eErik 91637960c091b5912dd45beb561a85a9064ea42e616Erik boolean isAllDay = model.mAllDay; 9179f24ae76bc2a57c7cfcd591e0ba2e4f931cad428Paul Sliwowski if (end >= start) { 91837960c091b5912dd45beb561a85a9064ea42e616Erik if (isAllDay) { 91937960c091b5912dd45beb561a85a9064ea42e616Erik // if it's all day compute the duration in days 92037960c091b5912dd45beb561a85a9064ea42e616Erik long days = (end - start + DateUtils.DAY_IN_MILLIS - 1) 92137960c091b5912dd45beb561a85a9064ea42e616Erik / DateUtils.DAY_IN_MILLIS; 92237960c091b5912dd45beb561a85a9064ea42e616Erik duration = "P" + days + "D"; 92373714169d34a55fc01e14b30897a1ebbacf9934eErik } else { 92437960c091b5912dd45beb561a85a9064ea42e616Erik // otherwise compute the duration in seconds 92537960c091b5912dd45beb561a85a9064ea42e616Erik long seconds = (end - start) / DateUtils.SECOND_IN_MILLIS; 92637960c091b5912dd45beb561a85a9064ea42e616Erik duration = "P" + seconds + "S"; 92737960c091b5912dd45beb561a85a9064ea42e616Erik } 92837960c091b5912dd45beb561a85a9064ea42e616Erik } else if (TextUtils.isEmpty(duration)) { 92937960c091b5912dd45beb561a85a9064ea42e616Erik 93037960c091b5912dd45beb561a85a9064ea42e616Erik // If no good duration info exists assume the default 93137960c091b5912dd45beb561a85a9064ea42e616Erik if (isAllDay) { 93237960c091b5912dd45beb561a85a9064ea42e616Erik duration = "P1D"; 93337960c091b5912dd45beb561a85a9064ea42e616Erik } else { 93437960c091b5912dd45beb561a85a9064ea42e616Erik duration = "P3600S"; 93573714169d34a55fc01e14b30897a1ebbacf9934eErik } 93673714169d34a55fc01e14b30897a1ebbacf9934eErik } 93773714169d34a55fc01e14b30897a1ebbacf9934eErik // recurring events should have a duration and dtend set to null 93873714169d34a55fc01e14b30897a1ebbacf9934eErik values.put(Events.DURATION, duration); 939eca82e9468c002590045338f98588ad3ce1a0bb3Erik values.put(Events.DTEND, (Long) null); 94073714169d34a55fc01e14b30897a1ebbacf9934eErik } 94173714169d34a55fc01e14b30897a1ebbacf9934eErik 94273714169d34a55fc01e14b30897a1ebbacf9934eErik /** 943eca82e9468c002590045338f98588ad3ce1a0bb3Erik * Uses the recurrence selection and the model data to build an rrule and 944eca82e9468c002590045338f98588ad3ce1a0bb3Erik * write it to the model. 94573714169d34a55fc01e14b30897a1ebbacf9934eErik * 94673714169d34a55fc01e14b30897a1ebbacf9934eErik * @param selection the type of rrule 94773714169d34a55fc01e14b30897a1ebbacf9934eErik * @param model The event to update 9488e3d430a020744faa21bf4ca24f1a99c36ec5c4fMason Tang * @param weekStart the week start day, specified as java.util.Calendar 9498e3d430a020744faa21bf4ca24f1a99c36ec5c4fMason Tang * constants 95073714169d34a55fc01e14b30897a1ebbacf9934eErik */ 9518e3d430a020744faa21bf4ca24f1a99c36ec5c4fMason Tang static void updateRecurrenceRule(int selection, CalendarEventModel model, 9528e3d430a020744faa21bf4ca24f1a99c36ec5c4fMason Tang int weekStart) { 95373714169d34a55fc01e14b30897a1ebbacf9934eErik // Make sure we don't have any leftover data from the previous setting 95473714169d34a55fc01e14b30897a1ebbacf9934eErik EventRecurrence eventRecurrence = new EventRecurrence(); 95573714169d34a55fc01e14b30897a1ebbacf9934eErik 95673714169d34a55fc01e14b30897a1ebbacf9934eErik if (selection == DOES_NOT_REPEAT) { 95773714169d34a55fc01e14b30897a1ebbacf9934eErik model.mRrule = null; 95873714169d34a55fc01e14b30897a1ebbacf9934eErik return; 95973714169d34a55fc01e14b30897a1ebbacf9934eErik } else if (selection == REPEATS_CUSTOM) { 96073714169d34a55fc01e14b30897a1ebbacf9934eErik // Keep custom recurrence as before. 96173714169d34a55fc01e14b30897a1ebbacf9934eErik return; 96273714169d34a55fc01e14b30897a1ebbacf9934eErik } else if (selection == REPEATS_DAILY) { 96373714169d34a55fc01e14b30897a1ebbacf9934eErik eventRecurrence.freq = EventRecurrence.DAILY; 96473714169d34a55fc01e14b30897a1ebbacf9934eErik } else if (selection == REPEATS_EVERY_WEEKDAY) { 96573714169d34a55fc01e14b30897a1ebbacf9934eErik eventRecurrence.freq = EventRecurrence.WEEKLY; 96673714169d34a55fc01e14b30897a1ebbacf9934eErik int dayCount = 5; 96773714169d34a55fc01e14b30897a1ebbacf9934eErik int[] byday = new int[dayCount]; 96873714169d34a55fc01e14b30897a1ebbacf9934eErik int[] bydayNum = new int[dayCount]; 96973714169d34a55fc01e14b30897a1ebbacf9934eErik 97073714169d34a55fc01e14b30897a1ebbacf9934eErik byday[0] = EventRecurrence.MO; 97173714169d34a55fc01e14b30897a1ebbacf9934eErik byday[1] = EventRecurrence.TU; 97273714169d34a55fc01e14b30897a1ebbacf9934eErik byday[2] = EventRecurrence.WE; 97373714169d34a55fc01e14b30897a1ebbacf9934eErik byday[3] = EventRecurrence.TH; 97473714169d34a55fc01e14b30897a1ebbacf9934eErik byday[4] = EventRecurrence.FR; 97573714169d34a55fc01e14b30897a1ebbacf9934eErik for (int day = 0; day < dayCount; day++) { 97673714169d34a55fc01e14b30897a1ebbacf9934eErik bydayNum[day] = 0; 97773714169d34a55fc01e14b30897a1ebbacf9934eErik } 97873714169d34a55fc01e14b30897a1ebbacf9934eErik 97973714169d34a55fc01e14b30897a1ebbacf9934eErik eventRecurrence.byday = byday; 98073714169d34a55fc01e14b30897a1ebbacf9934eErik eventRecurrence.bydayNum = bydayNum; 98173714169d34a55fc01e14b30897a1ebbacf9934eErik eventRecurrence.bydayCount = dayCount; 98273714169d34a55fc01e14b30897a1ebbacf9934eErik } else if (selection == REPEATS_WEEKLY_ON_DAY) { 98373714169d34a55fc01e14b30897a1ebbacf9934eErik eventRecurrence.freq = EventRecurrence.WEEKLY; 98473714169d34a55fc01e14b30897a1ebbacf9934eErik int[] days = new int[1]; 98573714169d34a55fc01e14b30897a1ebbacf9934eErik int dayCount = 1; 98673714169d34a55fc01e14b30897a1ebbacf9934eErik int[] dayNum = new int[dayCount]; 98773714169d34a55fc01e14b30897a1ebbacf9934eErik Time startTime = new Time(model.mTimezone); 98873714169d34a55fc01e14b30897a1ebbacf9934eErik startTime.set(model.mStart); 98973714169d34a55fc01e14b30897a1ebbacf9934eErik 99073714169d34a55fc01e14b30897a1ebbacf9934eErik days[0] = EventRecurrence.timeDay2Day(startTime.weekDay); 99173714169d34a55fc01e14b30897a1ebbacf9934eErik // not sure why this needs to be zero, but set it for now. 99273714169d34a55fc01e14b30897a1ebbacf9934eErik dayNum[0] = 0; 99373714169d34a55fc01e14b30897a1ebbacf9934eErik 99473714169d34a55fc01e14b30897a1ebbacf9934eErik eventRecurrence.byday = days; 99573714169d34a55fc01e14b30897a1ebbacf9934eErik eventRecurrence.bydayNum = dayNum; 99673714169d34a55fc01e14b30897a1ebbacf9934eErik eventRecurrence.bydayCount = dayCount; 99773714169d34a55fc01e14b30897a1ebbacf9934eErik } else if (selection == REPEATS_MONTHLY_ON_DAY) { 99873714169d34a55fc01e14b30897a1ebbacf9934eErik eventRecurrence.freq = EventRecurrence.MONTHLY; 99973714169d34a55fc01e14b30897a1ebbacf9934eErik eventRecurrence.bydayCount = 0; 100073714169d34a55fc01e14b30897a1ebbacf9934eErik eventRecurrence.bymonthdayCount = 1; 100173714169d34a55fc01e14b30897a1ebbacf9934eErik int[] bymonthday = new int[1]; 100273714169d34a55fc01e14b30897a1ebbacf9934eErik Time startTime = new Time(model.mTimezone); 100373714169d34a55fc01e14b30897a1ebbacf9934eErik startTime.set(model.mStart); 100473714169d34a55fc01e14b30897a1ebbacf9934eErik bymonthday[0] = startTime.monthDay; 100573714169d34a55fc01e14b30897a1ebbacf9934eErik eventRecurrence.bymonthday = bymonthday; 100673714169d34a55fc01e14b30897a1ebbacf9934eErik } else if (selection == REPEATS_MONTHLY_ON_DAY_COUNT) { 100773714169d34a55fc01e14b30897a1ebbacf9934eErik eventRecurrence.freq = EventRecurrence.MONTHLY; 100873714169d34a55fc01e14b30897a1ebbacf9934eErik eventRecurrence.bydayCount = 1; 100973714169d34a55fc01e14b30897a1ebbacf9934eErik eventRecurrence.bymonthdayCount = 0; 101073714169d34a55fc01e14b30897a1ebbacf9934eErik 101173714169d34a55fc01e14b30897a1ebbacf9934eErik int[] byday = new int[1]; 101273714169d34a55fc01e14b30897a1ebbacf9934eErik int[] bydayNum = new int[1]; 101373714169d34a55fc01e14b30897a1ebbacf9934eErik Time startTime = new Time(model.mTimezone); 101473714169d34a55fc01e14b30897a1ebbacf9934eErik startTime.set(model.mStart); 101573714169d34a55fc01e14b30897a1ebbacf9934eErik // Compute the week number (for example, the "2nd" Monday) 101673714169d34a55fc01e14b30897a1ebbacf9934eErik int dayCount = 1 + ((startTime.monthDay - 1) / 7); 101773714169d34a55fc01e14b30897a1ebbacf9934eErik if (dayCount == 5) { 101873714169d34a55fc01e14b30897a1ebbacf9934eErik dayCount = -1; 101973714169d34a55fc01e14b30897a1ebbacf9934eErik } 102073714169d34a55fc01e14b30897a1ebbacf9934eErik bydayNum[0] = dayCount; 102173714169d34a55fc01e14b30897a1ebbacf9934eErik byday[0] = EventRecurrence.timeDay2Day(startTime.weekDay); 102273714169d34a55fc01e14b30897a1ebbacf9934eErik eventRecurrence.byday = byday; 102373714169d34a55fc01e14b30897a1ebbacf9934eErik eventRecurrence.bydayNum = bydayNum; 102473714169d34a55fc01e14b30897a1ebbacf9934eErik } else if (selection == REPEATS_YEARLY) { 102573714169d34a55fc01e14b30897a1ebbacf9934eErik eventRecurrence.freq = EventRecurrence.YEARLY; 102673714169d34a55fc01e14b30897a1ebbacf9934eErik } 102773714169d34a55fc01e14b30897a1ebbacf9934eErik 102873714169d34a55fc01e14b30897a1ebbacf9934eErik // Set the week start day. 10298e3d430a020744faa21bf4ca24f1a99c36ec5c4fMason Tang eventRecurrence.wkst = EventRecurrence.calendarDay2Day(weekStart); 103073714169d34a55fc01e14b30897a1ebbacf9934eErik model.mRrule = eventRecurrence.toString(); 103173714169d34a55fc01e14b30897a1ebbacf9934eErik } 103273714169d34a55fc01e14b30897a1ebbacf9934eErik 103373714169d34a55fc01e14b30897a1ebbacf9934eErik /** 1034eca82e9468c002590045338f98588ad3ce1a0bb3Erik * Uses an event cursor to fill in the given model This method assumes the 1035eca82e9468c002590045338f98588ad3ce1a0bb3Erik * cursor used {@link #EVENT_PROJECTION} as it's query projection. It uses 1036eca82e9468c002590045338f98588ad3ce1a0bb3Erik * the cursor to fill in the given model with all the information available. 103773714169d34a55fc01e14b30897a1ebbacf9934eErik * 103873714169d34a55fc01e14b30897a1ebbacf9934eErik * @param model The model to fill in 103973714169d34a55fc01e14b30897a1ebbacf9934eErik * @param cursor An event cursor that used {@link #EVENT_PROJECTION} for the query 104073714169d34a55fc01e14b30897a1ebbacf9934eErik */ 104188c483a0189cbe7132330cb1b563f07326f3bcf9Erik public static void setModelFromCursor(CalendarEventModel model, Cursor cursor) { 104273714169d34a55fc01e14b30897a1ebbacf9934eErik if (model == null || cursor == null || cursor.getCount() != 1) { 104373714169d34a55fc01e14b30897a1ebbacf9934eErik Log.wtf(TAG, "Attempted to build non-existent model or from an incorrect query."); 104473714169d34a55fc01e14b30897a1ebbacf9934eErik return; 104573714169d34a55fc01e14b30897a1ebbacf9934eErik } 104673714169d34a55fc01e14b30897a1ebbacf9934eErik 104773714169d34a55fc01e14b30897a1ebbacf9934eErik model.clear(); 104873714169d34a55fc01e14b30897a1ebbacf9934eErik cursor.moveToFirst(); 104973714169d34a55fc01e14b30897a1ebbacf9934eErik 105073714169d34a55fc01e14b30897a1ebbacf9934eErik model.mId = cursor.getInt(EVENT_INDEX_ID); 105173714169d34a55fc01e14b30897a1ebbacf9934eErik model.mTitle = cursor.getString(EVENT_INDEX_TITLE); 105273714169d34a55fc01e14b30897a1ebbacf9934eErik model.mDescription = cursor.getString(EVENT_INDEX_DESCRIPTION); 105373714169d34a55fc01e14b30897a1ebbacf9934eErik model.mLocation = cursor.getString(EVENT_INDEX_EVENT_LOCATION); 105473714169d34a55fc01e14b30897a1ebbacf9934eErik model.mAllDay = cursor.getInt(EVENT_INDEX_ALL_DAY) != 0; 105573714169d34a55fc01e14b30897a1ebbacf9934eErik model.mHasAlarm = cursor.getInt(EVENT_INDEX_HAS_ALARM) != 0; 105673714169d34a55fc01e14b30897a1ebbacf9934eErik model.mCalendarId = cursor.getInt(EVENT_INDEX_CALENDAR_ID); 105773714169d34a55fc01e14b30897a1ebbacf9934eErik model.mStart = cursor.getLong(EVENT_INDEX_DTSTART); 105866a3c9ee58ae73a3a1ad3bdec79aa1566582465cRoboErik String tz = cursor.getString(EVENT_INDEX_TIMEZONE); 105966a3c9ee58ae73a3a1ad3bdec79aa1566582465cRoboErik if (!TextUtils.isEmpty(tz)) { 106066a3c9ee58ae73a3a1ad3bdec79aa1566582465cRoboErik model.mTimezone = tz; 106166a3c9ee58ae73a3a1ad3bdec79aa1566582465cRoboErik } 106273714169d34a55fc01e14b30897a1ebbacf9934eErik String rRule = cursor.getString(EVENT_INDEX_RRULE); 106373714169d34a55fc01e14b30897a1ebbacf9934eErik model.mRrule = rRule; 106473714169d34a55fc01e14b30897a1ebbacf9934eErik model.mSyncId = cursor.getString(EVENT_INDEX_SYNC_ID); 1065c6680c5819b6ad0d1914420902746fe5f86ba403RoboErik model.mAvailability = cursor.getInt(EVENT_INDEX_AVAILABILITY); 1066ced0eb67ef49c3203f72520c296e73d9cb4f654bMichael Chan int accessLevel = cursor.getInt(EVENT_INDEX_ACCESS_LEVEL); 106773714169d34a55fc01e14b30897a1ebbacf9934eErik model.mOwnerAccount = cursor.getString(EVENT_INDEX_OWNER_ACCOUNT); 106873714169d34a55fc01e14b30897a1ebbacf9934eErik model.mHasAttendeeData = cursor.getInt(EVENT_INDEX_HAS_ATTENDEE_DATA) != 0; 106928dab653f55caccbed32f700274f5274abaee089RoboErik model.mOriginalSyncId = cursor.getString(EVENT_INDEX_ORIGINAL_SYNC_ID); 107028dab653f55caccbed32f700274f5274abaee089RoboErik model.mOriginalId = cursor.getLong(EVENT_INDEX_ORIGINAL_ID); 1071352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan model.mOrganizer = cursor.getString(EVENT_INDEX_ORGANIZER); 1072352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan model.mIsOrganizer = model.mOwnerAccount.equalsIgnoreCase(model.mOrganizer); 1073352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan model.mGuestsCanModify = cursor.getInt(EVENT_INDEX_GUESTS_CAN_MODIFY) != 0; 107473714169d34a55fc01e14b30897a1ebbacf9934eErik 10754afba187f8990ae2b3afaf8fcdb6039f231f4914James Kung int rawEventColor; 10764afba187f8990ae2b3afaf8fcdb6039f231f4914James Kung if (cursor.isNull(EVENT_INDEX_EVENT_COLOR)) { 10774afba187f8990ae2b3afaf8fcdb6039f231f4914James Kung rawEventColor = cursor.getInt(EVENT_INDEX_CALENDAR_COLOR); 10784afba187f8990ae2b3afaf8fcdb6039f231f4914James Kung } else { 10794afba187f8990ae2b3afaf8fcdb6039f231f4914James Kung rawEventColor = cursor.getInt(EVENT_INDEX_EVENT_COLOR); 10804afba187f8990ae2b3afaf8fcdb6039f231f4914James Kung } 1081f56b14972130233fd7e7c64160084793a7a0a55bJames Kung model.setEventColor(Utils.getDisplayColorFromColor(rawEventColor)); 10824afba187f8990ae2b3afaf8fcdb6039f231f4914James Kung 1083ced0eb67ef49c3203f72520c296e73d9cb4f654bMichael Chan if (accessLevel > 0) { 1084eca82e9468c002590045338f98588ad3ce1a0bb3Erik // For now the array contains the values 0, 2, and 3. We subtract 1085eca82e9468c002590045338f98588ad3ce1a0bb3Erik // one to make it easier to handle in code as 0,1,2. 108673714169d34a55fc01e14b30897a1ebbacf9934eErik // Default (0), Private (1), Public (2) 1087ced0eb67ef49c3203f72520c296e73d9cb4f654bMichael Chan accessLevel--; 108873714169d34a55fc01e14b30897a1ebbacf9934eErik } 1089ced0eb67ef49c3203f72520c296e73d9cb4f654bMichael Chan model.mAccessLevel = accessLevel; 1090e3f85db16cfc50b25b97c64d8ab99820d579e5deMichael Chan model.mEventStatus = cursor.getInt(EVENT_INDEX_EVENT_STATUS); 109173714169d34a55fc01e14b30897a1ebbacf9934eErik 109273714169d34a55fc01e14b30897a1ebbacf9934eErik boolean hasRRule = !TextUtils.isEmpty(rRule); 109373714169d34a55fc01e14b30897a1ebbacf9934eErik 109473714169d34a55fc01e14b30897a1ebbacf9934eErik // We expect only one of these, so ignore the other 109573714169d34a55fc01e14b30897a1ebbacf9934eErik if (hasRRule) { 109673714169d34a55fc01e14b30897a1ebbacf9934eErik model.mDuration = cursor.getString(EVENT_INDEX_DURATION); 109773714169d34a55fc01e14b30897a1ebbacf9934eErik } else { 109873714169d34a55fc01e14b30897a1ebbacf9934eErik model.mEnd = cursor.getLong(EVENT_INDEX_DTEND); 109973714169d34a55fc01e14b30897a1ebbacf9934eErik } 1100352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan 1101352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan model.mModelUpdatedWithEventCursor = true; 1102352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan } 1103352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan 1104352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan /** 1105352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan * Uses a calendar cursor to fill in the given model This method assumes the 1106352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan * cursor used {@link #CALENDARS_PROJECTION} as it's query projection. It uses 1107352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan * the cursor to fill in the given model with all the information available. 1108352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan * 1109352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan * @param model The model to fill in 1110352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan * @param cursor An event cursor that used {@link #CALENDARS_PROJECTION} for the query 1111352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan * @return returns true if model was updated with the info in the cursor. 1112352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan */ 1113352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan public static boolean setModelFromCalendarCursor(CalendarEventModel model, Cursor cursor) { 1114352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan if (model == null || cursor == null) { 1115352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan Log.wtf(TAG, "Attempted to build non-existent model or from an incorrect query."); 1116352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan return false; 1117352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan } 1118352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan 1119352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan if (model.mCalendarId == -1) { 1120352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan return false; 1121352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan } 1122352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan 1123352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan if (!model.mModelUpdatedWithEventCursor) { 1124352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan Log.wtf(TAG, 1125352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan "Can't update model with a Calendar cursor until it has seen an Event cursor."); 1126352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan return false; 1127352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan } 1128352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan 1129352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan cursor.moveToPosition(-1); 1130352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan while (cursor.moveToNext()) { 1131ddbf80860566df8425f38a32190e9a64cad4e389Michael Chan if (model.mCalendarId != cursor.getInt(CALENDARS_INDEX_ID)) { 1132352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan continue; 1133352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan } 1134352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan 1135950e8a615f3dcca0b61b2eb8f8de72ecf6825d4bAndy McFadden model.mOrganizerCanRespond = cursor.getInt(CALENDARS_INDEX_CAN_ORGANIZER_RESPOND) != 0; 1136352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan 1137352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan model.mCalendarAccessLevel = cursor.getInt(CALENDARS_INDEX_ACCESS_LEVEL); 11389d5f351e45e20ff04da6f9118031cc2cc7419905Michael Chan model.mCalendarDisplayName = cursor.getString(CALENDARS_INDEX_DISPLAY_NAME); 1139f56b14972130233fd7e7c64160084793a7a0a55bJames Kung model.setCalendarColor(Utils.getDisplayColorFromColor( 1140f56b14972130233fd7e7c64160084793a7a0a55bJames Kung cursor.getInt(CALENDARS_INDEX_COLOR))); 11414afba187f8990ae2b3afaf8fcdb6039f231f4914James Kung 11424afba187f8990ae2b3afaf8fcdb6039f231f4914James Kung model.mCalendarAccountName = cursor.getString(CALENDARS_INDEX_ACCOUNT_NAME); 11434afba187f8990ae2b3afaf8fcdb6039f231f4914James Kung model.mCalendarAccountType = cursor.getString(CALENDARS_INDEX_ACCOUNT_TYPE); 1144352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan 11459645d9c3e9b2cfd35c5fc89f41caf6e99153ba10Andy McFadden model.mCalendarMaxReminders = cursor.getInt(CALENDARS_INDEX_MAX_REMINDERS); 11469645d9c3e9b2cfd35c5fc89f41caf6e99153ba10Andy McFadden model.mCalendarAllowedReminders = cursor.getString(CALENDARS_INDEX_ALLOWED_REMINDERS); 1147c6680c5819b6ad0d1914420902746fe5f86ba403RoboErik model.mCalendarAllowedAttendeeTypes = cursor 1148c6680c5819b6ad0d1914420902746fe5f86ba403RoboErik .getString(CALENDARS_INDEX_ALLOWED_ATTENDEE_TYPES); 1149c6680c5819b6ad0d1914420902746fe5f86ba403RoboErik model.mCalendarAllowedAvailability = cursor 1150c6680c5819b6ad0d1914420902746fe5f86ba403RoboErik .getString(CALENDARS_INDEX_ALLOWED_AVAILABILITY); 11519645d9c3e9b2cfd35c5fc89f41caf6e99153ba10Andy McFadden 1152352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan return true; 1153352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan } 1154352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan return false; 1155352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan } 1156352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan 1157352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan public static boolean canModifyEvent(CalendarEventModel model) { 1158352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan return canModifyCalendar(model) 1159352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan && (model.mIsOrganizer || model.mGuestsCanModify); 1160352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan } 1161352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan 1162352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan public static boolean canModifyCalendar(CalendarEventModel model) { 1163ef2add9145f058ea103ade2870a8ba899b9ed1c9RoboErik return model.mCalendarAccessLevel >= Calendars.CAL_ACCESS_CONTRIBUTOR 1164352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan || model.mCalendarId == -1; 1165352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan } 1166352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan 11675f62079ab9daefe95ede87631766e99e8b4a2220Erik public static boolean canAddReminders(CalendarEventModel model) { 1168ef2add9145f058ea103ade2870a8ba899b9ed1c9RoboErik return model.mCalendarAccessLevel >= Calendars.CAL_ACCESS_READ; 11695f62079ab9daefe95ede87631766e99e8b4a2220Erik } 11705f62079ab9daefe95ede87631766e99e8b4a2220Erik 1171352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan public static boolean canRespond(CalendarEventModel model) { 1172352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan // For non-organizers, write permission to the calendar is sufficient. 1173352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan // For organizers, the user needs a) write permission to the calendar 1174352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan // AND b) ownerCanRespond == true AND c) attendee data exist 1175352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan // (this means num of attendees > 1, the calendar owner's and others). 1176352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan // Note that mAttendeeList omits the organizer. 1177352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan 1178352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan // (there are more cases involved to be 100% accurate, such as 1179352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan // paying attention to whether or not an attendee status was 1180352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan // included in the feed, but we're currently omitting those corner cases 1181352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan // for simplicity). 1182352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan 1183352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan if (!canModifyCalendar(model)) { 1184352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan return false; 1185352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan } 1186352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan 1187352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan if (!model.mIsOrganizer) { 1188352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan return true; 1189352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan } 1190352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan 1191352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan if (!model.mOrganizerCanRespond) { 1192352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan return false; 1193352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan } 1194352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan 1195352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan // This means we don't have the attendees data so we can't send 1196352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan // the list of attendees and the status back to the server 1197352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan if (model.mHasAttendeeData && model.mAttendeesList.size() == 0) { 1198352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan return false; 1199352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan } 1200352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan 1201352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan return true; 120273714169d34a55fc01e14b30897a1ebbacf9934eErik } 120373714169d34a55fc01e14b30897a1ebbacf9934eErik 120473714169d34a55fc01e14b30897a1ebbacf9934eErik /** 1205152cb249897e8adbc674b0d991db1c47e597e0ffAndy McFadden * Goes through an event model and fills in content values for saving. This 1206eca82e9468c002590045338f98588ad3ce1a0bb3Erik * method will perform the initial collection of values from the model and 1207eca82e9468c002590045338f98588ad3ce1a0bb3Erik * put them into a set of ContentValues. It performs some basic work such as 1208eca82e9468c002590045338f98588ad3ce1a0bb3Erik * fixing the time on allDay events and choosing whether to use an rrule or 1209eca82e9468c002590045338f98588ad3ce1a0bb3Erik * dtend. 121073714169d34a55fc01e14b30897a1ebbacf9934eErik * 121173714169d34a55fc01e14b30897a1ebbacf9934eErik * @param model The complete model of the event you want to save 121273714169d34a55fc01e14b30897a1ebbacf9934eErik * @return values 121373714169d34a55fc01e14b30897a1ebbacf9934eErik */ 121473714169d34a55fc01e14b30897a1ebbacf9934eErik ContentValues getContentValuesFromModel(CalendarEventModel model) { 121573714169d34a55fc01e14b30897a1ebbacf9934eErik String title = model.mTitle; 121673714169d34a55fc01e14b30897a1ebbacf9934eErik boolean isAllDay = model.mAllDay; 121773714169d34a55fc01e14b30897a1ebbacf9934eErik String rrule = model.mRrule; 1218eca82e9468c002590045338f98588ad3ce1a0bb3Erik String timezone = model.mTimezone; 121973714169d34a55fc01e14b30897a1ebbacf9934eErik if (timezone == null) { 122073714169d34a55fc01e14b30897a1ebbacf9934eErik timezone = TimeZone.getDefault().getID(); 122173714169d34a55fc01e14b30897a1ebbacf9934eErik } 122273714169d34a55fc01e14b30897a1ebbacf9934eErik Time startTime = new Time(timezone); 122373714169d34a55fc01e14b30897a1ebbacf9934eErik Time endTime = new Time(timezone); 122473714169d34a55fc01e14b30897a1ebbacf9934eErik 122573714169d34a55fc01e14b30897a1ebbacf9934eErik startTime.set(model.mStart); 122673714169d34a55fc01e14b30897a1ebbacf9934eErik endTime.set(model.mEnd); 12276f3073ce88b9d120c86e9cd4d8be306add4363c9Sam Blitzstein offsetStartTimeIfNecessary(startTime, endTime, rrule, model); 122873714169d34a55fc01e14b30897a1ebbacf9934eErik 122973714169d34a55fc01e14b30897a1ebbacf9934eErik ContentValues values = new ContentValues(); 123073714169d34a55fc01e14b30897a1ebbacf9934eErik 123173714169d34a55fc01e14b30897a1ebbacf9934eErik long startMillis; 123273714169d34a55fc01e14b30897a1ebbacf9934eErik long endMillis; 123373714169d34a55fc01e14b30897a1ebbacf9934eErik long calendarId = model.mCalendarId; 123473714169d34a55fc01e14b30897a1ebbacf9934eErik if (isAllDay) { 123573714169d34a55fc01e14b30897a1ebbacf9934eErik // Reset start and end time, ensure at least 1 day duration, and set 123673714169d34a55fc01e14b30897a1ebbacf9934eErik // the timezone to UTC, as required for all-day events. 123773714169d34a55fc01e14b30897a1ebbacf9934eErik timezone = Time.TIMEZONE_UTC; 123873714169d34a55fc01e14b30897a1ebbacf9934eErik startTime.hour = 0; 123973714169d34a55fc01e14b30897a1ebbacf9934eErik startTime.minute = 0; 124073714169d34a55fc01e14b30897a1ebbacf9934eErik startTime.second = 0; 124173714169d34a55fc01e14b30897a1ebbacf9934eErik startTime.timezone = timezone; 124273714169d34a55fc01e14b30897a1ebbacf9934eErik startMillis = startTime.normalize(true); 124373714169d34a55fc01e14b30897a1ebbacf9934eErik 124473714169d34a55fc01e14b30897a1ebbacf9934eErik endTime.hour = 0; 124573714169d34a55fc01e14b30897a1ebbacf9934eErik endTime.minute = 0; 124673714169d34a55fc01e14b30897a1ebbacf9934eErik endTime.second = 0; 124773714169d34a55fc01e14b30897a1ebbacf9934eErik endTime.timezone = timezone; 124873714169d34a55fc01e14b30897a1ebbacf9934eErik endMillis = endTime.normalize(true); 124929190975b9238dd6841f822f82f2fb83b0557f36Daisuke Miyakawa if (endMillis < startMillis + DateUtils.DAY_IN_MILLIS) { 125029190975b9238dd6841f822f82f2fb83b0557f36Daisuke Miyakawa // EditEventView#fillModelFromUI() should treat this case, but we want to ensure 125129190975b9238dd6841f822f82f2fb83b0557f36Daisuke Miyakawa // the condition anyway. 125229190975b9238dd6841f822f82f2fb83b0557f36Daisuke Miyakawa endMillis = startMillis + DateUtils.DAY_IN_MILLIS; 125329190975b9238dd6841f822f82f2fb83b0557f36Daisuke Miyakawa } 125473714169d34a55fc01e14b30897a1ebbacf9934eErik } else { 125573714169d34a55fc01e14b30897a1ebbacf9934eErik startMillis = startTime.toMillis(true); 125673714169d34a55fc01e14b30897a1ebbacf9934eErik endMillis = endTime.toMillis(true); 125773714169d34a55fc01e14b30897a1ebbacf9934eErik } 125873714169d34a55fc01e14b30897a1ebbacf9934eErik 125973714169d34a55fc01e14b30897a1ebbacf9934eErik values.put(Events.CALENDAR_ID, calendarId); 126073714169d34a55fc01e14b30897a1ebbacf9934eErik values.put(Events.EVENT_TIMEZONE, timezone); 126173714169d34a55fc01e14b30897a1ebbacf9934eErik values.put(Events.TITLE, title); 126273714169d34a55fc01e14b30897a1ebbacf9934eErik values.put(Events.ALL_DAY, isAllDay ? 1 : 0); 126373714169d34a55fc01e14b30897a1ebbacf9934eErik values.put(Events.DTSTART, startMillis); 126473714169d34a55fc01e14b30897a1ebbacf9934eErik values.put(Events.RRULE, rrule); 126566b2067e0a389180f909eb0a528bea5f6becca23Michael Chan if (!TextUtils.isEmpty(rrule)) { 126673714169d34a55fc01e14b30897a1ebbacf9934eErik addRecurrenceRule(values, model); 126773714169d34a55fc01e14b30897a1ebbacf9934eErik } else { 1268eca82e9468c002590045338f98588ad3ce1a0bb3Erik values.put(Events.DURATION, (String) null); 126973714169d34a55fc01e14b30897a1ebbacf9934eErik values.put(Events.DTEND, endMillis); 127073714169d34a55fc01e14b30897a1ebbacf9934eErik } 1271bf7c96b7adbae17d822cb8dec45fdf253a16df51Michael Chan if (model.mDescription != null) { 1272bf7c96b7adbae17d822cb8dec45fdf253a16df51Michael Chan values.put(Events.DESCRIPTION, model.mDescription.trim()); 127311bd6bec90cba2e51ed4e0e0e4ed1fdacf4fea04RoboErik } else { 127411bd6bec90cba2e51ed4e0e0e4ed1fdacf4fea04RoboErik values.put(Events.DESCRIPTION, (String) null); 1275bf7c96b7adbae17d822cb8dec45fdf253a16df51Michael Chan } 1276bf7c96b7adbae17d822cb8dec45fdf253a16df51Michael Chan if (model.mLocation != null) { 1277bf7c96b7adbae17d822cb8dec45fdf253a16df51Michael Chan values.put(Events.EVENT_LOCATION, model.mLocation.trim()); 127811bd6bec90cba2e51ed4e0e0e4ed1fdacf4fea04RoboErik } else { 127911bd6bec90cba2e51ed4e0e0e4ed1fdacf4fea04RoboErik values.put(Events.EVENT_LOCATION, (String) null); 1280bf7c96b7adbae17d822cb8dec45fdf253a16df51Michael Chan } 1281c6680c5819b6ad0d1914420902746fe5f86ba403RoboErik values.put(Events.AVAILABILITY, model.mAvailability); 128288c483a0189cbe7132330cb1b563f07326f3bcf9Erik values.put(Events.HAS_ATTENDEE_DATA, model.mHasAttendeeData ? 1 : 0); 128373714169d34a55fc01e14b30897a1ebbacf9934eErik 1284ced0eb67ef49c3203f72520c296e73d9cb4f654bMichael Chan int accessLevel = model.mAccessLevel; 1285ced0eb67ef49c3203f72520c296e73d9cb4f654bMichael Chan if (accessLevel > 0) { 128673714169d34a55fc01e14b30897a1ebbacf9934eErik // For now the array contains the values 0, 2, and 3. We add one to match. 128773714169d34a55fc01e14b30897a1ebbacf9934eErik // Default (0), Private (2), Public (3) 1288ced0eb67ef49c3203f72520c296e73d9cb4f654bMichael Chan accessLevel++; 128973714169d34a55fc01e14b30897a1ebbacf9934eErik } 1290ced0eb67ef49c3203f72520c296e73d9cb4f654bMichael Chan values.put(Events.ACCESS_LEVEL, accessLevel); 1291e3f85db16cfc50b25b97c64d8ab99820d579e5deMichael Chan values.put(Events.STATUS, model.mEventStatus); 1292f56b14972130233fd7e7c64160084793a7a0a55bJames Kung if (model.isEventColorInitialized()) { 1293f56b14972130233fd7e7c64160084793a7a0a55bJames Kung if (model.getEventColor() == model.getCalendarColor()) { 1294f56b14972130233fd7e7c64160084793a7a0a55bJames Kung values.put(Events.EVENT_COLOR_KEY, NO_EVENT_COLOR); 1295f56b14972130233fd7e7c64160084793a7a0a55bJames Kung } else { 1296f56b14972130233fd7e7c64160084793a7a0a55bJames Kung values.put(Events.EVENT_COLOR_KEY, model.getEventColorKey()); 1297f56b14972130233fd7e7c64160084793a7a0a55bJames Kung } 12984afba187f8990ae2b3afaf8fcdb6039f231f4914James Kung } 129973714169d34a55fc01e14b30897a1ebbacf9934eErik return values; 130073714169d34a55fc01e14b30897a1ebbacf9934eErik } 130173714169d34a55fc01e14b30897a1ebbacf9934eErik 130273714169d34a55fc01e14b30897a1ebbacf9934eErik /** 13036f3073ce88b9d120c86e9cd4d8be306add4363c9Sam Blitzstein * If the recurrence rule is such that the event start date doesn't actually fall in one of the 13046f3073ce88b9d120c86e9cd4d8be306add4363c9Sam Blitzstein * recurrences, then push the start date up to the first actual instance of the event. 13056f3073ce88b9d120c86e9cd4d8be306add4363c9Sam Blitzstein */ 13066f3073ce88b9d120c86e9cd4d8be306add4363c9Sam Blitzstein private void offsetStartTimeIfNecessary(Time startTime, Time endTime, String rrule, 13076f3073ce88b9d120c86e9cd4d8be306add4363c9Sam Blitzstein CalendarEventModel model) { 13086f3073ce88b9d120c86e9cd4d8be306add4363c9Sam Blitzstein if (rrule == null || rrule.isEmpty()) { 13096f3073ce88b9d120c86e9cd4d8be306add4363c9Sam Blitzstein // No need to waste any time with the parsing if the rule is empty. 13106f3073ce88b9d120c86e9cd4d8be306add4363c9Sam Blitzstein return; 13116f3073ce88b9d120c86e9cd4d8be306add4363c9Sam Blitzstein } 13126f3073ce88b9d120c86e9cd4d8be306add4363c9Sam Blitzstein 13136f3073ce88b9d120c86e9cd4d8be306add4363c9Sam Blitzstein mEventRecurrence.parse(rrule); 13146f3073ce88b9d120c86e9cd4d8be306add4363c9Sam Blitzstein // Check if we meet the specific special case. It has to: 13156f3073ce88b9d120c86e9cd4d8be306add4363c9Sam Blitzstein // * be weekly 13166f3073ce88b9d120c86e9cd4d8be306add4363c9Sam Blitzstein // * not recur on the same day of the week that the startTime falls on 13176f3073ce88b9d120c86e9cd4d8be306add4363c9Sam Blitzstein // In this case, we'll need to push the start time to fall on the first day of the week 13186f3073ce88b9d120c86e9cd4d8be306add4363c9Sam Blitzstein // that is part of the recurrence. 13196f3073ce88b9d120c86e9cd4d8be306add4363c9Sam Blitzstein if (mEventRecurrence.freq != EventRecurrence.WEEKLY) { 13206f3073ce88b9d120c86e9cd4d8be306add4363c9Sam Blitzstein // Not weekly so nothing to worry about. 13216f3073ce88b9d120c86e9cd4d8be306add4363c9Sam Blitzstein return; 13226f3073ce88b9d120c86e9cd4d8be306add4363c9Sam Blitzstein } 1323fbfbc73dd74c0855123b50b4fa1adb6bbb86a512Paul Sliwowski if (mEventRecurrence.byday == null || 1324fbfbc73dd74c0855123b50b4fa1adb6bbb86a512Paul Sliwowski mEventRecurrence.byday.length > mEventRecurrence.bydayCount) { 13256f3073ce88b9d120c86e9cd4d8be306add4363c9Sam Blitzstein // This shouldn't happen, but just in case something is weird about the recurrence. 13266f3073ce88b9d120c86e9cd4d8be306add4363c9Sam Blitzstein return; 13276f3073ce88b9d120c86e9cd4d8be306add4363c9Sam Blitzstein } 13286f3073ce88b9d120c86e9cd4d8be306add4363c9Sam Blitzstein 13296f3073ce88b9d120c86e9cd4d8be306add4363c9Sam Blitzstein // Start to figure out what the nearest weekday is. 13306f3073ce88b9d120c86e9cd4d8be306add4363c9Sam Blitzstein int closestWeekday = Integer.MAX_VALUE; 13316f3073ce88b9d120c86e9cd4d8be306add4363c9Sam Blitzstein int weekstart = EventRecurrence.day2TimeDay(mEventRecurrence.wkst); 13326f3073ce88b9d120c86e9cd4d8be306add4363c9Sam Blitzstein int startDay = startTime.weekDay; 13336f3073ce88b9d120c86e9cd4d8be306add4363c9Sam Blitzstein for (int i = 0; i < mEventRecurrence.bydayCount; i++) { 13346f3073ce88b9d120c86e9cd4d8be306add4363c9Sam Blitzstein int day = EventRecurrence.day2TimeDay(mEventRecurrence.byday[i]); 13356f3073ce88b9d120c86e9cd4d8be306add4363c9Sam Blitzstein if (day == startDay) { 13366f3073ce88b9d120c86e9cd4d8be306add4363c9Sam Blitzstein // Our start day is one of the recurring days, so we're good. 13376f3073ce88b9d120c86e9cd4d8be306add4363c9Sam Blitzstein return; 13386f3073ce88b9d120c86e9cd4d8be306add4363c9Sam Blitzstein } 13396f3073ce88b9d120c86e9cd4d8be306add4363c9Sam Blitzstein 13406f3073ce88b9d120c86e9cd4d8be306add4363c9Sam Blitzstein if (day < weekstart) { 13416f3073ce88b9d120c86e9cd4d8be306add4363c9Sam Blitzstein // Let's not make any assumptions about what weekstart can be. 13426f3073ce88b9d120c86e9cd4d8be306add4363c9Sam Blitzstein day += 7; 13436f3073ce88b9d120c86e9cd4d8be306add4363c9Sam Blitzstein } 13446f3073ce88b9d120c86e9cd4d8be306add4363c9Sam Blitzstein // We either want the earliest day that is later in the week than startDay ... 13456f3073ce88b9d120c86e9cd4d8be306add4363c9Sam Blitzstein if (day > startDay && (day < closestWeekday || closestWeekday < startDay)) { 13466f3073ce88b9d120c86e9cd4d8be306add4363c9Sam Blitzstein closestWeekday = day; 13476f3073ce88b9d120c86e9cd4d8be306add4363c9Sam Blitzstein } 13486f3073ce88b9d120c86e9cd4d8be306add4363c9Sam Blitzstein // ... or if there are no days later than startDay, we want the earliest day that is 13496f3073ce88b9d120c86e9cd4d8be306add4363c9Sam Blitzstein // earlier in the week than startDay. 13506f3073ce88b9d120c86e9cd4d8be306add4363c9Sam Blitzstein if (closestWeekday == Integer.MAX_VALUE || closestWeekday < startDay) { 13516f3073ce88b9d120c86e9cd4d8be306add4363c9Sam Blitzstein // We haven't found a day that's later in the week than startDay yet. 13526f3073ce88b9d120c86e9cd4d8be306add4363c9Sam Blitzstein if (day < closestWeekday) { 13536f3073ce88b9d120c86e9cd4d8be306add4363c9Sam Blitzstein closestWeekday = day; 13546f3073ce88b9d120c86e9cd4d8be306add4363c9Sam Blitzstein } 13556f3073ce88b9d120c86e9cd4d8be306add4363c9Sam Blitzstein } 13566f3073ce88b9d120c86e9cd4d8be306add4363c9Sam Blitzstein } 13576f3073ce88b9d120c86e9cd4d8be306add4363c9Sam Blitzstein 13586f3073ce88b9d120c86e9cd4d8be306add4363c9Sam Blitzstein // We're here, so unfortunately our event's start day is not included in the days of 13596f3073ce88b9d120c86e9cd4d8be306add4363c9Sam Blitzstein // the week of the recurrence. To save this event correctly we'll need to push the start 13606f3073ce88b9d120c86e9cd4d8be306add4363c9Sam Blitzstein // date to the closest weekday that *is* part of the recurrence. 13616f3073ce88b9d120c86e9cd4d8be306add4363c9Sam Blitzstein if (closestWeekday < startDay) { 13626f3073ce88b9d120c86e9cd4d8be306add4363c9Sam Blitzstein closestWeekday += 7; 13636f3073ce88b9d120c86e9cd4d8be306add4363c9Sam Blitzstein } 13646f3073ce88b9d120c86e9cd4d8be306add4363c9Sam Blitzstein int daysOffset = closestWeekday - startDay; 13656f3073ce88b9d120c86e9cd4d8be306add4363c9Sam Blitzstein startTime.monthDay += daysOffset; 13666f3073ce88b9d120c86e9cd4d8be306add4363c9Sam Blitzstein endTime.monthDay += daysOffset; 13676f3073ce88b9d120c86e9cd4d8be306add4363c9Sam Blitzstein long newStartTime = startTime.normalize(true); 13686f3073ce88b9d120c86e9cd4d8be306add4363c9Sam Blitzstein long newEndTime = endTime.normalize(true); 13696f3073ce88b9d120c86e9cd4d8be306add4363c9Sam Blitzstein 13706f3073ce88b9d120c86e9cd4d8be306add4363c9Sam Blitzstein // Later we'll actually be using the values from the model rather than the startTime 13716f3073ce88b9d120c86e9cd4d8be306add4363c9Sam Blitzstein // and endTime themselves, so we need to make these changes to the model as well. 13726f3073ce88b9d120c86e9cd4d8be306add4363c9Sam Blitzstein model.mStart = newStartTime; 13736f3073ce88b9d120c86e9cd4d8be306add4363c9Sam Blitzstein model.mEnd = newEndTime; 13746f3073ce88b9d120c86e9cd4d8be306add4363c9Sam Blitzstein } 13756f3073ce88b9d120c86e9cd4d8be306add4363c9Sam Blitzstein 13766f3073ce88b9d120c86e9cd4d8be306add4363c9Sam Blitzstein /** 137773714169d34a55fc01e14b30897a1ebbacf9934eErik * Takes an e-mail address and returns the domain (everything after the last @) 137873714169d34a55fc01e14b30897a1ebbacf9934eErik */ 137973714169d34a55fc01e14b30897a1ebbacf9934eErik public static String extractDomain(String email) { 138073714169d34a55fc01e14b30897a1ebbacf9934eErik int separator = email.lastIndexOf('@'); 138173714169d34a55fc01e14b30897a1ebbacf9934eErik if (separator != -1 && ++separator < email.length()) { 138273714169d34a55fc01e14b30897a1ebbacf9934eErik return email.substring(separator); 138373714169d34a55fc01e14b30897a1ebbacf9934eErik } 138473714169d34a55fc01e14b30897a1ebbacf9934eErik return null; 138573714169d34a55fc01e14b30897a1ebbacf9934eErik } 138679f228124de7d98146ca526d743436f6419e2365Erik 138779f228124de7d98146ca526d743436f6419e2365Erik public interface EditDoneRunnable extends Runnable { 138879f228124de7d98146ca526d743436f6419e2365Erik public void setDoneCode(int code); 138979f228124de7d98146ca526d743436f6419e2365Erik } 139073714169d34a55fc01e14b30897a1ebbacf9934eErik} 1391