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