EditEventHelper.java revision 11bd6bec90cba2e51ed4e0e0e4ed1fdacf4fea04
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
19fbce65e53c7a111955f638db5bf8bee35381e5b7Erikimport com.android.calendar.AsyncQueryService;
20deced8999da73effe4133e2317c262fea7910090Michael Chanimport com.android.calendar.CalendarController;
21fbce65e53c7a111955f638db5bf8bee35381e5b7Erikimport com.android.calendar.CalendarEventModel;
22d845fbe558d5229102b58cce70a1b29fe6cb6967Erikimport com.android.calendar.CalendarEventModel.Attendee;
23fbce65e53c7a111955f638db5bf8bee35381e5b7Erikimport com.android.calendar.R;
24fbce65e53c7a111955f638db5bf8bee35381e5b7Erikimport com.android.calendar.Utils;
2573714169d34a55fc01e14b30897a1ebbacf9934eErikimport com.android.common.Rfc822Validator;
2673714169d34a55fc01e14b30897a1ebbacf9934eErik
27eca82e9468c002590045338f98588ad3ce1a0bb3Erikimport android.content.ContentProviderOperation;
28eca82e9468c002590045338f98588ad3ce1a0bb3Erikimport android.content.ContentUris;
29eca82e9468c002590045338f98588ad3ce1a0bb3Erikimport android.content.ContentValues;
30dd95df57c8c5a58a85c4c0effad5652dec14f621Erikimport android.content.Context;
3173714169d34a55fc01e14b30897a1ebbacf9934eErikimport android.content.res.Resources;
3273714169d34a55fc01e14b30897a1ebbacf9934eErikimport android.database.Cursor;
33d845fbe558d5229102b58cce70a1b29fe6cb6967Erikimport android.graphics.drawable.Drawable;
3473714169d34a55fc01e14b30897a1ebbacf9934eErikimport android.net.Uri;
3573714169d34a55fc01e14b30897a1ebbacf9934eErikimport android.pim.EventRecurrence;
3673714169d34a55fc01e14b30897a1ebbacf9934eErikimport android.provider.Calendar.Attendees;
37eca82e9468c002590045338f98588ad3ce1a0bb3Erikimport android.provider.Calendar.Calendars;
3873714169d34a55fc01e14b30897a1ebbacf9934eErikimport android.provider.Calendar.Events;
3973714169d34a55fc01e14b30897a1ebbacf9934eErikimport android.provider.Calendar.Reminders;
4073714169d34a55fc01e14b30897a1ebbacf9934eErikimport android.text.TextUtils;
4173714169d34a55fc01e14b30897a1ebbacf9934eErikimport android.text.format.DateUtils;
4273714169d34a55fc01e14b30897a1ebbacf9934eErikimport android.text.format.Time;
4373714169d34a55fc01e14b30897a1ebbacf9934eErikimport android.text.util.Rfc822Token;
4473714169d34a55fc01e14b30897a1ebbacf9934eErikimport android.text.util.Rfc822Tokenizer;
4573714169d34a55fc01e14b30897a1ebbacf9934eErikimport android.util.Log;
46352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chanimport android.view.View;
47d845fbe558d5229102b58cce70a1b29fe6cb6967Erikimport android.widget.ImageView;
48d845fbe558d5229102b58cce70a1b29fe6cb6967Erikimport android.widget.QuickContactBadge;
4973714169d34a55fc01e14b30897a1ebbacf9934eErik
50eca82e9468c002590045338f98588ad3ce1a0bb3Erikimport java.util.ArrayList;
51d845fbe558d5229102b58cce70a1b29fe6cb6967Erikimport java.util.HashMap;
52eca82e9468c002590045338f98588ad3ce1a0bb3Erikimport java.util.Iterator;
53eca82e9468c002590045338f98588ad3ce1a0bb3Erikimport java.util.LinkedHashSet;
54d845fbe558d5229102b58cce70a1b29fe6cb6967Erikimport java.util.LinkedList;
55eca82e9468c002590045338f98588ad3ce1a0bb3Erikimport java.util.TimeZone;
5673714169d34a55fc01e14b30897a1ebbacf9934eErik
5773714169d34a55fc01e14b30897a1ebbacf9934eErikpublic class EditEventHelper {
5873714169d34a55fc01e14b30897a1ebbacf9934eErik    private static final String TAG = "EditEventHelper";
59eca82e9468c002590045338f98588ad3ce1a0bb3Erik
6073714169d34a55fc01e14b30897a1ebbacf9934eErik    private static final boolean DEBUG = false;
6173714169d34a55fc01e14b30897a1ebbacf9934eErik
62eca82e9468c002590045338f98588ad3ce1a0bb3Erik    public static final int MAX_REMINDERS = 5;
63eca82e9468c002590045338f98588ad3ce1a0bb3Erik
64fbce65e53c7a111955f638db5bf8bee35381e5b7Erik    public static final String[] EVENT_PROJECTION = new String[] {
6573714169d34a55fc01e14b30897a1ebbacf9934eErik            Events._ID, // 0
6673714169d34a55fc01e14b30897a1ebbacf9934eErik            Events.TITLE, // 1
6773714169d34a55fc01e14b30897a1ebbacf9934eErik            Events.DESCRIPTION, // 2
6873714169d34a55fc01e14b30897a1ebbacf9934eErik            Events.EVENT_LOCATION, // 3
6973714169d34a55fc01e14b30897a1ebbacf9934eErik            Events.ALL_DAY, // 4
7073714169d34a55fc01e14b30897a1ebbacf9934eErik            Events.HAS_ALARM, // 5
7173714169d34a55fc01e14b30897a1ebbacf9934eErik            Events.CALENDAR_ID, // 6
7273714169d34a55fc01e14b30897a1ebbacf9934eErik            Events.DTSTART, // 7
7373714169d34a55fc01e14b30897a1ebbacf9934eErik            Events.DTEND, // 8
7473714169d34a55fc01e14b30897a1ebbacf9934eErik            Events.DURATION, // 9
7573714169d34a55fc01e14b30897a1ebbacf9934eErik            Events.EVENT_TIMEZONE, // 10
7673714169d34a55fc01e14b30897a1ebbacf9934eErik            Events.RRULE, // 11
7773714169d34a55fc01e14b30897a1ebbacf9934eErik            Events._SYNC_ID, // 12
7873714169d34a55fc01e14b30897a1ebbacf9934eErik            Events.TRANSPARENCY, // 13
7973714169d34a55fc01e14b30897a1ebbacf9934eErik            Events.VISIBILITY, // 14
8073714169d34a55fc01e14b30897a1ebbacf9934eErik            Events.OWNER_ACCOUNT, // 15
8173714169d34a55fc01e14b30897a1ebbacf9934eErik            Events.HAS_ATTENDEE_DATA, // 16
82eca82e9468c002590045338f98588ad3ce1a0bb3Erik            Events.ORIGINAL_EVENT, // 17
83352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan            Events.ORGANIZER, // 18
84352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan            Events.GUESTS_CAN_MODIFY, // 19
8573714169d34a55fc01e14b30897a1ebbacf9934eErik    };
8673714169d34a55fc01e14b30897a1ebbacf9934eErik    protected static final int EVENT_INDEX_ID = 0;
8773714169d34a55fc01e14b30897a1ebbacf9934eErik    protected static final int EVENT_INDEX_TITLE = 1;
8873714169d34a55fc01e14b30897a1ebbacf9934eErik    protected static final int EVENT_INDEX_DESCRIPTION = 2;
8973714169d34a55fc01e14b30897a1ebbacf9934eErik    protected static final int EVENT_INDEX_EVENT_LOCATION = 3;
9073714169d34a55fc01e14b30897a1ebbacf9934eErik    protected static final int EVENT_INDEX_ALL_DAY = 4;
9173714169d34a55fc01e14b30897a1ebbacf9934eErik    protected static final int EVENT_INDEX_HAS_ALARM = 5;
9273714169d34a55fc01e14b30897a1ebbacf9934eErik    protected static final int EVENT_INDEX_CALENDAR_ID = 6;
9373714169d34a55fc01e14b30897a1ebbacf9934eErik    protected static final int EVENT_INDEX_DTSTART = 7;
9473714169d34a55fc01e14b30897a1ebbacf9934eErik    protected static final int EVENT_INDEX_DTEND = 8;
9573714169d34a55fc01e14b30897a1ebbacf9934eErik    protected static final int EVENT_INDEX_DURATION = 9;
9673714169d34a55fc01e14b30897a1ebbacf9934eErik    protected static final int EVENT_INDEX_TIMEZONE = 10;
9773714169d34a55fc01e14b30897a1ebbacf9934eErik    protected static final int EVENT_INDEX_RRULE = 11;
9873714169d34a55fc01e14b30897a1ebbacf9934eErik    protected static final int EVENT_INDEX_SYNC_ID = 12;
9973714169d34a55fc01e14b30897a1ebbacf9934eErik    protected static final int EVENT_INDEX_TRANSPARENCY = 13;
10073714169d34a55fc01e14b30897a1ebbacf9934eErik    protected static final int EVENT_INDEX_VISIBILITY = 14;
10173714169d34a55fc01e14b30897a1ebbacf9934eErik    protected static final int EVENT_INDEX_OWNER_ACCOUNT = 15;
10273714169d34a55fc01e14b30897a1ebbacf9934eErik    protected static final int EVENT_INDEX_HAS_ATTENDEE_DATA = 16;
103eca82e9468c002590045338f98588ad3ce1a0bb3Erik    protected static final int EVENT_INDEX_ORIGINAL_EVENT = 17;
104352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan    protected static final int EVENT_INDEX_ORGANIZER = 18;
105352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan    protected static final int EVENT_INDEX_GUESTS_CAN_MODIFY = 19;
10673714169d34a55fc01e14b30897a1ebbacf9934eErik
107eca82e9468c002590045338f98588ad3ce1a0bb3Erik    public static final String[] REMINDERS_PROJECTION = new String[] {
10873714169d34a55fc01e14b30897a1ebbacf9934eErik            Reminders._ID, // 0
10973714169d34a55fc01e14b30897a1ebbacf9934eErik            Reminders.MINUTES, // 1
110eca82e9468c002590045338f98588ad3ce1a0bb3Erik            Reminders.METHOD, // 2
11173714169d34a55fc01e14b30897a1ebbacf9934eErik    };
112eca82e9468c002590045338f98588ad3ce1a0bb3Erik    public static final int REMINDERS_INDEX_MINUTES = 1;
113eca82e9468c002590045338f98588ad3ce1a0bb3Erik    public static final int REMINDERS_INDEX_METHOD = 2;
114eca82e9468c002590045338f98588ad3ce1a0bb3Erik    public static final String REMINDERS_WHERE = Reminders.EVENT_ID + "=? AND (" + Reminders.METHOD
115eca82e9468c002590045338f98588ad3ce1a0bb3Erik            + "=?" + " OR " + Reminders.METHOD + "=?" + ")";
116eca82e9468c002590045338f98588ad3ce1a0bb3Erik
11773714169d34a55fc01e14b30897a1ebbacf9934eErik    // Visible for testing
118eca82e9468c002590045338f98588ad3ce1a0bb3Erik    static final String ATTENDEES_DELETE_PREFIX = Attendees.EVENT_ID + "=? AND "
119eca82e9468c002590045338f98588ad3ce1a0bb3Erik            + Attendees.ATTENDEE_EMAIL + " IN (";
12073714169d34a55fc01e14b30897a1ebbacf9934eErik
12173714169d34a55fc01e14b30897a1ebbacf9934eErik    public static final int DOES_NOT_REPEAT = 0;
12273714169d34a55fc01e14b30897a1ebbacf9934eErik    public static final int REPEATS_DAILY = 1;
12373714169d34a55fc01e14b30897a1ebbacf9934eErik    public static final int REPEATS_EVERY_WEEKDAY = 2;
12473714169d34a55fc01e14b30897a1ebbacf9934eErik    public static final int REPEATS_WEEKLY_ON_DAY = 3;
12573714169d34a55fc01e14b30897a1ebbacf9934eErik    public static final int REPEATS_MONTHLY_ON_DAY_COUNT = 4;
12673714169d34a55fc01e14b30897a1ebbacf9934eErik    public static final int REPEATS_MONTHLY_ON_DAY = 5;
12773714169d34a55fc01e14b30897a1ebbacf9934eErik    public static final int REPEATS_YEARLY = 6;
12873714169d34a55fc01e14b30897a1ebbacf9934eErik    public static final int REPEATS_CUSTOM = 7;
12973714169d34a55fc01e14b30897a1ebbacf9934eErik
13073714169d34a55fc01e14b30897a1ebbacf9934eErik    protected static final int MODIFY_UNINITIALIZED = 0;
13173714169d34a55fc01e14b30897a1ebbacf9934eErik    protected static final int MODIFY_SELECTED = 1;
1328d69cd014d912e38d2ab37764aaf4aa7700c078bErik    protected static final int MODIFY_ALL_FOLLOWING = 2;
1338d69cd014d912e38d2ab37764aaf4aa7700c078bErik    protected static final int MODIFY_ALL = 3;
13473714169d34a55fc01e14b30897a1ebbacf9934eErik
13573714169d34a55fc01e14b30897a1ebbacf9934eErik    protected static final int DAY_IN_SECONDS = 24 * 60 * 60;
13673714169d34a55fc01e14b30897a1ebbacf9934eErik
137dd95df57c8c5a58a85c4c0effad5652dec14f621Erik    private Context mContext;
13837960c091b5912dd45beb561a85a9064ea42e616Erik    private AsyncQueryService mService;
139eca82e9468c002590045338f98588ad3ce1a0bb3Erik
140eca82e9468c002590045338f98588ad3ce1a0bb3Erik    // public int mModification;
14173714169d34a55fc01e14b30897a1ebbacf9934eErik    private Rfc822Validator mEmailValidator;
142eca82e9468c002590045338f98588ad3ce1a0bb3Erik
143eca82e9468c002590045338f98588ad3ce1a0bb3Erik    // This allows us to flag the event if something is wrong with it, right now
144eca82e9468c002590045338f98588ad3ce1a0bb3Erik    // if an uri is provided for an event that doesn't exist in the db.
14573714169d34a55fc01e14b30897a1ebbacf9934eErik    protected boolean mEventOk = true;
14673714169d34a55fc01e14b30897a1ebbacf9934eErik
147d845fbe558d5229102b58cce70a1b29fe6cb6967Erik    public static final int ATTENDEE_ID_NONE = -1;
148d845fbe558d5229102b58cce70a1b29fe6cb6967Erik    public static final int[] ATTENDEE_VALUES = {
149deced8999da73effe4133e2317c262fea7910090Michael Chan            CalendarController.ATTENDEE_NO_RESPONSE,
150d845fbe558d5229102b58cce70a1b29fe6cb6967Erik            Attendees.ATTENDEE_STATUS_ACCEPTED,
151d845fbe558d5229102b58cce70a1b29fe6cb6967Erik            Attendees.ATTENDEE_STATUS_TENTATIVE,
152d845fbe558d5229102b58cce70a1b29fe6cb6967Erik            Attendees.ATTENDEE_STATUS_DECLINED,
153d845fbe558d5229102b58cce70a1b29fe6cb6967Erik    };
154d845fbe558d5229102b58cce70a1b29fe6cb6967Erik
155eca82e9468c002590045338f98588ad3ce1a0bb3Erik    /**
156eca82e9468c002590045338f98588ad3ce1a0bb3Erik     * This is the symbolic name for the key used to pass in the boolean for
157eca82e9468c002590045338f98588ad3ce1a0bb3Erik     * creating all-day events that is part of the extra data of the intent.
158eca82e9468c002590045338f98588ad3ce1a0bb3Erik     * This is used only for creating new events and is set to true if the
159eca82e9468c002590045338f98588ad3ce1a0bb3Erik     * default for the new event should be an all-day event.
160eca82e9468c002590045338f98588ad3ce1a0bb3Erik     */
161eca82e9468c002590045338f98588ad3ce1a0bb3Erik    public static final String EVENT_ALL_DAY = "allDay";
162eca82e9468c002590045338f98588ad3ce1a0bb3Erik
163eca82e9468c002590045338f98588ad3ce1a0bb3Erik    static final String[] CALENDARS_PROJECTION = new String[] {
164eca82e9468c002590045338f98588ad3ce1a0bb3Erik            Calendars._ID, // 0
165eca82e9468c002590045338f98588ad3ce1a0bb3Erik            Calendars.DISPLAY_NAME, // 1
166eca82e9468c002590045338f98588ad3ce1a0bb3Erik            Calendars.OWNER_ACCOUNT, // 2
167eca82e9468c002590045338f98588ad3ce1a0bb3Erik            Calendars.COLOR, // 3
168352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan            Calendars.ORGANIZER_CAN_RESPOND, // 4
169352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan            Calendars.ACCESS_LEVEL, // 5
170352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan            Calendars.SELECTED, // 6
171eca82e9468c002590045338f98588ad3ce1a0bb3Erik    };
172eca82e9468c002590045338f98588ad3ce1a0bb3Erik    static final int CALENDARS_INDEX_ID = 0;
173eca82e9468c002590045338f98588ad3ce1a0bb3Erik    static final int CALENDARS_INDEX_DISPLAY_NAME = 1;
174eca82e9468c002590045338f98588ad3ce1a0bb3Erik    static final int CALENDARS_INDEX_OWNER_ACCOUNT = 2;
175eca82e9468c002590045338f98588ad3ce1a0bb3Erik    static final int CALENDARS_INDEX_COLOR = 3;
176352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan    static final int CALENDARS_INDEX_ORGANIZER_CAN_RESPOND = 4;
177352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan    static final int CALENDARS_INDEX_ACCESS_LEVEL = 5;
178352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan    static final int CALENDARS_INDEX_SELECTED = 6;
179eca82e9468c002590045338f98588ad3ce1a0bb3Erik
180eca82e9468c002590045338f98588ad3ce1a0bb3Erik    static final String CALENDARS_WHERE_WRITEABLE_VISIBLE = Calendars.ACCESS_LEVEL + ">="
181eca82e9468c002590045338f98588ad3ce1a0bb3Erik            + Calendars.CONTRIBUTOR_ACCESS + " AND " + Calendars.SELECTED + "=1";
182eca82e9468c002590045338f98588ad3ce1a0bb3Erik
183352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan    static final String CALENDARS_WHERE = Calendars._ID + "=?";
184352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan
185eca82e9468c002590045338f98588ad3ce1a0bb3Erik    static final String[] ATTENDEES_PROJECTION = new String[] {
186d845fbe558d5229102b58cce70a1b29fe6cb6967Erik            Attendees._ID, // 0
187d845fbe558d5229102b58cce70a1b29fe6cb6967Erik            Attendees.ATTENDEE_NAME, // 1
188d845fbe558d5229102b58cce70a1b29fe6cb6967Erik            Attendees.ATTENDEE_EMAIL, // 2
189d845fbe558d5229102b58cce70a1b29fe6cb6967Erik            Attendees.ATTENDEE_RELATIONSHIP, // 3
190d845fbe558d5229102b58cce70a1b29fe6cb6967Erik            Attendees.ATTENDEE_STATUS, // 4
191eca82e9468c002590045338f98588ad3ce1a0bb3Erik    };
192d845fbe558d5229102b58cce70a1b29fe6cb6967Erik    static final int ATTENDEES_INDEX_ID = 0;
193d845fbe558d5229102b58cce70a1b29fe6cb6967Erik    static final int ATTENDEES_INDEX_NAME = 1;
194d845fbe558d5229102b58cce70a1b29fe6cb6967Erik    static final int ATTENDEES_INDEX_EMAIL = 2;
195d845fbe558d5229102b58cce70a1b29fe6cb6967Erik    static final int ATTENDEES_INDEX_RELATIONSHIP = 3;
196d845fbe558d5229102b58cce70a1b29fe6cb6967Erik    static final int ATTENDEES_INDEX_STATUS = 4;
197eca82e9468c002590045338f98588ad3ce1a0bb3Erik    static final String ATTENDEES_WHERE_NOT_ORGANIZER = Attendees.EVENT_ID + "=? AND "
198eca82e9468c002590045338f98588ad3ce1a0bb3Erik            + Attendees.ATTENDEE_RELATIONSHIP + "<>" + Attendees.RELATIONSHIP_ORGANIZER;
199d845fbe558d5229102b58cce70a1b29fe6cb6967Erik    static final String ATTENDEES_WHERE = Attendees.EVENT_ID + "=?";
200d845fbe558d5229102b58cce70a1b29fe6cb6967Erik
201d845fbe558d5229102b58cce70a1b29fe6cb6967Erik    public static class ContactViewHolder {
202d845fbe558d5229102b58cce70a1b29fe6cb6967Erik        QuickContactBadge badge;
203d845fbe558d5229102b58cce70a1b29fe6cb6967Erik        ImageView presence;
204d845fbe558d5229102b58cce70a1b29fe6cb6967Erik        int updateCounts;
205d845fbe558d5229102b58cce70a1b29fe6cb6967Erik    }
206d845fbe558d5229102b58cce70a1b29fe6cb6967Erik
207d845fbe558d5229102b58cce70a1b29fe6cb6967Erik    public static class AttendeeItem {
208d845fbe558d5229102b58cce70a1b29fe6cb6967Erik        public boolean mRemoved;
209d845fbe558d5229102b58cce70a1b29fe6cb6967Erik        public Attendee mAttendee;
210d845fbe558d5229102b58cce70a1b29fe6cb6967Erik        public Drawable mBadge;
211d845fbe558d5229102b58cce70a1b29fe6cb6967Erik        public int mPresence;
212d845fbe558d5229102b58cce70a1b29fe6cb6967Erik        public int mUpdateCounts;
213bdcb9fcc732d52708fafb068f535a2c93ff2356fDaisuke Miyakawa        public View mView;
214bdcb9fcc732d52708fafb068f535a2c93ff2356fDaisuke Miyakawa
215bdcb9fcc732d52708fafb068f535a2c93ff2356fDaisuke Miyakawa        public AttendeeItem(Attendee attendee, int presence, Drawable badge) {
216bdcb9fcc732d52708fafb068f535a2c93ff2356fDaisuke Miyakawa            mAttendee = attendee;
217bdcb9fcc732d52708fafb068f535a2c93ff2356fDaisuke Miyakawa            mPresence = presence;
218bdcb9fcc732d52708fafb068f535a2c93ff2356fDaisuke Miyakawa            mBadge = badge;
219bdcb9fcc732d52708fafb068f535a2c93ff2356fDaisuke Miyakawa        }
220d845fbe558d5229102b58cce70a1b29fe6cb6967Erik    }
221eca82e9468c002590045338f98588ad3ce1a0bb3Erik
222dd95df57c8c5a58a85c4c0effad5652dec14f621Erik    public EditEventHelper(Context context, CalendarEventModel model) {
223dd95df57c8c5a58a85c4c0effad5652dec14f621Erik        mContext = context;
224dd95df57c8c5a58a85c4c0effad5652dec14f621Erik        mService = new AsyncQueryService(context);
2256ae7d69b90f22fd51ab677036ba987dbdc35a62dErik        setDomainFromModel(model);
2266ae7d69b90f22fd51ab677036ba987dbdc35a62dErik    }
2276ae7d69b90f22fd51ab677036ba987dbdc35a62dErik
228eca82e9468c002590045338f98588ad3ce1a0bb3Erik    // Sets up the email validator for the given model
2296ae7d69b90f22fd51ab677036ba987dbdc35a62dErik    public void setDomainFromModel(CalendarEventModel model) {
23073714169d34a55fc01e14b30897a1ebbacf9934eErik        String domain = "gmail.com";
231eca82e9468c002590045338f98588ad3ce1a0bb3Erik        if (model != null) {
23273714169d34a55fc01e14b30897a1ebbacf9934eErik            String ownerAccount = model.mOwnerAccount;
23373714169d34a55fc01e14b30897a1ebbacf9934eErik            if (!TextUtils.isEmpty(ownerAccount)) {
23473714169d34a55fc01e14b30897a1ebbacf9934eErik                String ownerDomain = extractDomain(ownerAccount);
23573714169d34a55fc01e14b30897a1ebbacf9934eErik                if (!TextUtils.isEmpty(ownerDomain)) {
23673714169d34a55fc01e14b30897a1ebbacf9934eErik                    domain = ownerDomain;
23773714169d34a55fc01e14b30897a1ebbacf9934eErik                }
23873714169d34a55fc01e14b30897a1ebbacf9934eErik            }
23973714169d34a55fc01e14b30897a1ebbacf9934eErik        }
24073714169d34a55fc01e14b30897a1ebbacf9934eErik        mEmailValidator = new Rfc822Validator(domain);
24173714169d34a55fc01e14b30897a1ebbacf9934eErik    }
24273714169d34a55fc01e14b30897a1ebbacf9934eErik
24373714169d34a55fc01e14b30897a1ebbacf9934eErik    /**
244eca82e9468c002590045338f98588ad3ce1a0bb3Erik     * Saves the event. Returns true if the event was successfully saved, false
245eca82e9468c002590045338f98588ad3ce1a0bb3Erik     * otherwise.
24673714169d34a55fc01e14b30897a1ebbacf9934eErik     *
24773714169d34a55fc01e14b30897a1ebbacf9934eErik     * @param model The event model to save
24873714169d34a55fc01e14b30897a1ebbacf9934eErik     * @param originalModel A model of the original event if it exists
24973714169d34a55fc01e14b30897a1ebbacf9934eErik     * @param modifyWhich For recurring events which type of series modification to use
25073714169d34a55fc01e14b30897a1ebbacf9934eErik     * @return true if the event was successfully queued for saving
25173714169d34a55fc01e14b30897a1ebbacf9934eErik     */
25273714169d34a55fc01e14b30897a1ebbacf9934eErik    public boolean saveEvent(CalendarEventModel model, CalendarEventModel originalModel,
25373714169d34a55fc01e14b30897a1ebbacf9934eErik            int modifyWhich) {
25473714169d34a55fc01e14b30897a1ebbacf9934eErik        boolean forceSaveReminders = false;
25573714169d34a55fc01e14b30897a1ebbacf9934eErik
25673714169d34a55fc01e14b30897a1ebbacf9934eErik        if (DEBUG) {
25773714169d34a55fc01e14b30897a1ebbacf9934eErik            Log.d(TAG, "Saving event model: " + model);
25873714169d34a55fc01e14b30897a1ebbacf9934eErik        }
25973714169d34a55fc01e14b30897a1ebbacf9934eErik
26073714169d34a55fc01e14b30897a1ebbacf9934eErik        if (!mEventOk) {
26173714169d34a55fc01e14b30897a1ebbacf9934eErik            if (DEBUG) {
26273714169d34a55fc01e14b30897a1ebbacf9934eErik                Log.w(TAG, "Event no longer exists. Event was not saved.");
26373714169d34a55fc01e14b30897a1ebbacf9934eErik            }
26473714169d34a55fc01e14b30897a1ebbacf9934eErik            return false;
26573714169d34a55fc01e14b30897a1ebbacf9934eErik        }
26673714169d34a55fc01e14b30897a1ebbacf9934eErik
26773714169d34a55fc01e14b30897a1ebbacf9934eErik        // It's a problem if we try to save a non-existent or invalid model or if we're
26873714169d34a55fc01e14b30897a1ebbacf9934eErik        // modifying an existing event and we have the wrong original model
26973714169d34a55fc01e14b30897a1ebbacf9934eErik        if (model == null) {
27073714169d34a55fc01e14b30897a1ebbacf9934eErik            Log.e(TAG, "Attempted to save null model.");
27173714169d34a55fc01e14b30897a1ebbacf9934eErik            return false;
27273714169d34a55fc01e14b30897a1ebbacf9934eErik        }
27373714169d34a55fc01e14b30897a1ebbacf9934eErik        if (!model.isValid()) {
27473714169d34a55fc01e14b30897a1ebbacf9934eErik            Log.e(TAG, "Attempted to save invalid model.");
27573714169d34a55fc01e14b30897a1ebbacf9934eErik            return false;
27673714169d34a55fc01e14b30897a1ebbacf9934eErik        }
27773714169d34a55fc01e14b30897a1ebbacf9934eErik        if (originalModel != null && !isSameEvent(model, originalModel)) {
278eca82e9468c002590045338f98588ad3ce1a0bb3Erik            Log.e(TAG, "Attempted to update existing event but models didn't refer to the same "
279eca82e9468c002590045338f98588ad3ce1a0bb3Erik                    + "event.");
28073714169d34a55fc01e14b30897a1ebbacf9934eErik            return false;
28173714169d34a55fc01e14b30897a1ebbacf9934eErik        }
28273714169d34a55fc01e14b30897a1ebbacf9934eErik
28373714169d34a55fc01e14b30897a1ebbacf9934eErik        ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
28473714169d34a55fc01e14b30897a1ebbacf9934eErik        int eventIdIndex = -1;
28573714169d34a55fc01e14b30897a1ebbacf9934eErik
28673714169d34a55fc01e14b30897a1ebbacf9934eErik        ContentValues values = getContentValuesFromModel(model);
28773714169d34a55fc01e14b30897a1ebbacf9934eErik
2884ebfbd26b1bc2f351d7e1657ccef261b1504b88cErik        if (model.mUri != null && originalModel == null) {
28973714169d34a55fc01e14b30897a1ebbacf9934eErik            Log.e(TAG, "Existing event but no originalModel provided. Aborting save.");
29073714169d34a55fc01e14b30897a1ebbacf9934eErik            return false;
29173714169d34a55fc01e14b30897a1ebbacf9934eErik        }
2924ebfbd26b1bc2f351d7e1657ccef261b1504b88cErik        Uri uri = null;
2934ebfbd26b1bc2f351d7e1657ccef261b1504b88cErik        if (model.mUri != null) {
2944ebfbd26b1bc2f351d7e1657ccef261b1504b88cErik            uri = Uri.parse(model.mUri);
2954ebfbd26b1bc2f351d7e1657ccef261b1504b88cErik        }
29673714169d34a55fc01e14b30897a1ebbacf9934eErik
29773714169d34a55fc01e14b30897a1ebbacf9934eErik        // Update the "hasAlarm" field for the event
29873714169d34a55fc01e14b30897a1ebbacf9934eErik        ArrayList<Integer> reminderMinutes = model.mReminderMinutes;
29973714169d34a55fc01e14b30897a1ebbacf9934eErik        int len = reminderMinutes.size();
30073714169d34a55fc01e14b30897a1ebbacf9934eErik        values.put(Events.HAS_ALARM, (len > 0) ? 1 : 0);
30173714169d34a55fc01e14b30897a1ebbacf9934eErik
30273714169d34a55fc01e14b30897a1ebbacf9934eErik        if (uri == null) {
30373714169d34a55fc01e14b30897a1ebbacf9934eErik            // Add hasAttendeeData for a new event
30473714169d34a55fc01e14b30897a1ebbacf9934eErik            values.put(Events.HAS_ATTENDEE_DATA, 1);
30574318092f1c512cc66cffc0df5354515a1d2f09bErik            values.put(Events.STATUS, Events.STATUS_TENTATIVE);
30673714169d34a55fc01e14b30897a1ebbacf9934eErik            eventIdIndex = ops.size();
307eca82e9468c002590045338f98588ad3ce1a0bb3Erik            ContentProviderOperation.Builder b = ContentProviderOperation.newInsert(
308eca82e9468c002590045338f98588ad3ce1a0bb3Erik                    Events.CONTENT_URI).withValues(values);
30973714169d34a55fc01e14b30897a1ebbacf9934eErik            ops.add(b.build());
31073714169d34a55fc01e14b30897a1ebbacf9934eErik            forceSaveReminders = true;
31173714169d34a55fc01e14b30897a1ebbacf9934eErik
31266b2067e0a389180f909eb0a528bea5f6becca23Michael Chan        } else if (TextUtils.isEmpty(model.mRrule) && TextUtils.isEmpty(originalModel.mRrule)) {
31373714169d34a55fc01e14b30897a1ebbacf9934eErik            // Simple update to a non-recurring event
31473714169d34a55fc01e14b30897a1ebbacf9934eErik            checkTimeDependentFields(originalModel, model, values, modifyWhich);
31573714169d34a55fc01e14b30897a1ebbacf9934eErik            ops.add(ContentProviderOperation.newUpdate(uri).withValues(values).build());
31673714169d34a55fc01e14b30897a1ebbacf9934eErik
31766b2067e0a389180f909eb0a528bea5f6becca23Michael Chan        } else if (TextUtils.isEmpty(originalModel.mRrule)) {
31873714169d34a55fc01e14b30897a1ebbacf9934eErik            // This event was changed from a non-repeating event to a
31973714169d34a55fc01e14b30897a1ebbacf9934eErik            // repeating event.
32073714169d34a55fc01e14b30897a1ebbacf9934eErik            ops.add(ContentProviderOperation.newUpdate(uri).withValues(values).build());
32173714169d34a55fc01e14b30897a1ebbacf9934eErik
32273714169d34a55fc01e14b30897a1ebbacf9934eErik        } else if (modifyWhich == MODIFY_SELECTED) {
32373714169d34a55fc01e14b30897a1ebbacf9934eErik            // Modify contents of the current instance of repeating event
32473714169d34a55fc01e14b30897a1ebbacf9934eErik            // Create a recurrence exception
32573714169d34a55fc01e14b30897a1ebbacf9934eErik            long begin = model.mOriginalStart;
32673714169d34a55fc01e14b30897a1ebbacf9934eErik            values.put(Events.ORIGINAL_EVENT, originalModel.mSyncId);
32773714169d34a55fc01e14b30897a1ebbacf9934eErik            values.put(Events.ORIGINAL_INSTANCE_TIME, begin);
32873714169d34a55fc01e14b30897a1ebbacf9934eErik            boolean allDay = originalModel.mAllDay;
32973714169d34a55fc01e14b30897a1ebbacf9934eErik            values.put(Events.ORIGINAL_ALL_DAY, allDay ? 1 : 0);
33074318092f1c512cc66cffc0df5354515a1d2f09bErik            values.put(Events.STATUS, Events.STATUS_TENTATIVE);
33173714169d34a55fc01e14b30897a1ebbacf9934eErik
33273714169d34a55fc01e14b30897a1ebbacf9934eErik            eventIdIndex = ops.size();
333eca82e9468c002590045338f98588ad3ce1a0bb3Erik            ContentProviderOperation.Builder b = ContentProviderOperation.newInsert(
334eca82e9468c002590045338f98588ad3ce1a0bb3Erik                    Events.CONTENT_URI).withValues(values);
33573714169d34a55fc01e14b30897a1ebbacf9934eErik            ops.add(b.build());
33673714169d34a55fc01e14b30897a1ebbacf9934eErik            forceSaveReminders = true;
33773714169d34a55fc01e14b30897a1ebbacf9934eErik
33873714169d34a55fc01e14b30897a1ebbacf9934eErik        } else if (modifyWhich == MODIFY_ALL_FOLLOWING) {
33973714169d34a55fc01e14b30897a1ebbacf9934eErik
34066b2067e0a389180f909eb0a528bea5f6becca23Michael Chan            if (TextUtils.isEmpty(model.mRrule)) {
34173714169d34a55fc01e14b30897a1ebbacf9934eErik                // We've changed a recurring event to a non-recurring event.
34273714169d34a55fc01e14b30897a1ebbacf9934eErik                // If the event we are editing is the first in the series,
343eca82e9468c002590045338f98588ad3ce1a0bb3Erik                // then delete the whole series. Otherwise, update the series
34473714169d34a55fc01e14b30897a1ebbacf9934eErik                // to end at the new start time.
34573714169d34a55fc01e14b30897a1ebbacf9934eErik                if (isFirstEventInSeries(model, originalModel)) {
34673714169d34a55fc01e14b30897a1ebbacf9934eErik                    ops.add(ContentProviderOperation.newDelete(uri).build());
34773714169d34a55fc01e14b30897a1ebbacf9934eErik                } else {
34873714169d34a55fc01e14b30897a1ebbacf9934eErik                    // Update the current repeating event to end at the new
34973714169d34a55fc01e14b30897a1ebbacf9934eErik                    // start time.
35073714169d34a55fc01e14b30897a1ebbacf9934eErik                    updatePastEvents(ops, originalModel, model.mOriginalStart);
35173714169d34a55fc01e14b30897a1ebbacf9934eErik                }
35273714169d34a55fc01e14b30897a1ebbacf9934eErik                eventIdIndex = ops.size();
35374318092f1c512cc66cffc0df5354515a1d2f09bErik                values.put(Events.STATUS, Events.STATUS_TENTATIVE);
354eca82e9468c002590045338f98588ad3ce1a0bb3Erik                ops.add(ContentProviderOperation.newInsert(Events.CONTENT_URI).withValues(values)
35573714169d34a55fc01e14b30897a1ebbacf9934eErik                        .build());
35673714169d34a55fc01e14b30897a1ebbacf9934eErik            } else {
35773714169d34a55fc01e14b30897a1ebbacf9934eErik                if (isFirstEventInSeries(model, originalModel)) {
35873714169d34a55fc01e14b30897a1ebbacf9934eErik                    checkTimeDependentFields(originalModel, model, values, modifyWhich);
359eca82e9468c002590045338f98588ad3ce1a0bb3Erik                    ContentProviderOperation.Builder b = ContentProviderOperation.newUpdate(uri)
36073714169d34a55fc01e14b30897a1ebbacf9934eErik                            .withValues(values);
36173714169d34a55fc01e14b30897a1ebbacf9934eErik                    ops.add(b.build());
36273714169d34a55fc01e14b30897a1ebbacf9934eErik                } else {
36373714169d34a55fc01e14b30897a1ebbacf9934eErik                    // Update the current repeating event to end at the new
36473714169d34a55fc01e14b30897a1ebbacf9934eErik                    // start time.
36573714169d34a55fc01e14b30897a1ebbacf9934eErik                    updatePastEvents(ops, originalModel, model.mOriginalStart);
36673714169d34a55fc01e14b30897a1ebbacf9934eErik
36773714169d34a55fc01e14b30897a1ebbacf9934eErik                    // Create a new event with the user-modified fields
36873714169d34a55fc01e14b30897a1ebbacf9934eErik                    eventIdIndex = ops.size();
36974318092f1c512cc66cffc0df5354515a1d2f09bErik                    values.put(Events.STATUS, Events.STATUS_TENTATIVE);
370eca82e9468c002590045338f98588ad3ce1a0bb3Erik                    ops.add(ContentProviderOperation.newInsert(Events.CONTENT_URI).withValues(
371eca82e9468c002590045338f98588ad3ce1a0bb3Erik                            values).build());
37273714169d34a55fc01e14b30897a1ebbacf9934eErik                }
37373714169d34a55fc01e14b30897a1ebbacf9934eErik            }
37473714169d34a55fc01e14b30897a1ebbacf9934eErik            forceSaveReminders = true;
37573714169d34a55fc01e14b30897a1ebbacf9934eErik
37673714169d34a55fc01e14b30897a1ebbacf9934eErik        } else if (modifyWhich == MODIFY_ALL) {
37773714169d34a55fc01e14b30897a1ebbacf9934eErik
37873714169d34a55fc01e14b30897a1ebbacf9934eErik            // Modify all instances of repeating event
37966b2067e0a389180f909eb0a528bea5f6becca23Michael Chan            if (TextUtils.isEmpty(model.mRrule)) {
38073714169d34a55fc01e14b30897a1ebbacf9934eErik                // We've changed a recurring event to a non-recurring event.
38173714169d34a55fc01e14b30897a1ebbacf9934eErik                // Delete the whole series and replace it with a new
38273714169d34a55fc01e14b30897a1ebbacf9934eErik                // non-recurring event.
38373714169d34a55fc01e14b30897a1ebbacf9934eErik                ops.add(ContentProviderOperation.newDelete(uri).build());
38473714169d34a55fc01e14b30897a1ebbacf9934eErik
38573714169d34a55fc01e14b30897a1ebbacf9934eErik                eventIdIndex = ops.size();
386eca82e9468c002590045338f98588ad3ce1a0bb3Erik                ops.add(ContentProviderOperation.newInsert(Events.CONTENT_URI).withValues(values)
38773714169d34a55fc01e14b30897a1ebbacf9934eErik                        .build());
38873714169d34a55fc01e14b30897a1ebbacf9934eErik                forceSaveReminders = true;
38973714169d34a55fc01e14b30897a1ebbacf9934eErik            } else {
39073714169d34a55fc01e14b30897a1ebbacf9934eErik                checkTimeDependentFields(originalModel, model, values, modifyWhich);
391eca82e9468c002590045338f98588ad3ce1a0bb3Erik                ops.add(ContentProviderOperation.newUpdate(uri).withValues(values).build());
39273714169d34a55fc01e14b30897a1ebbacf9934eErik            }
39373714169d34a55fc01e14b30897a1ebbacf9934eErik        }
39473714169d34a55fc01e14b30897a1ebbacf9934eErik
39573714169d34a55fc01e14b30897a1ebbacf9934eErik        // New Event or New Exception to an existing event
39673714169d34a55fc01e14b30897a1ebbacf9934eErik        boolean newEvent = (eventIdIndex != -1);
39773714169d34a55fc01e14b30897a1ebbacf9934eErik        ArrayList<Integer> originalMinutes;
39873714169d34a55fc01e14b30897a1ebbacf9934eErik        if (originalModel != null) {
39973714169d34a55fc01e14b30897a1ebbacf9934eErik            originalMinutes = originalModel.mReminderMinutes;
40073714169d34a55fc01e14b30897a1ebbacf9934eErik        } else {
40173714169d34a55fc01e14b30897a1ebbacf9934eErik            originalMinutes = new ArrayList<Integer>();
40273714169d34a55fc01e14b30897a1ebbacf9934eErik        }
40373714169d34a55fc01e14b30897a1ebbacf9934eErik
40473714169d34a55fc01e14b30897a1ebbacf9934eErik        if (newEvent) {
405eca82e9468c002590045338f98588ad3ce1a0bb3Erik            saveRemindersWithBackRef(ops, eventIdIndex, reminderMinutes, originalMinutes,
406eca82e9468c002590045338f98588ad3ce1a0bb3Erik                    forceSaveReminders);
40773714169d34a55fc01e14b30897a1ebbacf9934eErik        } else if (uri != null) {
40873714169d34a55fc01e14b30897a1ebbacf9934eErik            long eventId = ContentUris.parseId(uri);
409eca82e9468c002590045338f98588ad3ce1a0bb3Erik            saveReminders(ops, eventId, reminderMinutes, originalMinutes, forceSaveReminders);
41073714169d34a55fc01e14b30897a1ebbacf9934eErik        }
41173714169d34a55fc01e14b30897a1ebbacf9934eErik
41273714169d34a55fc01e14b30897a1ebbacf9934eErik        ContentProviderOperation.Builder b;
41373714169d34a55fc01e14b30897a1ebbacf9934eErik        boolean hasAttendeeData = model.mHasAttendeeData;
41473714169d34a55fc01e14b30897a1ebbacf9934eErik
41573714169d34a55fc01e14b30897a1ebbacf9934eErik        // New event/instance - Set Organizer's response as yes
41673714169d34a55fc01e14b30897a1ebbacf9934eErik        if (hasAttendeeData && newEvent) {
41773714169d34a55fc01e14b30897a1ebbacf9934eErik            values.clear();
41873714169d34a55fc01e14b30897a1ebbacf9934eErik
41973714169d34a55fc01e14b30897a1ebbacf9934eErik            String ownerEmail = model.mOwnerAccount;
42073714169d34a55fc01e14b30897a1ebbacf9934eErik            if (ownerEmail != null) {
42173714169d34a55fc01e14b30897a1ebbacf9934eErik                values.put(Attendees.ATTENDEE_EMAIL, ownerEmail);
422eca82e9468c002590045338f98588ad3ce1a0bb3Erik                values.put(Attendees.ATTENDEE_RELATIONSHIP, Attendees.RELATIONSHIP_ORGANIZER);
423eca82e9468c002590045338f98588ad3ce1a0bb3Erik                values.put(Attendees.ATTENDEE_TYPE, Attendees.TYPE_NONE);
424e5149edad36737783356778819a9c66993a9cf28Michael Chan                int initialStatus = Attendees.ATTENDEE_STATUS_ACCEPTED;
425364309f0bb9c3aeee8efd0c24faf478f7c62c6d7Erik                if (originalModel != null) {
426364309f0bb9c3aeee8efd0c24faf478f7c62c6d7Erik                    initialStatus = model.mSelfAttendeeStatus;
427364309f0bb9c3aeee8efd0c24faf478f7c62c6d7Erik                }
428e5149edad36737783356778819a9c66993a9cf28Michael Chan
429e5149edad36737783356778819a9c66993a9cf28Michael Chan                // Don't accept for secondary calendars
430e5149edad36737783356778819a9c66993a9cf28Michael Chan                if (ownerEmail.endsWith("calendar.google.com")) {
431e5149edad36737783356778819a9c66993a9cf28Michael Chan                    initialStatus = Attendees.ATTENDEE_STATUS_NONE;
432e5149edad36737783356778819a9c66993a9cf28Michael Chan                }
433e5149edad36737783356778819a9c66993a9cf28Michael Chan                values.put(Attendees.ATTENDEE_STATUS, initialStatus);
434eca82e9468c002590045338f98588ad3ce1a0bb3Erik
435eca82e9468c002590045338f98588ad3ce1a0bb3Erik                b = ContentProviderOperation.newInsert(Attendees.CONTENT_URI).withValues(values);
436d845fbe558d5229102b58cce70a1b29fe6cb6967Erik                b.withValueBackReference(Attendees.EVENT_ID, eventIdIndex);
43773714169d34a55fc01e14b30897a1ebbacf9934eErik                ops.add(b.build());
43873714169d34a55fc01e14b30897a1ebbacf9934eErik            }
439d845fbe558d5229102b58cce70a1b29fe6cb6967Erik        } else if (hasAttendeeData &&
440d845fbe558d5229102b58cce70a1b29fe6cb6967Erik                model.mSelfAttendeeStatus != originalModel.mSelfAttendeeStatus &&
441d845fbe558d5229102b58cce70a1b29fe6cb6967Erik                model.mOwnerAttendeeId != -1) {
442d845fbe558d5229102b58cce70a1b29fe6cb6967Erik            if (DEBUG) {
443d845fbe558d5229102b58cce70a1b29fe6cb6967Erik                Log.d(TAG, "Setting attendee status to " + model.mSelfAttendeeStatus);
444d845fbe558d5229102b58cce70a1b29fe6cb6967Erik            }
445d845fbe558d5229102b58cce70a1b29fe6cb6967Erik            Uri attUri = ContentUris.withAppendedId(Attendees.CONTENT_URI, model.mOwnerAttendeeId);
446d845fbe558d5229102b58cce70a1b29fe6cb6967Erik
447d845fbe558d5229102b58cce70a1b29fe6cb6967Erik            values.clear();
448d845fbe558d5229102b58cce70a1b29fe6cb6967Erik            values.put(Attendees.ATTENDEE_STATUS, model.mSelfAttendeeStatus);
449d845fbe558d5229102b58cce70a1b29fe6cb6967Erik            values.put(Attendees.EVENT_ID, model.mId);
450d845fbe558d5229102b58cce70a1b29fe6cb6967Erik            b = ContentProviderOperation.newUpdate(attUri).withValues(values);
451d845fbe558d5229102b58cce70a1b29fe6cb6967Erik            ops.add(b.build());
45273714169d34a55fc01e14b30897a1ebbacf9934eErik        }
45373714169d34a55fc01e14b30897a1ebbacf9934eErik
454eca82e9468c002590045338f98588ad3ce1a0bb3Erik        // TODO: is this the right test? this currently checks if this is
455eca82e9468c002590045338f98588ad3ce1a0bb3Erik        // a new event or an existing event. or is this a paranoia check?
45673714169d34a55fc01e14b30897a1ebbacf9934eErik        if (hasAttendeeData && (newEvent || uri != null)) {
457d845fbe558d5229102b58cce70a1b29fe6cb6967Erik            String attendees = model.getAttendeesString();
45873714169d34a55fc01e14b30897a1ebbacf9934eErik            String originalAttendeesString;
45973714169d34a55fc01e14b30897a1ebbacf9934eErik            if (originalModel != null) {
460d845fbe558d5229102b58cce70a1b29fe6cb6967Erik                originalAttendeesString = originalModel.getAttendeesString();
46173714169d34a55fc01e14b30897a1ebbacf9934eErik            } else {
46273714169d34a55fc01e14b30897a1ebbacf9934eErik                originalAttendeesString = "";
46373714169d34a55fc01e14b30897a1ebbacf9934eErik            }
464eca82e9468c002590045338f98588ad3ce1a0bb3Erik            // Hit the content provider only if this is a new event or the user
465eca82e9468c002590045338f98588ad3ce1a0bb3Erik            // has changed it
46673714169d34a55fc01e14b30897a1ebbacf9934eErik            if (newEvent || !TextUtils.equals(originalAttendeesString, attendees)) {
46773714169d34a55fc01e14b30897a1ebbacf9934eErik                // figure out which attendees need to be added and which ones
468eca82e9468c002590045338f98588ad3ce1a0bb3Erik                // need to be deleted. use a linked hash set, so we maintain
46973714169d34a55fc01e14b30897a1ebbacf9934eErik                // order (but also remove duplicates).
470eca82e9468c002590045338f98588ad3ce1a0bb3Erik                setDomainFromModel(model);
471d845fbe558d5229102b58cce70a1b29fe6cb6967Erik                HashMap<String, Attendee> newAttendees = model.mAttendeesList;
472d845fbe558d5229102b58cce70a1b29fe6cb6967Erik                LinkedList<String> removedAttendees = new LinkedList<String>();
47373714169d34a55fc01e14b30897a1ebbacf9934eErik
47473714169d34a55fc01e14b30897a1ebbacf9934eErik                // the eventId is only used if eventIdIndex is -1.
47573714169d34a55fc01e14b30897a1ebbacf9934eErik                // TODO: clean up this code.
47673714169d34a55fc01e14b30897a1ebbacf9934eErik                long eventId = uri != null ? ContentUris.parseId(uri) : -1;
47773714169d34a55fc01e14b30897a1ebbacf9934eErik
47873714169d34a55fc01e14b30897a1ebbacf9934eErik                // only compute deltas if this is an existing event.
47973714169d34a55fc01e14b30897a1ebbacf9934eErik                // new events (being inserted into the Events table) won't
48073714169d34a55fc01e14b30897a1ebbacf9934eErik                // have any existing attendees.
48173714169d34a55fc01e14b30897a1ebbacf9934eErik                if (!newEvent) {
482d845fbe558d5229102b58cce70a1b29fe6cb6967Erik                    removedAttendees.clear();
483d845fbe558d5229102b58cce70a1b29fe6cb6967Erik                    HashMap<String, Attendee> originalAttendees = originalModel.mAttendeesList;
484d845fbe558d5229102b58cce70a1b29fe6cb6967Erik                    for (String originalEmail : originalAttendees.keySet()) {
485d845fbe558d5229102b58cce70a1b29fe6cb6967Erik                        if (newAttendees.containsKey(originalEmail)) {
486eca82e9468c002590045338f98588ad3ce1a0bb3Erik                            // existing attendee. remove from new attendees set.
487d845fbe558d5229102b58cce70a1b29fe6cb6967Erik                            newAttendees.remove(originalEmail);
48873714169d34a55fc01e14b30897a1ebbacf9934eErik                        } else {
489eca82e9468c002590045338f98588ad3ce1a0bb3Erik                            // no longer in attendees. mark as removed.
490d845fbe558d5229102b58cce70a1b29fe6cb6967Erik                            removedAttendees.add(originalEmail);
49173714169d34a55fc01e14b30897a1ebbacf9934eErik                        }
49273714169d34a55fc01e14b30897a1ebbacf9934eErik                    }
49373714169d34a55fc01e14b30897a1ebbacf9934eErik
49473714169d34a55fc01e14b30897a1ebbacf9934eErik                    // delete removed attendees if necessary
49573714169d34a55fc01e14b30897a1ebbacf9934eErik                    if (removedAttendees.size() > 0) {
49673714169d34a55fc01e14b30897a1ebbacf9934eErik                        b = ContentProviderOperation.newDelete(Attendees.CONTENT_URI);
49773714169d34a55fc01e14b30897a1ebbacf9934eErik
49873714169d34a55fc01e14b30897a1ebbacf9934eErik                        String[] args = new String[removedAttendees.size() + 1];
49973714169d34a55fc01e14b30897a1ebbacf9934eErik                        args[0] = Long.toString(eventId);
50073714169d34a55fc01e14b30897a1ebbacf9934eErik                        int i = 1;
50173714169d34a55fc01e14b30897a1ebbacf9934eErik                        StringBuilder deleteWhere = new StringBuilder(ATTENDEES_DELETE_PREFIX);
502d845fbe558d5229102b58cce70a1b29fe6cb6967Erik                        for (String removedAttendee : removedAttendees) {
50373714169d34a55fc01e14b30897a1ebbacf9934eErik                            if (i > 1) {
50473714169d34a55fc01e14b30897a1ebbacf9934eErik                                deleteWhere.append(",");
50573714169d34a55fc01e14b30897a1ebbacf9934eErik                            }
50673714169d34a55fc01e14b30897a1ebbacf9934eErik                            deleteWhere.append("?");
507d845fbe558d5229102b58cce70a1b29fe6cb6967Erik                            args[i++] = removedAttendee;
50873714169d34a55fc01e14b30897a1ebbacf9934eErik                        }
50973714169d34a55fc01e14b30897a1ebbacf9934eErik                        deleteWhere.append(")");
51073714169d34a55fc01e14b30897a1ebbacf9934eErik                        b.withSelection(deleteWhere.toString(), args);
51173714169d34a55fc01e14b30897a1ebbacf9934eErik                        ops.add(b.build());
51273714169d34a55fc01e14b30897a1ebbacf9934eErik                    }
51373714169d34a55fc01e14b30897a1ebbacf9934eErik                }
51473714169d34a55fc01e14b30897a1ebbacf9934eErik
51573714169d34a55fc01e14b30897a1ebbacf9934eErik                if (newAttendees.size() > 0) {
51673714169d34a55fc01e14b30897a1ebbacf9934eErik                    // Insert the new attendees
517d845fbe558d5229102b58cce70a1b29fe6cb6967Erik                    for (Attendee attendee : newAttendees.values()) {
51873714169d34a55fc01e14b30897a1ebbacf9934eErik                        values.clear();
519d845fbe558d5229102b58cce70a1b29fe6cb6967Erik                        values.put(Attendees.ATTENDEE_NAME, attendee.mName);
520d845fbe558d5229102b58cce70a1b29fe6cb6967Erik                        values.put(Attendees.ATTENDEE_EMAIL, attendee.mEmail);
52173714169d34a55fc01e14b30897a1ebbacf9934eErik                        values.put(Attendees.ATTENDEE_RELATIONSHIP,
52273714169d34a55fc01e14b30897a1ebbacf9934eErik                                Attendees.RELATIONSHIP_ATTENDEE);
523eca82e9468c002590045338f98588ad3ce1a0bb3Erik                        values.put(Attendees.ATTENDEE_TYPE, Attendees.TYPE_NONE);
524eca82e9468c002590045338f98588ad3ce1a0bb3Erik                        values.put(Attendees.ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_NONE);
52573714169d34a55fc01e14b30897a1ebbacf9934eErik
52673714169d34a55fc01e14b30897a1ebbacf9934eErik                        if (newEvent) {
527eca82e9468c002590045338f98588ad3ce1a0bb3Erik                            b = ContentProviderOperation.newInsert(Attendees.CONTENT_URI)
52873714169d34a55fc01e14b30897a1ebbacf9934eErik                                    .withValues(values);
52973714169d34a55fc01e14b30897a1ebbacf9934eErik                            b.withValueBackReference(Attendees.EVENT_ID, eventIdIndex);
53073714169d34a55fc01e14b30897a1ebbacf9934eErik                        } else {
53173714169d34a55fc01e14b30897a1ebbacf9934eErik                            values.put(Attendees.EVENT_ID, eventId);
532eca82e9468c002590045338f98588ad3ce1a0bb3Erik                            b = ContentProviderOperation.newInsert(Attendees.CONTENT_URI)
53373714169d34a55fc01e14b30897a1ebbacf9934eErik                                    .withValues(values);
53473714169d34a55fc01e14b30897a1ebbacf9934eErik                        }
53573714169d34a55fc01e14b30897a1ebbacf9934eErik                        ops.add(b.build());
53673714169d34a55fc01e14b30897a1ebbacf9934eErik                    }
53773714169d34a55fc01e14b30897a1ebbacf9934eErik                }
53873714169d34a55fc01e14b30897a1ebbacf9934eErik            }
53973714169d34a55fc01e14b30897a1ebbacf9934eErik        }
54073714169d34a55fc01e14b30897a1ebbacf9934eErik
54137960c091b5912dd45beb561a85a9064ea42e616Erik
54237960c091b5912dd45beb561a85a9064ea42e616Erik        mService.startBatch(mService.getNextToken(), null, android.provider.Calendar.AUTHORITY, ops,
54337960c091b5912dd45beb561a85a9064ea42e616Erik                Utils.UNDO_DELAY);
54473714169d34a55fc01e14b30897a1ebbacf9934eErik
54573714169d34a55fc01e14b30897a1ebbacf9934eErik        return true;
54673714169d34a55fc01e14b30897a1ebbacf9934eErik    }
54773714169d34a55fc01e14b30897a1ebbacf9934eErik
548d845fbe558d5229102b58cce70a1b29fe6cb6967Erik    public static LinkedHashSet<Rfc822Token> getAddressesFromList(String list,
549d845fbe558d5229102b58cce70a1b29fe6cb6967Erik            Rfc822Validator validator) {
55073714169d34a55fc01e14b30897a1ebbacf9934eErik        LinkedHashSet<Rfc822Token> addresses = new LinkedHashSet<Rfc822Token>();
55173714169d34a55fc01e14b30897a1ebbacf9934eErik        Rfc822Tokenizer.tokenize(list, addresses);
552d845fbe558d5229102b58cce70a1b29fe6cb6967Erik        if (validator == null) {
553d845fbe558d5229102b58cce70a1b29fe6cb6967Erik            return addresses;
554d845fbe558d5229102b58cce70a1b29fe6cb6967Erik        }
55573714169d34a55fc01e14b30897a1ebbacf9934eErik
556eca82e9468c002590045338f98588ad3ce1a0bb3Erik        // validate the emails, out of paranoia. they should already be
55773714169d34a55fc01e14b30897a1ebbacf9934eErik        // validated on input, but drop any invalid emails just to be safe.
55873714169d34a55fc01e14b30897a1ebbacf9934eErik        Iterator<Rfc822Token> addressIterator = addresses.iterator();
55973714169d34a55fc01e14b30897a1ebbacf9934eErik        while (addressIterator.hasNext()) {
56073714169d34a55fc01e14b30897a1ebbacf9934eErik            Rfc822Token address = addressIterator.next();
561d845fbe558d5229102b58cce70a1b29fe6cb6967Erik            if (!validator.isValid(address.getAddress())) {
562d845fbe558d5229102b58cce70a1b29fe6cb6967Erik                Log.v(TAG, "Dropping invalid attendee email address: " + address.getAddress());
56373714169d34a55fc01e14b30897a1ebbacf9934eErik                addressIterator.remove();
56473714169d34a55fc01e14b30897a1ebbacf9934eErik            }
56573714169d34a55fc01e14b30897a1ebbacf9934eErik        }
56673714169d34a55fc01e14b30897a1ebbacf9934eErik        return addresses;
56773714169d34a55fc01e14b30897a1ebbacf9934eErik    }
56873714169d34a55fc01e14b30897a1ebbacf9934eErik
569e700c16ec2464cbba86f91f8f757ae59cbed34b0Mason Tang    /**
570e700c16ec2464cbba86f91f8f757ae59cbed34b0Mason Tang     * When we aren't given an explicit start time, we default to the next
571e700c16ec2464cbba86f91f8f757ae59cbed34b0Mason Tang     * upcoming half hour. So, for example, 5:01 -> 5:30, 5:30 -> 6:00, etc.
572e700c16ec2464cbba86f91f8f757ae59cbed34b0Mason Tang     *
573e700c16ec2464cbba86f91f8f757ae59cbed34b0Mason Tang     * @return a UTC time in milliseconds representing the next upcoming half
574e700c16ec2464cbba86f91f8f757ae59cbed34b0Mason Tang     * hour
575e700c16ec2464cbba86f91f8f757ae59cbed34b0Mason Tang     */
576e700c16ec2464cbba86f91f8f757ae59cbed34b0Mason Tang    protected long constructDefaultStartTime(long now) {
577e700c16ec2464cbba86f91f8f757ae59cbed34b0Mason Tang        Time defaultStart = new Time();
578e700c16ec2464cbba86f91f8f757ae59cbed34b0Mason Tang        defaultStart.set(now);
579e700c16ec2464cbba86f91f8f757ae59cbed34b0Mason Tang        defaultStart.second = 0;
580e700c16ec2464cbba86f91f8f757ae59cbed34b0Mason Tang        defaultStart.minute = 30;
581e700c16ec2464cbba86f91f8f757ae59cbed34b0Mason Tang        long defaultStartMillis = defaultStart.toMillis(false);
582e700c16ec2464cbba86f91f8f757ae59cbed34b0Mason Tang        if (now < defaultStartMillis) {
583e700c16ec2464cbba86f91f8f757ae59cbed34b0Mason Tang            return defaultStartMillis;
584e700c16ec2464cbba86f91f8f757ae59cbed34b0Mason Tang        } else {
585e700c16ec2464cbba86f91f8f757ae59cbed34b0Mason Tang            return defaultStartMillis + 30 * DateUtils.MINUTE_IN_MILLIS;
586e700c16ec2464cbba86f91f8f757ae59cbed34b0Mason Tang        }
587e700c16ec2464cbba86f91f8f757ae59cbed34b0Mason Tang    }
588e700c16ec2464cbba86f91f8f757ae59cbed34b0Mason Tang
589e700c16ec2464cbba86f91f8f757ae59cbed34b0Mason Tang    /**
590e700c16ec2464cbba86f91f8f757ae59cbed34b0Mason Tang     * When we aren't given an explicit end time, we default to an hour after
591e700c16ec2464cbba86f91f8f757ae59cbed34b0Mason Tang     * the start time.
592e700c16ec2464cbba86f91f8f757ae59cbed34b0Mason Tang     * @param startTime the start time
593e700c16ec2464cbba86f91f8f757ae59cbed34b0Mason Tang     * @return a default end time
594e700c16ec2464cbba86f91f8f757ae59cbed34b0Mason Tang     */
595e700c16ec2464cbba86f91f8f757ae59cbed34b0Mason Tang    protected long constructDefaultEndTime(long startTime) {
596e700c16ec2464cbba86f91f8f757ae59cbed34b0Mason Tang        return startTime + DateUtils.HOUR_IN_MILLIS;
597e700c16ec2464cbba86f91f8f757ae59cbed34b0Mason Tang    }
598e700c16ec2464cbba86f91f8f757ae59cbed34b0Mason Tang
599eca82e9468c002590045338f98588ad3ce1a0bb3Erik    // TODO think about how useful this is. Probably check if our event has
600eca82e9468c002590045338f98588ad3ce1a0bb3Erik    // changed early on and either update all or nothing. Should still do the if
601eca82e9468c002590045338f98588ad3ce1a0bb3Erik    // MODIFY_ALL bit.
602eca82e9468c002590045338f98588ad3ce1a0bb3Erik    void checkTimeDependentFields(CalendarEventModel originalModel, CalendarEventModel model,
603eca82e9468c002590045338f98588ad3ce1a0bb3Erik            ContentValues values, int modifyWhich) {
60473714169d34a55fc01e14b30897a1ebbacf9934eErik        long oldBegin = model.mOriginalStart;
60573714169d34a55fc01e14b30897a1ebbacf9934eErik        long oldEnd = model.mOriginalEnd;
60673714169d34a55fc01e14b30897a1ebbacf9934eErik        boolean oldAllDay = originalModel.mAllDay;
60773714169d34a55fc01e14b30897a1ebbacf9934eErik        String oldRrule = originalModel.mRrule;
60873714169d34a55fc01e14b30897a1ebbacf9934eErik        String oldTimezone = originalModel.mTimezone;
60973714169d34a55fc01e14b30897a1ebbacf9934eErik
61073714169d34a55fc01e14b30897a1ebbacf9934eErik        long newBegin = model.mStart;
61173714169d34a55fc01e14b30897a1ebbacf9934eErik        long newEnd = model.mEnd;
61273714169d34a55fc01e14b30897a1ebbacf9934eErik        boolean newAllDay = model.mAllDay;
61373714169d34a55fc01e14b30897a1ebbacf9934eErik        String newRrule = model.mRrule;
61473714169d34a55fc01e14b30897a1ebbacf9934eErik        String newTimezone = model.mTimezone;
61573714169d34a55fc01e14b30897a1ebbacf9934eErik
61673714169d34a55fc01e14b30897a1ebbacf9934eErik        // If none of the time-dependent fields changed, then remove them.
61773714169d34a55fc01e14b30897a1ebbacf9934eErik        if (oldBegin == newBegin && oldEnd == newEnd && oldAllDay == newAllDay
61873714169d34a55fc01e14b30897a1ebbacf9934eErik                && TextUtils.equals(oldRrule, newRrule)
619eca82e9468c002590045338f98588ad3ce1a0bb3Erik                && TextUtils.equals(oldTimezone, newTimezone)) {
62073714169d34a55fc01e14b30897a1ebbacf9934eErik            values.remove(Events.DTSTART);
62173714169d34a55fc01e14b30897a1ebbacf9934eErik            values.remove(Events.DTEND);
62273714169d34a55fc01e14b30897a1ebbacf9934eErik            values.remove(Events.DURATION);
62373714169d34a55fc01e14b30897a1ebbacf9934eErik            values.remove(Events.ALL_DAY);
62473714169d34a55fc01e14b30897a1ebbacf9934eErik            values.remove(Events.RRULE);
62573714169d34a55fc01e14b30897a1ebbacf9934eErik            values.remove(Events.EVENT_TIMEZONE);
62673714169d34a55fc01e14b30897a1ebbacf9934eErik            return;
62773714169d34a55fc01e14b30897a1ebbacf9934eErik        }
62873714169d34a55fc01e14b30897a1ebbacf9934eErik
62966b2067e0a389180f909eb0a528bea5f6becca23Michael Chan        if (TextUtils.isEmpty(oldRrule) || TextUtils.isEmpty(newRrule)) {
63073714169d34a55fc01e14b30897a1ebbacf9934eErik            return;
63173714169d34a55fc01e14b30897a1ebbacf9934eErik        }
63273714169d34a55fc01e14b30897a1ebbacf9934eErik
63373714169d34a55fc01e14b30897a1ebbacf9934eErik        // If we are modifying all events then we need to set DTSTART to the
63473714169d34a55fc01e14b30897a1ebbacf9934eErik        // start time of the first event in the series, not the current
635eca82e9468c002590045338f98588ad3ce1a0bb3Erik        // date and time. If the start time of the event was changed
63673714169d34a55fc01e14b30897a1ebbacf9934eErik        // (from, say, 3pm to 4pm), then we want to add the time difference
63773714169d34a55fc01e14b30897a1ebbacf9934eErik        // to the start time of the first event in the series (the DTSTART
638eca82e9468c002590045338f98588ad3ce1a0bb3Erik        // value). If we are modifying one instance or all following instances,
63973714169d34a55fc01e14b30897a1ebbacf9934eErik        // then we leave the DTSTART field alone.
64073714169d34a55fc01e14b30897a1ebbacf9934eErik        if (modifyWhich == MODIFY_ALL) {
64173714169d34a55fc01e14b30897a1ebbacf9934eErik            long oldStartMillis = originalModel.mStart;
64273714169d34a55fc01e14b30897a1ebbacf9934eErik            long oldStartMillis2 = oldStartMillis;
64373714169d34a55fc01e14b30897a1ebbacf9934eErik            if (oldBegin != newBegin) {
64473714169d34a55fc01e14b30897a1ebbacf9934eErik                // The user changed the start time of this event
64573714169d34a55fc01e14b30897a1ebbacf9934eErik                long offset = newBegin - oldBegin;
64673714169d34a55fc01e14b30897a1ebbacf9934eErik                oldStartMillis += offset;
64773714169d34a55fc01e14b30897a1ebbacf9934eErik            }
64873714169d34a55fc01e14b30897a1ebbacf9934eErik            if (newAllDay) {
64973714169d34a55fc01e14b30897a1ebbacf9934eErik                Time time = new Time(Time.TIMEZONE_UTC);
65073714169d34a55fc01e14b30897a1ebbacf9934eErik                time.set(oldStartMillis);
65173714169d34a55fc01e14b30897a1ebbacf9934eErik                time.hour = 0;
65273714169d34a55fc01e14b30897a1ebbacf9934eErik                time.minute = 0;
65373714169d34a55fc01e14b30897a1ebbacf9934eErik                time.second = 0;
65473714169d34a55fc01e14b30897a1ebbacf9934eErik                oldStartMillis = time.toMillis(false);
65573714169d34a55fc01e14b30897a1ebbacf9934eErik            }
65673714169d34a55fc01e14b30897a1ebbacf9934eErik            values.put(Events.DTSTART, oldStartMillis);
65773714169d34a55fc01e14b30897a1ebbacf9934eErik        }
65873714169d34a55fc01e14b30897a1ebbacf9934eErik    }
65973714169d34a55fc01e14b30897a1ebbacf9934eErik
66073714169d34a55fc01e14b30897a1ebbacf9934eErik    /**
661eca82e9468c002590045338f98588ad3ce1a0bb3Erik     * Prepares an update to the original event so it stops where the new series
662eca82e9468c002590045338f98588ad3ce1a0bb3Erik     * begins When we update 'this and all following' events we need to change
663eca82e9468c002590045338f98588ad3ce1a0bb3Erik     * the original event to end before a new series starts. This creates an
664eca82e9468c002590045338f98588ad3ce1a0bb3Erik     * update to the old event's rrule to do that.
66573714169d34a55fc01e14b30897a1ebbacf9934eErik     *
66673714169d34a55fc01e14b30897a1ebbacf9934eErik     * @param ops The list of operations to add the update to
66773714169d34a55fc01e14b30897a1ebbacf9934eErik     * @param originalModel The original event that we're updating
66873714169d34a55fc01e14b30897a1ebbacf9934eErik     * @param initialBeginTime The original start time for the exception
66973714169d34a55fc01e14b30897a1ebbacf9934eErik     */
670fbce65e53c7a111955f638db5bf8bee35381e5b7Erik    public void updatePastEvents(ArrayList<ContentProviderOperation> ops,
67173714169d34a55fc01e14b30897a1ebbacf9934eErik            CalendarEventModel originalModel, long initialBeginTime) {
67273714169d34a55fc01e14b30897a1ebbacf9934eErik        boolean allDay = originalModel.mAllDay;
67373714169d34a55fc01e14b30897a1ebbacf9934eErik        String oldRrule = originalModel.mRrule;
674eca82e9468c002590045338f98588ad3ce1a0bb3Erik
67573714169d34a55fc01e14b30897a1ebbacf9934eErik        EventRecurrence eventRecurrence = new EventRecurrence();
67673714169d34a55fc01e14b30897a1ebbacf9934eErik        eventRecurrence.parse(oldRrule);
67773714169d34a55fc01e14b30897a1ebbacf9934eErik
67873714169d34a55fc01e14b30897a1ebbacf9934eErik        Time untilTime = new Time();
679eca82e9468c002590045338f98588ad3ce1a0bb3Erik        Time dtstart = new Time();
68073714169d34a55fc01e14b30897a1ebbacf9934eErik        long begin = initialBeginTime;
68173714169d34a55fc01e14b30897a1ebbacf9934eErik        ContentValues oldValues = new ContentValues();
68273714169d34a55fc01e14b30897a1ebbacf9934eErik
68373714169d34a55fc01e14b30897a1ebbacf9934eErik        // The "until" time must be in UTC time in order for Google calendar
684eca82e9468c002590045338f98588ad3ce1a0bb3Erik        // to display it properly. For all-day events, the "until" time string
685eca82e9468c002590045338f98588ad3ce1a0bb3Erik        // must include just the date field, and not the time field. The
68673714169d34a55fc01e14b30897a1ebbacf9934eErik        // repeating events repeat up to and including the "until" time.
68773714169d34a55fc01e14b30897a1ebbacf9934eErik        untilTime.timezone = Time.TIMEZONE_UTC;
688eca82e9468c002590045338f98588ad3ce1a0bb3Erik        dtstart.timezone = originalModel.mTimezone;
689eca82e9468c002590045338f98588ad3ce1a0bb3Erik        dtstart.set(originalModel.mStart);
69073714169d34a55fc01e14b30897a1ebbacf9934eErik
69173714169d34a55fc01e14b30897a1ebbacf9934eErik        // Subtract one second from the old begin time to get the new
69273714169d34a55fc01e14b30897a1ebbacf9934eErik        // "until" time.
693eca82e9468c002590045338f98588ad3ce1a0bb3Erik        untilTime.set(begin - 1000); // subtract one second (1000 millis)
69473714169d34a55fc01e14b30897a1ebbacf9934eErik        if (allDay) {
69573714169d34a55fc01e14b30897a1ebbacf9934eErik            untilTime.hour = 0;
69673714169d34a55fc01e14b30897a1ebbacf9934eErik            untilTime.minute = 0;
69773714169d34a55fc01e14b30897a1ebbacf9934eErik            untilTime.second = 0;
69873714169d34a55fc01e14b30897a1ebbacf9934eErik            untilTime.allDay = true;
69973714169d34a55fc01e14b30897a1ebbacf9934eErik            untilTime.normalize(false);
700eca82e9468c002590045338f98588ad3ce1a0bb3Erik
701eca82e9468c002590045338f98588ad3ce1a0bb3Erik            dtstart.hour = 0;
702eca82e9468c002590045338f98588ad3ce1a0bb3Erik            dtstart.minute = 0;
703eca82e9468c002590045338f98588ad3ce1a0bb3Erik            dtstart.second = 0;
704eca82e9468c002590045338f98588ad3ce1a0bb3Erik            dtstart.allDay = true;
705eca82e9468c002590045338f98588ad3ce1a0bb3Erik            dtstart.timezone = Time.TIMEZONE_UTC;
70673714169d34a55fc01e14b30897a1ebbacf9934eErik        }
70773714169d34a55fc01e14b30897a1ebbacf9934eErik        eventRecurrence.until = untilTime.format2445();
70873714169d34a55fc01e14b30897a1ebbacf9934eErik
70973714169d34a55fc01e14b30897a1ebbacf9934eErik        oldValues.put(Events.RRULE, eventRecurrence.toString());
710eca82e9468c002590045338f98588ad3ce1a0bb3Erik        oldValues.put(Events.DTSTART, dtstart.normalize(true));
71159ead671b50514b49749fcd6a1d4186a270239d2Erik        ContentProviderOperation.Builder b =
71259ead671b50514b49749fcd6a1d4186a270239d2Erik                ContentProviderOperation.newUpdate(Uri.parse(originalModel.mUri))
713eca82e9468c002590045338f98588ad3ce1a0bb3Erik                .withValues(oldValues);
71473714169d34a55fc01e14b30897a1ebbacf9934eErik        ops.add(b.build());
71573714169d34a55fc01e14b30897a1ebbacf9934eErik    }
71673714169d34a55fc01e14b30897a1ebbacf9934eErik
717eca82e9468c002590045338f98588ad3ce1a0bb3Erik    // Constructs a label given an arbitrary number of minutes. For example,
71873714169d34a55fc01e14b30897a1ebbacf9934eErik    // if the given minutes is 63, then this returns the string "63 minutes".
71973714169d34a55fc01e14b30897a1ebbacf9934eErik    // As another example, if the given minutes is 120, then this returns
72073714169d34a55fc01e14b30897a1ebbacf9934eErik    // "2 hours".
721fbce65e53c7a111955f638db5bf8bee35381e5b7Erik    public String constructReminderLabel(int minutes, boolean abbrev) {
722dd95df57c8c5a58a85c4c0effad5652dec14f621Erik        Resources resources = mContext.getResources();
72373714169d34a55fc01e14b30897a1ebbacf9934eErik        int value, resId;
72473714169d34a55fc01e14b30897a1ebbacf9934eErik
72573714169d34a55fc01e14b30897a1ebbacf9934eErik        if (minutes % 60 != 0) {
72673714169d34a55fc01e14b30897a1ebbacf9934eErik            value = minutes;
72773714169d34a55fc01e14b30897a1ebbacf9934eErik            if (abbrev) {
72873714169d34a55fc01e14b30897a1ebbacf9934eErik                resId = R.plurals.Nmins;
72973714169d34a55fc01e14b30897a1ebbacf9934eErik            } else {
73073714169d34a55fc01e14b30897a1ebbacf9934eErik                resId = R.plurals.Nminutes;
73173714169d34a55fc01e14b30897a1ebbacf9934eErik            }
73273714169d34a55fc01e14b30897a1ebbacf9934eErik        } else if (minutes % (24 * 60) != 0) {
73373714169d34a55fc01e14b30897a1ebbacf9934eErik            value = minutes / 60;
73473714169d34a55fc01e14b30897a1ebbacf9934eErik            resId = R.plurals.Nhours;
73573714169d34a55fc01e14b30897a1ebbacf9934eErik        } else {
736eca82e9468c002590045338f98588ad3ce1a0bb3Erik            value = minutes / (24 * 60);
73773714169d34a55fc01e14b30897a1ebbacf9934eErik            resId = R.plurals.Ndays;
73873714169d34a55fc01e14b30897a1ebbacf9934eErik        }
73973714169d34a55fc01e14b30897a1ebbacf9934eErik
74073714169d34a55fc01e14b30897a1ebbacf9934eErik        String format = resources.getQuantityString(resId, value);
74173714169d34a55fc01e14b30897a1ebbacf9934eErik        return String.format(format, value);
74273714169d34a55fc01e14b30897a1ebbacf9934eErik    }
74373714169d34a55fc01e14b30897a1ebbacf9934eErik
74473714169d34a55fc01e14b30897a1ebbacf9934eErik    /**
745eca82e9468c002590045338f98588ad3ce1a0bb3Erik     * Compares two models to ensure that they refer to the same event. This is
746eca82e9468c002590045338f98588ad3ce1a0bb3Erik     * a safety check to make sure an updated event model refers to the same
747eca82e9468c002590045338f98588ad3ce1a0bb3Erik     * event as the original model. If the original model is null then this is a
748eca82e9468c002590045338f98588ad3ce1a0bb3Erik     * new event or we're forcing an overwrite so we return true in that case.
749eca82e9468c002590045338f98588ad3ce1a0bb3Erik     * The important identifiers are the Calendar Id and the Event Id.
75073714169d34a55fc01e14b30897a1ebbacf9934eErik     *
75173714169d34a55fc01e14b30897a1ebbacf9934eErik     * @return
75273714169d34a55fc01e14b30897a1ebbacf9934eErik     */
753fbce65e53c7a111955f638db5bf8bee35381e5b7Erik    public static boolean isSameEvent(CalendarEventModel model, CalendarEventModel originalModel) {
75473714169d34a55fc01e14b30897a1ebbacf9934eErik        if (originalModel == null) {
75573714169d34a55fc01e14b30897a1ebbacf9934eErik            return true;
75673714169d34a55fc01e14b30897a1ebbacf9934eErik        }
75773714169d34a55fc01e14b30897a1ebbacf9934eErik
75873714169d34a55fc01e14b30897a1ebbacf9934eErik        if (model.mCalendarId != originalModel.mCalendarId) {
75973714169d34a55fc01e14b30897a1ebbacf9934eErik            return false;
76073714169d34a55fc01e14b30897a1ebbacf9934eErik        }
76173714169d34a55fc01e14b30897a1ebbacf9934eErik        if (model.mId != originalModel.mId) {
76273714169d34a55fc01e14b30897a1ebbacf9934eErik            return false;
76373714169d34a55fc01e14b30897a1ebbacf9934eErik        }
76473714169d34a55fc01e14b30897a1ebbacf9934eErik
76573714169d34a55fc01e14b30897a1ebbacf9934eErik        return true;
76673714169d34a55fc01e14b30897a1ebbacf9934eErik    }
76773714169d34a55fc01e14b30897a1ebbacf9934eErik
768eca82e9468c002590045338f98588ad3ce1a0bb3Erik    /**
769eca82e9468c002590045338f98588ad3ce1a0bb3Erik     * Saves the reminders, if they changed. Returns true if operations to
770eca82e9468c002590045338f98588ad3ce1a0bb3Erik     * update the database were added.
77173714169d34a55fc01e14b30897a1ebbacf9934eErik     *
77273714169d34a55fc01e14b30897a1ebbacf9934eErik     * @param ops the array of ContentProviderOperations
77373714169d34a55fc01e14b30897a1ebbacf9934eErik     * @param eventId the id of the event whose reminders are being updated
77473714169d34a55fc01e14b30897a1ebbacf9934eErik     * @param reminderMinutes the array of reminders set by the user
77573714169d34a55fc01e14b30897a1ebbacf9934eErik     * @param originalMinutes the original array of reminders
776eca82e9468c002590045338f98588ad3ce1a0bb3Erik     * @param forceSave if true, then save the reminders even if they didn't change
77773714169d34a55fc01e14b30897a1ebbacf9934eErik     * @return true if operations to update the database were added
77873714169d34a55fc01e14b30897a1ebbacf9934eErik     */
779fbce65e53c7a111955f638db5bf8bee35381e5b7Erik    public static boolean saveReminders(ArrayList<ContentProviderOperation> ops, long eventId,
78073714169d34a55fc01e14b30897a1ebbacf9934eErik            ArrayList<Integer> reminderMinutes, ArrayList<Integer> originalMinutes,
78173714169d34a55fc01e14b30897a1ebbacf9934eErik            boolean forceSave) {
78273714169d34a55fc01e14b30897a1ebbacf9934eErik        // If the reminders have not changed, then don't update the database
78373714169d34a55fc01e14b30897a1ebbacf9934eErik        if (reminderMinutes.equals(originalMinutes) && !forceSave) {
78473714169d34a55fc01e14b30897a1ebbacf9934eErik            return false;
78573714169d34a55fc01e14b30897a1ebbacf9934eErik        }
78673714169d34a55fc01e14b30897a1ebbacf9934eErik
78773714169d34a55fc01e14b30897a1ebbacf9934eErik        // Delete all the existing reminders for this event
78873714169d34a55fc01e14b30897a1ebbacf9934eErik        String where = Reminders.EVENT_ID + "=?";
78973714169d34a55fc01e14b30897a1ebbacf9934eErik        String[] args = new String[] {Long.toString(eventId)};
790eca82e9468c002590045338f98588ad3ce1a0bb3Erik        ContentProviderOperation.Builder b = ContentProviderOperation
791eca82e9468c002590045338f98588ad3ce1a0bb3Erik                .newDelete(Reminders.CONTENT_URI);
79273714169d34a55fc01e14b30897a1ebbacf9934eErik        b.withSelection(where, args);
79373714169d34a55fc01e14b30897a1ebbacf9934eErik        ops.add(b.build());
79473714169d34a55fc01e14b30897a1ebbacf9934eErik
79573714169d34a55fc01e14b30897a1ebbacf9934eErik        ContentValues values = new ContentValues();
79673714169d34a55fc01e14b30897a1ebbacf9934eErik        int len = reminderMinutes.size();
79773714169d34a55fc01e14b30897a1ebbacf9934eErik
79873714169d34a55fc01e14b30897a1ebbacf9934eErik        // Insert the new reminders, if any
79973714169d34a55fc01e14b30897a1ebbacf9934eErik        for (int i = 0; i < len; i++) {
80073714169d34a55fc01e14b30897a1ebbacf9934eErik            int minutes = reminderMinutes.get(i);
80173714169d34a55fc01e14b30897a1ebbacf9934eErik
80273714169d34a55fc01e14b30897a1ebbacf9934eErik            values.clear();
80373714169d34a55fc01e14b30897a1ebbacf9934eErik            values.put(Reminders.MINUTES, minutes);
80473714169d34a55fc01e14b30897a1ebbacf9934eErik            values.put(Reminders.METHOD, Reminders.METHOD_ALERT);
80573714169d34a55fc01e14b30897a1ebbacf9934eErik            values.put(Reminders.EVENT_ID, eventId);
80673714169d34a55fc01e14b30897a1ebbacf9934eErik            b = ContentProviderOperation.newInsert(Reminders.CONTENT_URI).withValues(values);
80773714169d34a55fc01e14b30897a1ebbacf9934eErik            ops.add(b.build());
80873714169d34a55fc01e14b30897a1ebbacf9934eErik        }
80973714169d34a55fc01e14b30897a1ebbacf9934eErik        return true;
81073714169d34a55fc01e14b30897a1ebbacf9934eErik    }
81173714169d34a55fc01e14b30897a1ebbacf9934eErik
81273714169d34a55fc01e14b30897a1ebbacf9934eErik    /**
813eca82e9468c002590045338f98588ad3ce1a0bb3Erik     * Saves the reminders, if they changed. Returns true if operations to
814eca82e9468c002590045338f98588ad3ce1a0bb3Erik     * update the database were added. Uses a reference id since an id isn't
815eca82e9468c002590045338f98588ad3ce1a0bb3Erik     * created until the row is added.
81673714169d34a55fc01e14b30897a1ebbacf9934eErik     *
81773714169d34a55fc01e14b30897a1ebbacf9934eErik     * @param ops the array of ContentProviderOperations
81873714169d34a55fc01e14b30897a1ebbacf9934eErik     * @param eventId the id of the event whose reminders are being updated
81973714169d34a55fc01e14b30897a1ebbacf9934eErik     * @param reminderMinutes the array of reminders set by the user
82073714169d34a55fc01e14b30897a1ebbacf9934eErik     * @param originalMinutes the original array of reminders
821eca82e9468c002590045338f98588ad3ce1a0bb3Erik     * @param forceSave if true, then save the reminders even if they didn't change
82273714169d34a55fc01e14b30897a1ebbacf9934eErik     * @return true if operations to update the database were added
82373714169d34a55fc01e14b30897a1ebbacf9934eErik     */
824d845fbe558d5229102b58cce70a1b29fe6cb6967Erik    public boolean saveRemindersWithBackRef(ArrayList<ContentProviderOperation> ops,
825d845fbe558d5229102b58cce70a1b29fe6cb6967Erik            int eventIdIndex, ArrayList<Integer> reminderMinutes,
826d845fbe558d5229102b58cce70a1b29fe6cb6967Erik            ArrayList<Integer> originalMinutes, boolean forceSave) {
82773714169d34a55fc01e14b30897a1ebbacf9934eErik        // If the reminders have not changed, then don't update the database
82873714169d34a55fc01e14b30897a1ebbacf9934eErik        if (reminderMinutes.equals(originalMinutes) && !forceSave) {
82973714169d34a55fc01e14b30897a1ebbacf9934eErik            return false;
83073714169d34a55fc01e14b30897a1ebbacf9934eErik        }
83173714169d34a55fc01e14b30897a1ebbacf9934eErik
83273714169d34a55fc01e14b30897a1ebbacf9934eErik        // Delete all the existing reminders for this event
833eca82e9468c002590045338f98588ad3ce1a0bb3Erik        ContentProviderOperation.Builder b = ContentProviderOperation
834eca82e9468c002590045338f98588ad3ce1a0bb3Erik                .newDelete(Reminders.CONTENT_URI);
83573714169d34a55fc01e14b30897a1ebbacf9934eErik        b.withSelection(Reminders.EVENT_ID + "=?", new String[1]);
83673714169d34a55fc01e14b30897a1ebbacf9934eErik        b.withSelectionBackReference(0, eventIdIndex);
83773714169d34a55fc01e14b30897a1ebbacf9934eErik        ops.add(b.build());
83873714169d34a55fc01e14b30897a1ebbacf9934eErik
83973714169d34a55fc01e14b30897a1ebbacf9934eErik        ContentValues values = new ContentValues();
84073714169d34a55fc01e14b30897a1ebbacf9934eErik        int len = reminderMinutes.size();
84173714169d34a55fc01e14b30897a1ebbacf9934eErik
84273714169d34a55fc01e14b30897a1ebbacf9934eErik        // Insert the new reminders, if any
84373714169d34a55fc01e14b30897a1ebbacf9934eErik        for (int i = 0; i < len; i++) {
84473714169d34a55fc01e14b30897a1ebbacf9934eErik            int minutes = reminderMinutes.get(i);
84573714169d34a55fc01e14b30897a1ebbacf9934eErik
84673714169d34a55fc01e14b30897a1ebbacf9934eErik            values.clear();
84773714169d34a55fc01e14b30897a1ebbacf9934eErik            values.put(Reminders.MINUTES, minutes);
84873714169d34a55fc01e14b30897a1ebbacf9934eErik            values.put(Reminders.METHOD, Reminders.METHOD_ALERT);
84973714169d34a55fc01e14b30897a1ebbacf9934eErik            b = ContentProviderOperation.newInsert(Reminders.CONTENT_URI).withValues(values);
85073714169d34a55fc01e14b30897a1ebbacf9934eErik            b.withValueBackReference(Reminders.EVENT_ID, eventIdIndex);
85173714169d34a55fc01e14b30897a1ebbacf9934eErik            ops.add(b.build());
85273714169d34a55fc01e14b30897a1ebbacf9934eErik        }
85373714169d34a55fc01e14b30897a1ebbacf9934eErik        return true;
85473714169d34a55fc01e14b30897a1ebbacf9934eErik    }
85573714169d34a55fc01e14b30897a1ebbacf9934eErik
856eca82e9468c002590045338f98588ad3ce1a0bb3Erik    // It's the first event in the series if the start time before being
857eca82e9468c002590045338f98588ad3ce1a0bb3Erik    // modified is the same as the original event's start time
85873714169d34a55fc01e14b30897a1ebbacf9934eErik    static boolean isFirstEventInSeries(CalendarEventModel model,
85973714169d34a55fc01e14b30897a1ebbacf9934eErik            CalendarEventModel originalModel) {
86073714169d34a55fc01e14b30897a1ebbacf9934eErik        return model.mOriginalStart == originalModel.mStart;
86173714169d34a55fc01e14b30897a1ebbacf9934eErik    }
86273714169d34a55fc01e14b30897a1ebbacf9934eErik
86373714169d34a55fc01e14b30897a1ebbacf9934eErik    // Adds an rRule and duration to a set of content values
86473714169d34a55fc01e14b30897a1ebbacf9934eErik    void addRecurrenceRule(ContentValues values, CalendarEventModel model) {
86573714169d34a55fc01e14b30897a1ebbacf9934eErik        String rrule = model.mRrule;
86673714169d34a55fc01e14b30897a1ebbacf9934eErik
86773714169d34a55fc01e14b30897a1ebbacf9934eErik        values.put(Events.RRULE, rrule);
86873714169d34a55fc01e14b30897a1ebbacf9934eErik        long end = model.mEnd;
86973714169d34a55fc01e14b30897a1ebbacf9934eErik        long start = model.mStart;
87073714169d34a55fc01e14b30897a1ebbacf9934eErik        String duration = model.mDuration;
87173714169d34a55fc01e14b30897a1ebbacf9934eErik
87237960c091b5912dd45beb561a85a9064ea42e616Erik        boolean isAllDay = model.mAllDay;
87337960c091b5912dd45beb561a85a9064ea42e616Erik        if (end > start) {
87437960c091b5912dd45beb561a85a9064ea42e616Erik            if (isAllDay) {
87537960c091b5912dd45beb561a85a9064ea42e616Erik                // if it's all day compute the duration in days
87637960c091b5912dd45beb561a85a9064ea42e616Erik                long days = (end - start + DateUtils.DAY_IN_MILLIS - 1)
87737960c091b5912dd45beb561a85a9064ea42e616Erik                        / DateUtils.DAY_IN_MILLIS;
87837960c091b5912dd45beb561a85a9064ea42e616Erik                duration = "P" + days + "D";
87973714169d34a55fc01e14b30897a1ebbacf9934eErik            } else {
88037960c091b5912dd45beb561a85a9064ea42e616Erik                // otherwise compute the duration in seconds
88137960c091b5912dd45beb561a85a9064ea42e616Erik                long seconds = (end - start) / DateUtils.SECOND_IN_MILLIS;
88237960c091b5912dd45beb561a85a9064ea42e616Erik                duration = "P" + seconds + "S";
88337960c091b5912dd45beb561a85a9064ea42e616Erik            }
88437960c091b5912dd45beb561a85a9064ea42e616Erik        } else if (TextUtils.isEmpty(duration)) {
88537960c091b5912dd45beb561a85a9064ea42e616Erik
88637960c091b5912dd45beb561a85a9064ea42e616Erik            // If no good duration info exists assume the default
88737960c091b5912dd45beb561a85a9064ea42e616Erik            if (isAllDay) {
88837960c091b5912dd45beb561a85a9064ea42e616Erik                duration = "P1D";
88937960c091b5912dd45beb561a85a9064ea42e616Erik            } else {
89037960c091b5912dd45beb561a85a9064ea42e616Erik                duration = "P3600S";
89173714169d34a55fc01e14b30897a1ebbacf9934eErik            }
89273714169d34a55fc01e14b30897a1ebbacf9934eErik        }
89373714169d34a55fc01e14b30897a1ebbacf9934eErik        // recurring events should have a duration and dtend set to null
89473714169d34a55fc01e14b30897a1ebbacf9934eErik        values.put(Events.DURATION, duration);
895eca82e9468c002590045338f98588ad3ce1a0bb3Erik        values.put(Events.DTEND, (Long) null);
89673714169d34a55fc01e14b30897a1ebbacf9934eErik    }
89773714169d34a55fc01e14b30897a1ebbacf9934eErik
89873714169d34a55fc01e14b30897a1ebbacf9934eErik    /**
899eca82e9468c002590045338f98588ad3ce1a0bb3Erik     * Uses the recurrence selection and the model data to build an rrule and
900eca82e9468c002590045338f98588ad3ce1a0bb3Erik     * write it to the model.
90173714169d34a55fc01e14b30897a1ebbacf9934eErik     *
90273714169d34a55fc01e14b30897a1ebbacf9934eErik     * @param selection the type of rrule
90373714169d34a55fc01e14b30897a1ebbacf9934eErik     * @param model The event to update
9048e3d430a020744faa21bf4ca24f1a99c36ec5c4fMason Tang     * @param weekStart the week start day, specified as java.util.Calendar
9058e3d430a020744faa21bf4ca24f1a99c36ec5c4fMason Tang     * constants
90673714169d34a55fc01e14b30897a1ebbacf9934eErik     */
9078e3d430a020744faa21bf4ca24f1a99c36ec5c4fMason Tang    static void updateRecurrenceRule(int selection, CalendarEventModel model,
9088e3d430a020744faa21bf4ca24f1a99c36ec5c4fMason Tang            int weekStart) {
90973714169d34a55fc01e14b30897a1ebbacf9934eErik        // Make sure we don't have any leftover data from the previous setting
91073714169d34a55fc01e14b30897a1ebbacf9934eErik        EventRecurrence eventRecurrence = new EventRecurrence();
91173714169d34a55fc01e14b30897a1ebbacf9934eErik
91273714169d34a55fc01e14b30897a1ebbacf9934eErik        if (selection == DOES_NOT_REPEAT) {
91373714169d34a55fc01e14b30897a1ebbacf9934eErik            model.mRrule = null;
91473714169d34a55fc01e14b30897a1ebbacf9934eErik            return;
91573714169d34a55fc01e14b30897a1ebbacf9934eErik        } else if (selection == REPEATS_CUSTOM) {
91673714169d34a55fc01e14b30897a1ebbacf9934eErik            // Keep custom recurrence as before.
91773714169d34a55fc01e14b30897a1ebbacf9934eErik            return;
91873714169d34a55fc01e14b30897a1ebbacf9934eErik        } else if (selection == REPEATS_DAILY) {
91973714169d34a55fc01e14b30897a1ebbacf9934eErik            eventRecurrence.freq = EventRecurrence.DAILY;
92073714169d34a55fc01e14b30897a1ebbacf9934eErik        } else if (selection == REPEATS_EVERY_WEEKDAY) {
92173714169d34a55fc01e14b30897a1ebbacf9934eErik            eventRecurrence.freq = EventRecurrence.WEEKLY;
92273714169d34a55fc01e14b30897a1ebbacf9934eErik            int dayCount = 5;
92373714169d34a55fc01e14b30897a1ebbacf9934eErik            int[] byday = new int[dayCount];
92473714169d34a55fc01e14b30897a1ebbacf9934eErik            int[] bydayNum = new int[dayCount];
92573714169d34a55fc01e14b30897a1ebbacf9934eErik
92673714169d34a55fc01e14b30897a1ebbacf9934eErik            byday[0] = EventRecurrence.MO;
92773714169d34a55fc01e14b30897a1ebbacf9934eErik            byday[1] = EventRecurrence.TU;
92873714169d34a55fc01e14b30897a1ebbacf9934eErik            byday[2] = EventRecurrence.WE;
92973714169d34a55fc01e14b30897a1ebbacf9934eErik            byday[3] = EventRecurrence.TH;
93073714169d34a55fc01e14b30897a1ebbacf9934eErik            byday[4] = EventRecurrence.FR;
93173714169d34a55fc01e14b30897a1ebbacf9934eErik            for (int day = 0; day < dayCount; day++) {
93273714169d34a55fc01e14b30897a1ebbacf9934eErik                bydayNum[day] = 0;
93373714169d34a55fc01e14b30897a1ebbacf9934eErik            }
93473714169d34a55fc01e14b30897a1ebbacf9934eErik
93573714169d34a55fc01e14b30897a1ebbacf9934eErik            eventRecurrence.byday = byday;
93673714169d34a55fc01e14b30897a1ebbacf9934eErik            eventRecurrence.bydayNum = bydayNum;
93773714169d34a55fc01e14b30897a1ebbacf9934eErik            eventRecurrence.bydayCount = dayCount;
93873714169d34a55fc01e14b30897a1ebbacf9934eErik        } else if (selection == REPEATS_WEEKLY_ON_DAY) {
93973714169d34a55fc01e14b30897a1ebbacf9934eErik            eventRecurrence.freq = EventRecurrence.WEEKLY;
94073714169d34a55fc01e14b30897a1ebbacf9934eErik            int[] days = new int[1];
94173714169d34a55fc01e14b30897a1ebbacf9934eErik            int dayCount = 1;
94273714169d34a55fc01e14b30897a1ebbacf9934eErik            int[] dayNum = new int[dayCount];
94373714169d34a55fc01e14b30897a1ebbacf9934eErik            Time startTime = new Time(model.mTimezone);
94473714169d34a55fc01e14b30897a1ebbacf9934eErik            startTime.set(model.mStart);
94573714169d34a55fc01e14b30897a1ebbacf9934eErik
94673714169d34a55fc01e14b30897a1ebbacf9934eErik            days[0] = EventRecurrence.timeDay2Day(startTime.weekDay);
94773714169d34a55fc01e14b30897a1ebbacf9934eErik            // not sure why this needs to be zero, but set it for now.
94873714169d34a55fc01e14b30897a1ebbacf9934eErik            dayNum[0] = 0;
94973714169d34a55fc01e14b30897a1ebbacf9934eErik
95073714169d34a55fc01e14b30897a1ebbacf9934eErik            eventRecurrence.byday = days;
95173714169d34a55fc01e14b30897a1ebbacf9934eErik            eventRecurrence.bydayNum = dayNum;
95273714169d34a55fc01e14b30897a1ebbacf9934eErik            eventRecurrence.bydayCount = dayCount;
95373714169d34a55fc01e14b30897a1ebbacf9934eErik        } else if (selection == REPEATS_MONTHLY_ON_DAY) {
95473714169d34a55fc01e14b30897a1ebbacf9934eErik            eventRecurrence.freq = EventRecurrence.MONTHLY;
95573714169d34a55fc01e14b30897a1ebbacf9934eErik            eventRecurrence.bydayCount = 0;
95673714169d34a55fc01e14b30897a1ebbacf9934eErik            eventRecurrence.bymonthdayCount = 1;
95773714169d34a55fc01e14b30897a1ebbacf9934eErik            int[] bymonthday = new int[1];
95873714169d34a55fc01e14b30897a1ebbacf9934eErik            Time startTime = new Time(model.mTimezone);
95973714169d34a55fc01e14b30897a1ebbacf9934eErik            startTime.set(model.mStart);
96073714169d34a55fc01e14b30897a1ebbacf9934eErik            bymonthday[0] = startTime.monthDay;
96173714169d34a55fc01e14b30897a1ebbacf9934eErik            eventRecurrence.bymonthday = bymonthday;
96273714169d34a55fc01e14b30897a1ebbacf9934eErik        } else if (selection == REPEATS_MONTHLY_ON_DAY_COUNT) {
96373714169d34a55fc01e14b30897a1ebbacf9934eErik            eventRecurrence.freq = EventRecurrence.MONTHLY;
96473714169d34a55fc01e14b30897a1ebbacf9934eErik            eventRecurrence.bydayCount = 1;
96573714169d34a55fc01e14b30897a1ebbacf9934eErik            eventRecurrence.bymonthdayCount = 0;
96673714169d34a55fc01e14b30897a1ebbacf9934eErik
96773714169d34a55fc01e14b30897a1ebbacf9934eErik            int[] byday = new int[1];
96873714169d34a55fc01e14b30897a1ebbacf9934eErik            int[] bydayNum = new int[1];
96973714169d34a55fc01e14b30897a1ebbacf9934eErik            Time startTime = new Time(model.mTimezone);
97073714169d34a55fc01e14b30897a1ebbacf9934eErik            startTime.set(model.mStart);
97173714169d34a55fc01e14b30897a1ebbacf9934eErik            // Compute the week number (for example, the "2nd" Monday)
97273714169d34a55fc01e14b30897a1ebbacf9934eErik            int dayCount = 1 + ((startTime.monthDay - 1) / 7);
97373714169d34a55fc01e14b30897a1ebbacf9934eErik            if (dayCount == 5) {
97473714169d34a55fc01e14b30897a1ebbacf9934eErik                dayCount = -1;
97573714169d34a55fc01e14b30897a1ebbacf9934eErik            }
97673714169d34a55fc01e14b30897a1ebbacf9934eErik            bydayNum[0] = dayCount;
97773714169d34a55fc01e14b30897a1ebbacf9934eErik            byday[0] = EventRecurrence.timeDay2Day(startTime.weekDay);
97873714169d34a55fc01e14b30897a1ebbacf9934eErik            eventRecurrence.byday = byday;
97973714169d34a55fc01e14b30897a1ebbacf9934eErik            eventRecurrence.bydayNum = bydayNum;
98073714169d34a55fc01e14b30897a1ebbacf9934eErik        } else if (selection == REPEATS_YEARLY) {
98173714169d34a55fc01e14b30897a1ebbacf9934eErik            eventRecurrence.freq = EventRecurrence.YEARLY;
98273714169d34a55fc01e14b30897a1ebbacf9934eErik        }
98373714169d34a55fc01e14b30897a1ebbacf9934eErik
98473714169d34a55fc01e14b30897a1ebbacf9934eErik        // Set the week start day.
9858e3d430a020744faa21bf4ca24f1a99c36ec5c4fMason Tang        eventRecurrence.wkst = EventRecurrence.calendarDay2Day(weekStart);
98673714169d34a55fc01e14b30897a1ebbacf9934eErik        model.mRrule = eventRecurrence.toString();
98773714169d34a55fc01e14b30897a1ebbacf9934eErik    }
98873714169d34a55fc01e14b30897a1ebbacf9934eErik
98973714169d34a55fc01e14b30897a1ebbacf9934eErik    /**
990eca82e9468c002590045338f98588ad3ce1a0bb3Erik     * Uses an event cursor to fill in the given model This method assumes the
991eca82e9468c002590045338f98588ad3ce1a0bb3Erik     * cursor used {@link #EVENT_PROJECTION} as it's query projection. It uses
992eca82e9468c002590045338f98588ad3ce1a0bb3Erik     * the cursor to fill in the given model with all the information available.
99373714169d34a55fc01e14b30897a1ebbacf9934eErik     *
99473714169d34a55fc01e14b30897a1ebbacf9934eErik     * @param model The model to fill in
99573714169d34a55fc01e14b30897a1ebbacf9934eErik     * @param cursor An event cursor that used {@link #EVENT_PROJECTION} for the query
99673714169d34a55fc01e14b30897a1ebbacf9934eErik     */
99788c483a0189cbe7132330cb1b563f07326f3bcf9Erik    public static void setModelFromCursor(CalendarEventModel model, Cursor cursor) {
99873714169d34a55fc01e14b30897a1ebbacf9934eErik        if (model == null || cursor == null || cursor.getCount() != 1) {
99973714169d34a55fc01e14b30897a1ebbacf9934eErik            Log.wtf(TAG, "Attempted to build non-existent model or from an incorrect query.");
100073714169d34a55fc01e14b30897a1ebbacf9934eErik            return;
100173714169d34a55fc01e14b30897a1ebbacf9934eErik        }
100273714169d34a55fc01e14b30897a1ebbacf9934eErik
100373714169d34a55fc01e14b30897a1ebbacf9934eErik        model.clear();
100473714169d34a55fc01e14b30897a1ebbacf9934eErik        cursor.moveToFirst();
100573714169d34a55fc01e14b30897a1ebbacf9934eErik
100673714169d34a55fc01e14b30897a1ebbacf9934eErik        model.mId = cursor.getInt(EVENT_INDEX_ID);
100773714169d34a55fc01e14b30897a1ebbacf9934eErik        model.mTitle = cursor.getString(EVENT_INDEX_TITLE);
100873714169d34a55fc01e14b30897a1ebbacf9934eErik        model.mDescription = cursor.getString(EVENT_INDEX_DESCRIPTION);
100973714169d34a55fc01e14b30897a1ebbacf9934eErik        model.mLocation = cursor.getString(EVENT_INDEX_EVENT_LOCATION);
101073714169d34a55fc01e14b30897a1ebbacf9934eErik        model.mAllDay = cursor.getInt(EVENT_INDEX_ALL_DAY) != 0;
101173714169d34a55fc01e14b30897a1ebbacf9934eErik        model.mHasAlarm = cursor.getInt(EVENT_INDEX_HAS_ALARM) != 0;
101273714169d34a55fc01e14b30897a1ebbacf9934eErik        model.mCalendarId = cursor.getInt(EVENT_INDEX_CALENDAR_ID);
101373714169d34a55fc01e14b30897a1ebbacf9934eErik        model.mStart = cursor.getLong(EVENT_INDEX_DTSTART);
101473714169d34a55fc01e14b30897a1ebbacf9934eErik        model.mTimezone = cursor.getString(EVENT_INDEX_TIMEZONE);
101573714169d34a55fc01e14b30897a1ebbacf9934eErik        String rRule = cursor.getString(EVENT_INDEX_RRULE);
101673714169d34a55fc01e14b30897a1ebbacf9934eErik        model.mRrule = rRule;
101773714169d34a55fc01e14b30897a1ebbacf9934eErik        model.mSyncId = cursor.getString(EVENT_INDEX_SYNC_ID);
101873714169d34a55fc01e14b30897a1ebbacf9934eErik        model.mTransparency = cursor.getInt(EVENT_INDEX_TRANSPARENCY) != 0;
101973714169d34a55fc01e14b30897a1ebbacf9934eErik        int visibility = cursor.getInt(EVENT_INDEX_VISIBILITY);
102073714169d34a55fc01e14b30897a1ebbacf9934eErik        model.mOwnerAccount = cursor.getString(EVENT_INDEX_OWNER_ACCOUNT);
102173714169d34a55fc01e14b30897a1ebbacf9934eErik        model.mHasAttendeeData = cursor.getInt(EVENT_INDEX_HAS_ATTENDEE_DATA) != 0;
1022eca82e9468c002590045338f98588ad3ce1a0bb3Erik        model.mOriginalEvent = cursor.getString(EVENT_INDEX_ORIGINAL_EVENT);
1023352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan        model.mOrganizer = cursor.getString(EVENT_INDEX_ORGANIZER);
1024352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan        model.mIsOrganizer = model.mOwnerAccount.equalsIgnoreCase(model.mOrganizer);
1025352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan        model.mGuestsCanModify = cursor.getInt(EVENT_INDEX_GUESTS_CAN_MODIFY) != 0;
102673714169d34a55fc01e14b30897a1ebbacf9934eErik
102773714169d34a55fc01e14b30897a1ebbacf9934eErik        if (visibility > 0) {
1028eca82e9468c002590045338f98588ad3ce1a0bb3Erik            // For now the array contains the values 0, 2, and 3. We subtract
1029eca82e9468c002590045338f98588ad3ce1a0bb3Erik            // one to make it easier to handle in code as 0,1,2.
103073714169d34a55fc01e14b30897a1ebbacf9934eErik            // Default (0), Private (1), Public (2)
103173714169d34a55fc01e14b30897a1ebbacf9934eErik            visibility--;
103273714169d34a55fc01e14b30897a1ebbacf9934eErik        }
103373714169d34a55fc01e14b30897a1ebbacf9934eErik        model.mVisibility = visibility;
103473714169d34a55fc01e14b30897a1ebbacf9934eErik
103573714169d34a55fc01e14b30897a1ebbacf9934eErik        boolean hasRRule = !TextUtils.isEmpty(rRule);
103673714169d34a55fc01e14b30897a1ebbacf9934eErik
103773714169d34a55fc01e14b30897a1ebbacf9934eErik        // We expect only one of these, so ignore the other
103873714169d34a55fc01e14b30897a1ebbacf9934eErik        if (hasRRule) {
103973714169d34a55fc01e14b30897a1ebbacf9934eErik            model.mDuration = cursor.getString(EVENT_INDEX_DURATION);
104073714169d34a55fc01e14b30897a1ebbacf9934eErik        } else {
104173714169d34a55fc01e14b30897a1ebbacf9934eErik            model.mEnd = cursor.getLong(EVENT_INDEX_DTEND);
104273714169d34a55fc01e14b30897a1ebbacf9934eErik        }
1043352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan
1044352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan        model.mModelUpdatedWithEventCursor = true;
1045352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan    }
1046352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan
1047352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan    /**
1048352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan     * Uses a calendar cursor to fill in the given model This method assumes the
1049352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan     * cursor used {@link #CALENDARS_PROJECTION} as it's query projection. It uses
1050352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan     * the cursor to fill in the given model with all the information available.
1051352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan     *
1052352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan     * @param model The model to fill in
1053352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan     * @param cursor An event cursor that used {@link #CALENDARS_PROJECTION} for the query
1054352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan     * @return returns true if model was updated with the info in the cursor.
1055352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan     */
1056352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan    public static boolean setModelFromCalendarCursor(CalendarEventModel model, Cursor cursor) {
1057352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan        if (model == null || cursor == null) {
1058352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan            Log.wtf(TAG, "Attempted to build non-existent model or from an incorrect query.");
1059352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan            return false;
1060352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan        }
1061352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan
1062352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan        if (model.mCalendarId == -1) {
1063352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan            return false;
1064352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan        }
1065352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan
1066352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan        if (!model.mModelUpdatedWithEventCursor) {
1067352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan            Log.wtf(TAG,
1068352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan                    "Can't update model with a Calendar cursor until it has seen an Event cursor.");
1069352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan            return false;
1070352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan        }
1071352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan
1072352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan        cursor.moveToPosition(-1);
1073352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan        while (cursor.moveToNext()) {
1074ddbf80860566df8425f38a32190e9a64cad4e389Michael Chan            if (model.mCalendarId != cursor.getInt(CALENDARS_INDEX_ID)) {
1075352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan                continue;
1076352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan            }
1077352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan
1078352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan            model.mOrganizerCanRespond = cursor.getInt(CALENDARS_INDEX_ORGANIZER_CAN_RESPOND) != 0;
1079352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan
1080352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan            model.mCalendarAccessLevel = cursor.getInt(CALENDARS_INDEX_ACCESS_LEVEL);
10819d5f351e45e20ff04da6f9118031cc2cc7419905Michael Chan            model.mCalendarDisplayName = cursor.getString(CALENDARS_INDEX_DISPLAY_NAME);
10823400a2bfc1e6be435ea2bb0f436a6ef338446504Erik            model.mCalendarColor = cursor.getInt(CALENDARS_INDEX_COLOR);
1083352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan
1084352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan            return true;
1085352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan       }
1086352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan       return false;
1087352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan    }
1088352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan
1089352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan    public static boolean canModifyEvent(CalendarEventModel model) {
1090352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan        return canModifyCalendar(model)
1091352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan                && (model.mIsOrganizer || model.mGuestsCanModify);
1092352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan    }
1093352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan
1094352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan    public static boolean canModifyCalendar(CalendarEventModel model) {
1095352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan        return model.mCalendarAccessLevel >= Calendars.CONTRIBUTOR_ACCESS
1096352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan                || model.mCalendarId == -1;
1097352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan    }
1098352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan
10995f62079ab9daefe95ede87631766e99e8b4a2220Erik    public static boolean canAddReminders(CalendarEventModel model) {
11005f62079ab9daefe95ede87631766e99e8b4a2220Erik        return model.mCalendarAccessLevel >= Calendars.READ_ACCESS;
11015f62079ab9daefe95ede87631766e99e8b4a2220Erik    }
11025f62079ab9daefe95ede87631766e99e8b4a2220Erik
1103352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan    public static boolean canRespond(CalendarEventModel model) {
1104352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan        // For non-organizers, write permission to the calendar is sufficient.
1105352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan        // For organizers, the user needs a) write permission to the calendar
1106352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan        // AND b) ownerCanRespond == true AND c) attendee data exist
1107352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan        // (this means num of attendees > 1, the calendar owner's and others).
1108352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan        // Note that mAttendeeList omits the organizer.
1109352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan
1110352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan        // (there are more cases involved to be 100% accurate, such as
1111352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan        // paying attention to whether or not an attendee status was
1112352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan        // included in the feed, but we're currently omitting those corner cases
1113352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan        // for simplicity).
1114352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan
1115352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan        if (!canModifyCalendar(model)) {
1116352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan            return false;
1117352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan        }
1118352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan
1119352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan        if (!model.mIsOrganizer) {
1120352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan            return true;
1121352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan        }
1122352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan
1123352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan        if (!model.mOrganizerCanRespond) {
1124352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan            return false;
1125352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan        }
1126352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan
1127352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan        // This means we don't have the attendees data so we can't send
1128352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan        // the list of attendees and the status back to the server
1129352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan        if (model.mHasAttendeeData && model.mAttendeesList.size() == 0) {
1130352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan            return false;
1131352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan        }
1132352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan
1133352e1a2f834460bd54e03ce94cadd36a46debacbMichael Chan        return true;
113473714169d34a55fc01e14b30897a1ebbacf9934eErik    }
113573714169d34a55fc01e14b30897a1ebbacf9934eErik
113673714169d34a55fc01e14b30897a1ebbacf9934eErik    /**
1137eca82e9468c002590045338f98588ad3ce1a0bb3Erik     * Goes through an event model and fills in content values for saving This
1138eca82e9468c002590045338f98588ad3ce1a0bb3Erik     * method will perform the initial collection of values from the model and
1139eca82e9468c002590045338f98588ad3ce1a0bb3Erik     * put them into a set of ContentValues. It performs some basic work such as
1140eca82e9468c002590045338f98588ad3ce1a0bb3Erik     * fixing the time on allDay events and choosing whether to use an rrule or
1141eca82e9468c002590045338f98588ad3ce1a0bb3Erik     * dtend.
114273714169d34a55fc01e14b30897a1ebbacf9934eErik     *
114373714169d34a55fc01e14b30897a1ebbacf9934eErik     * @param model The complete model of the event you want to save
114473714169d34a55fc01e14b30897a1ebbacf9934eErik     * @return values
114573714169d34a55fc01e14b30897a1ebbacf9934eErik     */
114673714169d34a55fc01e14b30897a1ebbacf9934eErik    ContentValues getContentValuesFromModel(CalendarEventModel model) {
114773714169d34a55fc01e14b30897a1ebbacf9934eErik        String title = model.mTitle;
114873714169d34a55fc01e14b30897a1ebbacf9934eErik        boolean isAllDay = model.mAllDay;
114973714169d34a55fc01e14b30897a1ebbacf9934eErik        String rrule = model.mRrule;
1150eca82e9468c002590045338f98588ad3ce1a0bb3Erik        String timezone = model.mTimezone;
115173714169d34a55fc01e14b30897a1ebbacf9934eErik        if (timezone == null) {
115273714169d34a55fc01e14b30897a1ebbacf9934eErik            timezone = TimeZone.getDefault().getID();
115373714169d34a55fc01e14b30897a1ebbacf9934eErik        }
115473714169d34a55fc01e14b30897a1ebbacf9934eErik        Time startTime = new Time(timezone);
115573714169d34a55fc01e14b30897a1ebbacf9934eErik        Time endTime = new Time(timezone);
115673714169d34a55fc01e14b30897a1ebbacf9934eErik
115773714169d34a55fc01e14b30897a1ebbacf9934eErik        startTime.set(model.mStart);
115873714169d34a55fc01e14b30897a1ebbacf9934eErik        endTime.set(model.mEnd);
115973714169d34a55fc01e14b30897a1ebbacf9934eErik
116073714169d34a55fc01e14b30897a1ebbacf9934eErik        ContentValues values = new ContentValues();
116173714169d34a55fc01e14b30897a1ebbacf9934eErik
116273714169d34a55fc01e14b30897a1ebbacf9934eErik        long startMillis;
116373714169d34a55fc01e14b30897a1ebbacf9934eErik        long endMillis;
116473714169d34a55fc01e14b30897a1ebbacf9934eErik        long calendarId = model.mCalendarId;
116573714169d34a55fc01e14b30897a1ebbacf9934eErik        if (isAllDay) {
116673714169d34a55fc01e14b30897a1ebbacf9934eErik            // Reset start and end time, ensure at least 1 day duration, and set
116773714169d34a55fc01e14b30897a1ebbacf9934eErik            // the timezone to UTC, as required for all-day events.
116873714169d34a55fc01e14b30897a1ebbacf9934eErik            timezone = Time.TIMEZONE_UTC;
116973714169d34a55fc01e14b30897a1ebbacf9934eErik            startTime.hour = 0;
117073714169d34a55fc01e14b30897a1ebbacf9934eErik            startTime.minute = 0;
117173714169d34a55fc01e14b30897a1ebbacf9934eErik            startTime.second = 0;
117273714169d34a55fc01e14b30897a1ebbacf9934eErik            startTime.timezone = timezone;
117373714169d34a55fc01e14b30897a1ebbacf9934eErik            startMillis = startTime.normalize(true);
117473714169d34a55fc01e14b30897a1ebbacf9934eErik
117573714169d34a55fc01e14b30897a1ebbacf9934eErik            endTime.hour = 0;
117673714169d34a55fc01e14b30897a1ebbacf9934eErik            endTime.minute = 0;
117773714169d34a55fc01e14b30897a1ebbacf9934eErik            endTime.second = 0;
117873714169d34a55fc01e14b30897a1ebbacf9934eErik            endTime.timezone = timezone;
117973714169d34a55fc01e14b30897a1ebbacf9934eErik            endMillis = endTime.normalize(true);
118029190975b9238dd6841f822f82f2fb83b0557f36Daisuke Miyakawa            if (endMillis < startMillis + DateUtils.DAY_IN_MILLIS) {
118129190975b9238dd6841f822f82f2fb83b0557f36Daisuke Miyakawa                // EditEventView#fillModelFromUI() should treat this case, but we want to ensure
118229190975b9238dd6841f822f82f2fb83b0557f36Daisuke Miyakawa                // the condition anyway.
118329190975b9238dd6841f822f82f2fb83b0557f36Daisuke Miyakawa                endMillis = startMillis + DateUtils.DAY_IN_MILLIS;
118429190975b9238dd6841f822f82f2fb83b0557f36Daisuke Miyakawa            }
118573714169d34a55fc01e14b30897a1ebbacf9934eErik        } else {
118673714169d34a55fc01e14b30897a1ebbacf9934eErik            startMillis = startTime.toMillis(true);
118773714169d34a55fc01e14b30897a1ebbacf9934eErik            endMillis = endTime.toMillis(true);
118873714169d34a55fc01e14b30897a1ebbacf9934eErik        }
118973714169d34a55fc01e14b30897a1ebbacf9934eErik
119073714169d34a55fc01e14b30897a1ebbacf9934eErik        values.put(Events.CALENDAR_ID, calendarId);
119173714169d34a55fc01e14b30897a1ebbacf9934eErik        values.put(Events.EVENT_TIMEZONE, timezone);
119273714169d34a55fc01e14b30897a1ebbacf9934eErik        values.put(Events.TITLE, title);
119373714169d34a55fc01e14b30897a1ebbacf9934eErik        values.put(Events.ALL_DAY, isAllDay ? 1 : 0);
119473714169d34a55fc01e14b30897a1ebbacf9934eErik        values.put(Events.DTSTART, startMillis);
119573714169d34a55fc01e14b30897a1ebbacf9934eErik        values.put(Events.RRULE, rrule);
119666b2067e0a389180f909eb0a528bea5f6becca23Michael Chan        if (!TextUtils.isEmpty(rrule)) {
119773714169d34a55fc01e14b30897a1ebbacf9934eErik            addRecurrenceRule(values, model);
119873714169d34a55fc01e14b30897a1ebbacf9934eErik        } else {
1199eca82e9468c002590045338f98588ad3ce1a0bb3Erik            values.put(Events.DURATION, (String) null);
120073714169d34a55fc01e14b30897a1ebbacf9934eErik            values.put(Events.DTEND, endMillis);
120173714169d34a55fc01e14b30897a1ebbacf9934eErik        }
1202bf7c96b7adbae17d822cb8dec45fdf253a16df51Michael Chan        if (model.mDescription != null) {
1203bf7c96b7adbae17d822cb8dec45fdf253a16df51Michael Chan            values.put(Events.DESCRIPTION, model.mDescription.trim());
120411bd6bec90cba2e51ed4e0e0e4ed1fdacf4fea04RoboErik        } else {
120511bd6bec90cba2e51ed4e0e0e4ed1fdacf4fea04RoboErik            values.put(Events.DESCRIPTION, (String) null);
1206bf7c96b7adbae17d822cb8dec45fdf253a16df51Michael Chan        }
1207bf7c96b7adbae17d822cb8dec45fdf253a16df51Michael Chan        if (model.mLocation != null) {
1208bf7c96b7adbae17d822cb8dec45fdf253a16df51Michael Chan            values.put(Events.EVENT_LOCATION, model.mLocation.trim());
120911bd6bec90cba2e51ed4e0e0e4ed1fdacf4fea04RoboErik        } else {
121011bd6bec90cba2e51ed4e0e0e4ed1fdacf4fea04RoboErik            values.put(Events.EVENT_LOCATION, (String) null);
1211bf7c96b7adbae17d822cb8dec45fdf253a16df51Michael Chan        }
121273714169d34a55fc01e14b30897a1ebbacf9934eErik        values.put(Events.TRANSPARENCY, model.mTransparency ? 1 : 0);
121388c483a0189cbe7132330cb1b563f07326f3bcf9Erik        values.put(Events.HAS_ATTENDEE_DATA, model.mHasAttendeeData ? 1 : 0);
121473714169d34a55fc01e14b30897a1ebbacf9934eErik
121573714169d34a55fc01e14b30897a1ebbacf9934eErik        int visibility = model.mVisibility;
121673714169d34a55fc01e14b30897a1ebbacf9934eErik        if (visibility > 0) {
121773714169d34a55fc01e14b30897a1ebbacf9934eErik            // For now the array contains the values 0, 2, and 3. We add one to match.
121873714169d34a55fc01e14b30897a1ebbacf9934eErik            // Default (0), Private (2), Public (3)
121973714169d34a55fc01e14b30897a1ebbacf9934eErik            visibility++;
122073714169d34a55fc01e14b30897a1ebbacf9934eErik        }
122173714169d34a55fc01e14b30897a1ebbacf9934eErik        values.put(Events.VISIBILITY, visibility);
122273714169d34a55fc01e14b30897a1ebbacf9934eErik
122373714169d34a55fc01e14b30897a1ebbacf9934eErik        return values;
122473714169d34a55fc01e14b30897a1ebbacf9934eErik    }
122573714169d34a55fc01e14b30897a1ebbacf9934eErik
122673714169d34a55fc01e14b30897a1ebbacf9934eErik    /**
122773714169d34a55fc01e14b30897a1ebbacf9934eErik     * Takes an e-mail address and returns the domain (everything after the last @)
122873714169d34a55fc01e14b30897a1ebbacf9934eErik     */
122973714169d34a55fc01e14b30897a1ebbacf9934eErik    public static String extractDomain(String email) {
123073714169d34a55fc01e14b30897a1ebbacf9934eErik        int separator = email.lastIndexOf('@');
123173714169d34a55fc01e14b30897a1ebbacf9934eErik        if (separator != -1 && ++separator < email.length()) {
123273714169d34a55fc01e14b30897a1ebbacf9934eErik            return email.substring(separator);
123373714169d34a55fc01e14b30897a1ebbacf9934eErik        }
123473714169d34a55fc01e14b30897a1ebbacf9934eErik        return null;
123573714169d34a55fc01e14b30897a1ebbacf9934eErik    }
123679f228124de7d98146ca526d743436f6419e2365Erik
123779f228124de7d98146ca526d743436f6419e2365Erik    public interface EditDoneRunnable extends Runnable {
123879f228124de7d98146ca526d743436f6419e2365Erik        public void setDoneCode(int code);
123979f228124de7d98146ca526d743436f6419e2365Erik    }
124073714169d34a55fc01e14b30897a1ebbacf9934eErik}
1241