CalendarDatabaseHelper.java revision 387535fec9f646e0b7acb82d5354f2b5ebee4395
1/*
2 * Copyright (C) 2009 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License
15 */
16
17package com.android.providers.calendar;
18
19import android.accounts.Account;
20import android.content.ContentResolver;
21import android.content.ContentValues;
22import android.content.Context;
23import android.database.Cursor;
24import android.database.DatabaseUtils;
25import android.database.sqlite.SQLiteDatabase;
26import android.database.sqlite.SQLiteException;
27import android.database.sqlite.SQLiteOpenHelper;
28import android.os.Bundle;
29import android.provider.CalendarContract;
30import android.provider.CalendarContract.Attendees;
31import android.provider.CalendarContract.Calendars;
32import android.provider.CalendarContract.Colors;
33import android.provider.CalendarContract.Events;
34import android.provider.CalendarContract.Reminders;
35import android.provider.SyncStateContract;
36import android.text.TextUtils;
37import android.text.format.Time;
38import android.util.Log;
39import com.android.common.content.SyncStateContentProviderHelper;
40import com.google.common.annotations.VisibleForTesting;
41
42import java.io.UnsupportedEncodingException;
43import java.net.URLDecoder;
44import java.util.TimeZone;
45
46/**
47 * Database helper for calendar. Designed as a singleton to make sure that all
48 * {@link android.content.ContentProvider} users get the same reference.
49 */
50/* package */ class CalendarDatabaseHelper extends SQLiteOpenHelper {
51
52    private static final String TAG = "CalendarDatabaseHelper";
53
54    private static final boolean LOGD = false;
55
56    private static final String DATABASE_NAME = "calendar.db";
57
58    private static final int DAY_IN_SECONDS = 24 * 60 * 60;
59
60    // Note: if you update the version number, you must also update the code
61    // in upgradeDatabase() to modify the database (gracefully, if possible).
62    // Versions under 100 cover through Froyo, 1xx version are for Gingerbread,
63    // 2xx for Honeycomb, and 3xx for ICS. For future versions bump this to the
64    // next hundred at each major release.
65    static final int DATABASE_VERSION = 308;
66
67    private static final int PRE_FROYO_SYNC_STATE_VERSION = 3;
68
69    // columns used to duplicate an event row
70    private static final String LAST_SYNCED_EVENT_COLUMNS =
71            Events._SYNC_ID + "," +
72            Events.CALENDAR_ID + "," +
73            Events.TITLE + "," +
74            Events.EVENT_LOCATION + "," +
75            Events.DESCRIPTION + "," +
76            Events.EVENT_COLOR + "," +
77            Events.EVENT_COLOR_KEY + "," +
78            Events.STATUS + "," +
79            Events.SELF_ATTENDEE_STATUS + "," +
80            Events.DTSTART + "," +
81            Events.DTEND + "," +
82            Events.EVENT_TIMEZONE + "," +
83            Events.EVENT_END_TIMEZONE + "," +
84            Events.DURATION + "," +
85            Events.ALL_DAY + "," +
86            Events.ACCESS_LEVEL + "," +
87            Events.AVAILABILITY + "," +
88            Events.HAS_ALARM + "," +
89            Events.HAS_EXTENDED_PROPERTIES + "," +
90            Events.RRULE + "," +
91            Events.RDATE + "," +
92            Events.EXRULE + "," +
93            Events.EXDATE + "," +
94            Events.ORIGINAL_SYNC_ID + "," +
95            Events.ORIGINAL_ID + "," +
96            Events.ORIGINAL_INSTANCE_TIME + "," +
97            Events.ORIGINAL_ALL_DAY + "," +
98            Events.LAST_DATE + "," +
99            Events.HAS_ATTENDEE_DATA + "," +
100            Events.GUESTS_CAN_MODIFY + "," +
101            Events.GUESTS_CAN_INVITE_OTHERS + "," +
102            Events.GUESTS_CAN_SEE_GUESTS + "," +
103            Events.ORGANIZER;
104
105    // columns used to duplicate a reminder row
106    private static final String LAST_SYNCED_REMINDER_COLUMNS =
107            Reminders.MINUTES + "," +
108            Reminders.METHOD;
109
110    // columns used to duplicate an attendee row
111    private static final String LAST_SYNCED_ATTENDEE_COLUMNS =
112            Attendees.ATTENDEE_NAME + "," +
113            Attendees.ATTENDEE_EMAIL + "," +
114            Attendees.ATTENDEE_STATUS + "," +
115            Attendees.ATTENDEE_RELATIONSHIP + "," +
116            Attendees.ATTENDEE_TYPE;
117
118    // columns used to duplicate an extended property row
119    private static final String LAST_SYNCED_EXTENDED_PROPERTY_COLUMNS =
120            CalendarContract.ExtendedProperties.NAME + "," +
121            CalendarContract.ExtendedProperties.VALUE;
122
123    public interface Tables {
124        public static final String CALENDARS = "Calendars";
125        public static final String EVENTS = "Events";
126        public static final String EVENTS_RAW_TIMES = "EventsRawTimes";
127        public static final String INSTANCES = "Instances";
128        public static final String ATTENDEES = "Attendees";
129        public static final String REMINDERS = "Reminders";
130        public static final String CALENDAR_ALERTS = "CalendarAlerts";
131        public static final String EXTENDED_PROPERTIES = "ExtendedProperties";
132        public static final String CALENDAR_META_DATA = "CalendarMetaData";
133        public static final String CALENDAR_CACHE = "CalendarCache";
134        public static final String SYNC_STATE = "_sync_state";
135        public static final String SYNC_STATE_META = "_sync_state_metadata";
136        public static final String COLORS = "Colors";
137    }
138
139    public interface Views {
140        public static final String EVENTS = "view_events";
141    }
142
143    // Copied from SyncStateContentProviderHelper.  Don't really want to make them public there.
144    private static final String SYNC_STATE_META_VERSION_COLUMN = "version";
145
146    // This needs to be done when all the tables are already created
147    private static final String EVENTS_CLEANUP_TRIGGER_SQL =
148            "DELETE FROM " + Tables.INSTANCES +
149                " WHERE "+ CalendarContract.Instances.EVENT_ID + "=" +
150                    "old." + CalendarContract.Events._ID + ";" +
151            "DELETE FROM " + Tables.EVENTS_RAW_TIMES +
152                " WHERE " + CalendarContract.EventsRawTimes.EVENT_ID + "=" +
153                    "old." + CalendarContract.Events._ID + ";" +
154            "DELETE FROM " + Tables.ATTENDEES +
155                " WHERE " + CalendarContract.Attendees.EVENT_ID + "=" +
156                    "old." + CalendarContract.Events._ID + ";" +
157            "DELETE FROM " + Tables.REMINDERS +
158                " WHERE " + CalendarContract.Reminders.EVENT_ID + "=" +
159                    "old." + CalendarContract.Events._ID + ";" +
160            "DELETE FROM " + Tables.CALENDAR_ALERTS +
161                " WHERE " + CalendarContract.CalendarAlerts.EVENT_ID + "=" +
162                    "old." + CalendarContract.Events._ID + ";" +
163            "DELETE FROM " + Tables.EXTENDED_PROPERTIES +
164                " WHERE " + CalendarContract.ExtendedProperties.EVENT_ID + "=" +
165                    "old." + CalendarContract.Events._ID + ";";
166
167    // This ensures any exceptions based on an event get their original_sync_id
168    // column set when an the _sync_id is set.
169    private static final String EVENTS_ORIGINAL_SYNC_TRIGGER_SQL =
170            "UPDATE " + Tables.EVENTS +
171                " SET " + Events.ORIGINAL_SYNC_ID + "=new." + Events._SYNC_ID +
172                " WHERE " + Events.ORIGINAL_ID + "=old." + Events._ID + ";";
173
174    private static final String SYNC_ID_UPDATE_TRIGGER_NAME = "original_sync_update";
175    private static final String CREATE_SYNC_ID_UPDATE_TRIGGER =
176            "CREATE TRIGGER " + SYNC_ID_UPDATE_TRIGGER_NAME + " UPDATE OF " + Events._SYNC_ID +
177            " ON " + Tables.EVENTS +
178            " BEGIN " +
179                EVENTS_ORIGINAL_SYNC_TRIGGER_SQL +
180            " END";
181
182    private static final String CALENDAR_CLEANUP_TRIGGER_SQL = "DELETE FROM " + Tables.EVENTS +
183            " WHERE " + CalendarContract.Events.CALENDAR_ID + "=" +
184                "old." + CalendarContract.Events._ID + ";";
185
186    private static final String CALENDAR_UPDATE_COLOR_TRIGGER_SQL = "UPDATE " + Tables.CALENDARS
187            + " SET calendar_color=(SELECT " + Colors.COLOR + " FROM " + Tables.COLORS + " WHERE "
188            + Colors.ACCOUNT_NAME + "=" + "new." + Calendars.ACCOUNT_NAME + " AND "
189            + Colors.ACCOUNT_TYPE + "=" + "new." + Calendars.ACCOUNT_TYPE + " AND "
190            + Colors.COLOR_KEY + "=" + "new." + Calendars.CALENDAR_COLOR_KEY + ") "
191            + " WHERE " + Calendars._ID + "=" + "old." + Calendars._ID
192            + ";";
193    private static final String CALENDAR_COLOR_UPDATE_TRIGGER_NAME = "calendar_color_update";
194    private static final String CREATE_CALENDAR_COLOR_UPDATE_TRIGGER = "CREATE TRIGGER "
195            + CALENDAR_COLOR_UPDATE_TRIGGER_NAME + " UPDATE OF " + Calendars.CALENDAR_COLOR_KEY
196            + " ON " + Tables.CALENDARS + " WHEN new." + Calendars.CALENDAR_COLOR_KEY
197            + " NOT NULL BEGIN " + CALENDAR_UPDATE_COLOR_TRIGGER_SQL + " END";
198
199    private static final String EVENT_UPDATE_COLOR_TRIGGER_SQL = "UPDATE " + Tables.EVENTS
200            + " SET eventColor=(SELECT " + Colors.COLOR + " FROM " + Tables.COLORS + " WHERE "
201            + Colors.ACCOUNT_NAME + "=" + "(SELECT " + Calendars.ACCOUNT_NAME + " FROM "
202            + Tables.CALENDARS + " WHERE " + Calendars._ID + "=new." + Events.CALENDAR_ID
203            + ") AND " + Colors.ACCOUNT_TYPE + "=" + "(SELECT " + Calendars.ACCOUNT_TYPE + " FROM "
204            + Tables.CALENDARS + " WHERE " + Calendars._ID + "=new." + Events.CALENDAR_ID
205            + ") AND " + Colors.COLOR_KEY + "=" + "new." + Events.EVENT_COLOR_KEY + ") "
206            + " WHERE " + Events._ID + "=" + "old." + Events._ID + ";";
207    private static final String EVENT_COLOR_UPDATE_TRIGGER_NAME = "event_color_update";
208    private static final String CREATE_EVENT_COLOR_UPDATE_TRIGGER = "CREATE TRIGGER "
209            + EVENT_COLOR_UPDATE_TRIGGER_NAME + " UPDATE OF " + Events.EVENT_COLOR_KEY + " ON "
210            + Tables.EVENTS + " WHEN new." + Events.EVENT_COLOR_KEY + " NOT NULL BEGIN "
211            + EVENT_UPDATE_COLOR_TRIGGER_SQL + " END";
212
213    /** Selects rows from Attendees for which the event_id refers to a nonexistent Event */
214    private static final String WHERE_ATTENDEES_ORPHANS =
215            Attendees.EVENT_ID + " IN (SELECT " + Attendees.EVENT_ID + " FROM " +
216            Tables.ATTENDEES + " LEFT OUTER JOIN " + Tables.EVENTS + " ON " +
217            Attendees.EVENT_ID + "=" + Tables.EVENTS + "." + Events._ID +
218            " WHERE " + Tables.EVENTS + "." + Events._ID + " IS NULL)";
219    /** Selects rows from Reminders for which the event_id refers to a nonexistent Event */
220    private static final String WHERE_REMINDERS_ORPHANS =
221            Reminders.EVENT_ID + " IN (SELECT " + Reminders.EVENT_ID + " FROM " +
222            Tables.REMINDERS + " LEFT OUTER JOIN " + Tables.EVENTS + " ON " +
223            Reminders.EVENT_ID + "=" + Tables.EVENTS + "." + Events._ID +
224            " WHERE " + Tables.EVENTS + "." + Events._ID + " IS NULL)";
225
226    private static final String SCHEMA_HTTPS = "https://";
227    private static final String SCHEMA_HTTP = "http://";
228
229    private final SyncStateContentProviderHelper mSyncState;
230
231    private static CalendarDatabaseHelper sSingleton = null;
232
233    private DatabaseUtils.InsertHelper mCalendarsInserter;
234    private DatabaseUtils.InsertHelper mColorsInserter;
235    private DatabaseUtils.InsertHelper mEventsInserter;
236    private DatabaseUtils.InsertHelper mEventsRawTimesInserter;
237    private DatabaseUtils.InsertHelper mInstancesInserter;
238    private DatabaseUtils.InsertHelper mAttendeesInserter;
239    private DatabaseUtils.InsertHelper mRemindersInserter;
240    private DatabaseUtils.InsertHelper mCalendarAlertsInserter;
241    private DatabaseUtils.InsertHelper mExtendedPropertiesInserter;
242
243    public long calendarsInsert(ContentValues values) {
244        return mCalendarsInserter.insert(values);
245    }
246
247    public long colorsInsert(ContentValues values) {
248        return mColorsInserter.insert(values);
249    }
250
251    public long eventsInsert(ContentValues values) {
252        return mEventsInserter.insert(values);
253    }
254
255    public long eventsRawTimesInsert(ContentValues values) {
256        return mEventsRawTimesInserter.insert(values);
257    }
258
259    public long eventsRawTimesReplace(ContentValues values) {
260        return mEventsRawTimesInserter.replace(values);
261    }
262
263    public long instancesInsert(ContentValues values) {
264        return mInstancesInserter.insert(values);
265    }
266
267    public long instancesReplace(ContentValues values) {
268        return mInstancesInserter.replace(values);
269    }
270
271    public long attendeesInsert(ContentValues values) {
272        return mAttendeesInserter.insert(values);
273    }
274
275    public long remindersInsert(ContentValues values) {
276        return mRemindersInserter.insert(values);
277    }
278
279    public long calendarAlertsInsert(ContentValues values) {
280        return mCalendarAlertsInserter.insert(values);
281    }
282
283    public long extendedPropertiesInsert(ContentValues values) {
284        return mExtendedPropertiesInserter.insert(values);
285    }
286
287    public static synchronized CalendarDatabaseHelper getInstance(Context context) {
288        if (sSingleton == null) {
289            sSingleton = new CalendarDatabaseHelper(context);
290        }
291        return sSingleton;
292    }
293
294    /**
295     * Private constructor, callers except unit tests should obtain an instance through
296     * {@link #getInstance(android.content.Context)} instead.
297     */
298    /* package */ CalendarDatabaseHelper(Context context) {
299        super(context, DATABASE_NAME, null, DATABASE_VERSION);
300        if (LOGD) Log.d(TAG, "Creating OpenHelper");
301
302        mSyncState = new SyncStateContentProviderHelper();
303    }
304
305    @Override
306    public void onOpen(SQLiteDatabase db) {
307        mSyncState.onDatabaseOpened(db);
308
309        mCalendarsInserter = new DatabaseUtils.InsertHelper(db, Tables.CALENDARS);
310        mColorsInserter = new DatabaseUtils.InsertHelper(db, Tables.COLORS);
311        mEventsInserter = new DatabaseUtils.InsertHelper(db, Tables.EVENTS);
312        mEventsRawTimesInserter = new DatabaseUtils.InsertHelper(db, Tables.EVENTS_RAW_TIMES);
313        mInstancesInserter = new DatabaseUtils.InsertHelper(db, Tables.INSTANCES);
314        mAttendeesInserter = new DatabaseUtils.InsertHelper(db, Tables.ATTENDEES);
315        mRemindersInserter = new DatabaseUtils.InsertHelper(db, Tables.REMINDERS);
316        mCalendarAlertsInserter = new DatabaseUtils.InsertHelper(db, Tables.CALENDAR_ALERTS);
317        mExtendedPropertiesInserter =
318                new DatabaseUtils.InsertHelper(db, Tables.EXTENDED_PROPERTIES);
319    }
320
321    /*
322     * Upgrade sync state table if necessary.  Note that the data bundle
323     * in the table is not upgraded.
324     *
325     * The sync state used to be stored with version 3, but now uses the
326     * same sync state code as contacts, which is version 1.  This code
327     * upgrades from 3 to 1 if necessary.  (Yes, the numbers are unfortunately
328     * backwards.)
329     *
330     * This code is only called when upgrading from an old calendar version,
331     * so there is no problem if sync state version 3 gets used again in the
332     * future.
333     */
334    private void upgradeSyncState(SQLiteDatabase db) {
335        long version = DatabaseUtils.longForQuery(db,
336                 "SELECT " + SYNC_STATE_META_VERSION_COLUMN
337                 + " FROM " + Tables.SYNC_STATE_META,
338                 null);
339        if (version == PRE_FROYO_SYNC_STATE_VERSION) {
340            Log.i(TAG, "Upgrading calendar sync state table");
341            db.execSQL("CREATE TEMPORARY TABLE state_backup(_sync_account TEXT, "
342                    + "_sync_account_type TEXT, data TEXT);");
343            db.execSQL("INSERT INTO state_backup SELECT _sync_account, _sync_account_type, data"
344                    + " FROM "
345                    + Tables.SYNC_STATE
346                    + " WHERE _sync_account is not NULL and _sync_account_type is not NULL;");
347            db.execSQL("DROP TABLE " + Tables.SYNC_STATE + ";");
348            mSyncState.onDatabaseOpened(db);
349            db.execSQL("INSERT INTO " + Tables.SYNC_STATE + "("
350                    + SyncStateContract.Columns.ACCOUNT_NAME + ","
351                    + SyncStateContract.Columns.ACCOUNT_TYPE + ","
352                    + SyncStateContract.Columns.DATA
353                    + ") SELECT _sync_account, _sync_account_type, data from state_backup;");
354            db.execSQL("DROP TABLE state_backup;");
355        } else {
356            // Wrong version to upgrade.
357            // Don't need to do anything more here because mSyncState.onDatabaseOpened() will blow
358            // away and recreate  the database (which will result in a resync).
359            Log.w(TAG, "upgradeSyncState: current version is " + version + ", skipping upgrade.");
360        }
361    }
362
363    @Override
364    public void onCreate(SQLiteDatabase db) {
365        bootstrapDB(db);
366    }
367
368    private void bootstrapDB(SQLiteDatabase db) {
369        Log.i(TAG, "Bootstrapping database");
370
371        mSyncState.createDatabase(db);
372
373        createColorsTable(db);
374
375        createCalendarsTable(db);
376
377        createEventsTable(db);
378
379        db.execSQL("CREATE TABLE " + Tables.EVENTS_RAW_TIMES + " (" +
380                CalendarContract.EventsRawTimes._ID + " INTEGER PRIMARY KEY," +
381                CalendarContract.EventsRawTimes.EVENT_ID + " INTEGER NOT NULL," +
382                CalendarContract.EventsRawTimes.DTSTART_2445 + " TEXT," +
383                CalendarContract.EventsRawTimes.DTEND_2445 + " TEXT," +
384                CalendarContract.EventsRawTimes.ORIGINAL_INSTANCE_TIME_2445 + " TEXT," +
385                CalendarContract.EventsRawTimes.LAST_DATE_2445 + " TEXT," +
386                "UNIQUE (" + CalendarContract.EventsRawTimes.EVENT_ID + ")" +
387                ");");
388
389        db.execSQL("CREATE TABLE " + Tables.INSTANCES + " (" +
390                CalendarContract.Instances._ID + " INTEGER PRIMARY KEY," +
391                CalendarContract.Instances.EVENT_ID + " INTEGER," +
392                CalendarContract.Instances.BEGIN + " INTEGER," +         // UTC millis
393                CalendarContract.Instances.END + " INTEGER," +           // UTC millis
394                CalendarContract.Instances.START_DAY + " INTEGER," +      // Julian start day
395                CalendarContract.Instances.END_DAY + " INTEGER," +        // Julian end day
396                CalendarContract.Instances.START_MINUTE + " INTEGER," +   // minutes from midnight
397                CalendarContract.Instances.END_MINUTE + " INTEGER," +     // minutes from midnight
398                "UNIQUE (" +
399                    CalendarContract.Instances.EVENT_ID + ", " +
400                    CalendarContract.Instances.BEGIN + ", " +
401                    CalendarContract.Instances.END + ")" +
402                ");");
403
404        db.execSQL("CREATE INDEX instancesStartDayIndex ON " + Tables.INSTANCES + " (" +
405                CalendarContract.Instances.START_DAY +
406                ");");
407
408        createCalendarMetaDataTable(db);
409
410        createCalendarCacheTable(db, null);
411
412        db.execSQL("CREATE TABLE " + Tables.ATTENDEES + " (" +
413                CalendarContract.Attendees._ID + " INTEGER PRIMARY KEY," +
414                CalendarContract.Attendees.EVENT_ID + " INTEGER," +
415                CalendarContract.Attendees.ATTENDEE_NAME + " TEXT," +
416                CalendarContract.Attendees.ATTENDEE_EMAIL + " TEXT," +
417                CalendarContract.Attendees.ATTENDEE_STATUS + " INTEGER," +
418                CalendarContract.Attendees.ATTENDEE_RELATIONSHIP + " INTEGER," +
419                CalendarContract.Attendees.ATTENDEE_TYPE + " INTEGER" +
420                ");");
421
422        db.execSQL("CREATE INDEX attendeesEventIdIndex ON " + Tables.ATTENDEES + " (" +
423                CalendarContract.Attendees.EVENT_ID +
424                ");");
425
426        db.execSQL("CREATE TABLE " + Tables.REMINDERS + " (" +
427                CalendarContract.Reminders._ID + " INTEGER PRIMARY KEY," +
428                CalendarContract.Reminders.EVENT_ID + " INTEGER," +
429                CalendarContract.Reminders.MINUTES + " INTEGER," +
430                CalendarContract.Reminders.METHOD + " INTEGER NOT NULL" +
431                " DEFAULT " + CalendarContract.Reminders.METHOD_DEFAULT +
432                ");");
433
434        db.execSQL("CREATE INDEX remindersEventIdIndex ON " + Tables.REMINDERS + " (" +
435                CalendarContract.Reminders.EVENT_ID +
436                ");");
437
438         // This table stores the Calendar notifications that have gone off.
439        db.execSQL("CREATE TABLE " + Tables.CALENDAR_ALERTS + " (" +
440                CalendarContract.CalendarAlerts._ID + " INTEGER PRIMARY KEY," +
441                CalendarContract.CalendarAlerts.EVENT_ID + " INTEGER," +
442                CalendarContract.CalendarAlerts.BEGIN + " INTEGER NOT NULL," +      // UTC millis
443                CalendarContract.CalendarAlerts.END + " INTEGER NOT NULL," +        // UTC millis
444                CalendarContract.CalendarAlerts.ALARM_TIME + " INTEGER NOT NULL," + // UTC millis
445                // UTC millis
446                CalendarContract.CalendarAlerts.CREATION_TIME + " INTEGER NOT NULL DEFAULT 0," +
447                // UTC millis
448                CalendarContract.CalendarAlerts.RECEIVED_TIME + " INTEGER NOT NULL DEFAULT 0," +
449                // UTC millis
450                CalendarContract.CalendarAlerts.NOTIFY_TIME + " INTEGER NOT NULL DEFAULT 0," +
451                CalendarContract.CalendarAlerts.STATE + " INTEGER NOT NULL," +
452                CalendarContract.CalendarAlerts.MINUTES + " INTEGER," +
453                "UNIQUE (" +
454                    CalendarContract.CalendarAlerts.ALARM_TIME + ", " +
455                    CalendarContract.CalendarAlerts.BEGIN + ", " +
456                    CalendarContract.CalendarAlerts.EVENT_ID + ")" +
457                ");");
458
459        db.execSQL("CREATE INDEX calendarAlertsEventIdIndex ON " + Tables.CALENDAR_ALERTS + " (" +
460                CalendarContract.CalendarAlerts.EVENT_ID +
461                ");");
462
463        db.execSQL("CREATE TABLE " + Tables.EXTENDED_PROPERTIES + " (" +
464                CalendarContract.ExtendedProperties._ID + " INTEGER PRIMARY KEY," +
465                CalendarContract.ExtendedProperties.EVENT_ID + " INTEGER," +
466                CalendarContract.ExtendedProperties.NAME + " TEXT," +
467                CalendarContract.ExtendedProperties.VALUE + " TEXT" +
468                ");");
469
470        db.execSQL("CREATE INDEX extendedPropertiesEventIdIndex ON " + Tables.EXTENDED_PROPERTIES
471                + " (" +
472                CalendarContract.ExtendedProperties.EVENT_ID +
473                ");");
474
475        createEventsView(db);
476
477        // Trigger to remove data tied to an event when we delete that event.
478        db.execSQL("CREATE TRIGGER events_cleanup_delete DELETE ON " + Tables.EVENTS + " " +
479                "BEGIN " +
480                EVENTS_CLEANUP_TRIGGER_SQL +
481                "END");
482
483        // Triggers to update the color stored in an event or a calendar when
484        // the color_index is changed.
485        createColorsTriggers(db);
486
487        // Trigger to update exceptions when an original event updates its
488        // _sync_id
489        db.execSQL(CREATE_SYNC_ID_UPDATE_TRIGGER);
490
491        scheduleSync(null /* all accounts */, false, null);
492    }
493
494    private void createEventsTable(SQLiteDatabase db) {
495        // IMPORTANT: when adding new columns, be sure to update ALLOWED_IN_EXCEPTION and
496        // DONT_CLONE_INTO_EXCEPTION in CalendarProvider2.
497        //
498        // TODO: do we need both dtend and duration?
499        // **When updating this be sure to also update LAST_SYNCED_EVENT_COLUMNS
500        db.execSQL("CREATE TABLE " + Tables.EVENTS + " (" +
501                CalendarContract.Events._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
502                CalendarContract.Events._SYNC_ID + " TEXT," +
503                CalendarContract.Events.DIRTY + " INTEGER," +
504                CalendarContract.Events.LAST_SYNCED + " INTEGER DEFAULT 0," +
505                CalendarContract.Events.CALENDAR_ID + " INTEGER NOT NULL," +
506                CalendarContract.Events.TITLE + " TEXT," +
507                CalendarContract.Events.EVENT_LOCATION + " TEXT," +
508                CalendarContract.Events.DESCRIPTION + " TEXT," +
509                CalendarContract.Events.EVENT_COLOR + " INTEGER," +
510                CalendarContract.Events.EVENT_COLOR_KEY + " TEXT," +
511                CalendarContract.Events.STATUS + " INTEGER," +
512                CalendarContract.Events.SELF_ATTENDEE_STATUS + " INTEGER NOT NULL DEFAULT 0," +
513                // dtstart in millis since epoch
514                CalendarContract.Events.DTSTART + " INTEGER," +
515                // dtend in millis since epoch
516                CalendarContract.Events.DTEND + " INTEGER," +
517                // timezone for event
518                CalendarContract.Events.EVENT_TIMEZONE + " TEXT," +
519                CalendarContract.Events.DURATION + " TEXT," +
520                CalendarContract.Events.ALL_DAY + " INTEGER NOT NULL DEFAULT 0," +
521                CalendarContract.Events.ACCESS_LEVEL + " INTEGER NOT NULL DEFAULT 0," +
522                CalendarContract.Events.AVAILABILITY + " INTEGER NOT NULL DEFAULT 0," +
523                CalendarContract.Events.HAS_ALARM + " INTEGER NOT NULL DEFAULT 0," +
524                CalendarContract.Events.HAS_EXTENDED_PROPERTIES + " INTEGER NOT NULL DEFAULT 0," +
525                CalendarContract.Events.RRULE + " TEXT," +
526                CalendarContract.Events.RDATE + " TEXT," +
527                CalendarContract.Events.EXRULE + " TEXT," +
528                CalendarContract.Events.EXDATE + " TEXT," +
529                CalendarContract.Events.ORIGINAL_ID + " INTEGER," +
530                // ORIGINAL_SYNC_ID is the _sync_id of recurring event
531                CalendarContract.Events.ORIGINAL_SYNC_ID + " TEXT," +
532                // originalInstanceTime is in millis since epoch
533                CalendarContract.Events.ORIGINAL_INSTANCE_TIME + " INTEGER," +
534                CalendarContract.Events.ORIGINAL_ALL_DAY + " INTEGER," +
535                // lastDate is in millis since epoch
536                CalendarContract.Events.LAST_DATE + " INTEGER," +
537                CalendarContract.Events.HAS_ATTENDEE_DATA + " INTEGER NOT NULL DEFAULT 0," +
538                CalendarContract.Events.GUESTS_CAN_MODIFY + " INTEGER NOT NULL DEFAULT 0," +
539                CalendarContract.Events.GUESTS_CAN_INVITE_OTHERS + " INTEGER NOT NULL DEFAULT 1," +
540                CalendarContract.Events.GUESTS_CAN_SEE_GUESTS + " INTEGER NOT NULL DEFAULT 1," +
541                CalendarContract.Events.ORGANIZER + " STRING," +
542                CalendarContract.Events.DELETED + " INTEGER NOT NULL DEFAULT 0," +
543                // timezone for event with allDay events are in local timezone
544                CalendarContract.Events.EVENT_END_TIMEZONE + " TEXT," +
545                // SYNC_DATAX columns are available for use by sync adapters
546                CalendarContract.Events.SYNC_DATA1 + " TEXT," +
547                CalendarContract.Events.SYNC_DATA2 + " TEXT," +
548                CalendarContract.Events.SYNC_DATA3 + " TEXT," +
549                CalendarContract.Events.SYNC_DATA4 + " TEXT," +
550                CalendarContract.Events.SYNC_DATA5 + " TEXT," +
551                CalendarContract.Events.SYNC_DATA6 + " TEXT," +
552                CalendarContract.Events.SYNC_DATA7 + " TEXT," +
553                CalendarContract.Events.SYNC_DATA8 + " TEXT," +
554                CalendarContract.Events.SYNC_DATA9 + " TEXT," +
555                CalendarContract.Events.SYNC_DATA10 + " TEXT" + ");");
556
557        // **When updating this be sure to also update LAST_SYNCED_EVENT_COLUMNS
558
559        db.execSQL("CREATE INDEX eventsCalendarIdIndex ON " + Tables.EVENTS + " ("
560                + CalendarContract.Events.CALENDAR_ID + ");");
561    }
562
563    private void createEventsTable307(SQLiteDatabase db) {
564        db.execSQL("CREATE TABLE Events ("
565                + "_id INTEGER PRIMARY KEY AUTOINCREMENT,"
566                + "_sync_id TEXT,"
567                + "dirty INTEGER,"
568                + "lastSynced INTEGER DEFAULT 0,"
569                + "calendar_id INTEGER NOT NULL,"
570                + "title TEXT,"
571                + "eventLocation TEXT,"
572                + "description TEXT,"
573                + "eventColor INTEGER,"
574                + "eventStatus INTEGER,"
575                + "selfAttendeeStatus INTEGER NOT NULL DEFAULT 0,"
576                // dtstart in millis since epoch
577                + "dtstart INTEGER,"
578                // dtend in millis since epoch
579                + "dtend INTEGER,"
580                // timezone for event
581                + "eventTimezone TEXT,"
582                + "duration TEXT,"
583                + "allDay INTEGER NOT NULL DEFAULT 0,"
584                + "accessLevel INTEGER NOT NULL DEFAULT 0,"
585                + "availability INTEGER NOT NULL DEFAULT 0,"
586                + "hasAlarm INTEGER NOT NULL DEFAULT 0,"
587                + "hasExtendedProperties INTEGER NOT NULL DEFAULT 0,"
588                + "rrule TEXT,"
589                + "rdate TEXT,"
590                + "exrule TEXT,"
591                + "exdate TEXT,"
592                + "original_id INTEGER,"
593                // ORIGINAL_SYNC_ID is the _sync_id of recurring event
594                + "original_sync_id TEXT,"
595                // originalInstanceTime is in millis since epoch
596                + "originalInstanceTime INTEGER,"
597                + "originalAllDay INTEGER,"
598                // lastDate is in millis since epoch
599                + "lastDate INTEGER,"
600                + "hasAttendeeData INTEGER NOT NULL DEFAULT 0,"
601                + "guestsCanModify INTEGER NOT NULL DEFAULT 0,"
602                + "guestsCanInviteOthers INTEGER NOT NULL DEFAULT 1,"
603                + "guestsCanSeeGuests INTEGER NOT NULL DEFAULT 1,"
604                + "organizer STRING,"
605                + "deleted INTEGER NOT NULL DEFAULT 0,"
606                // timezone for event with allDay events are in local timezone
607                + "eventEndTimezone TEXT,"
608                // SYNC_DATAX columns are available for use by sync adapters
609                + "sync_data1 TEXT,"
610                + "sync_data2 TEXT,"
611                + "sync_data3 TEXT,"
612                + "sync_data4 TEXT,"
613                + "sync_data5 TEXT,"
614                + "sync_data6 TEXT,"
615                + "sync_data7 TEXT,"
616                + "sync_data8 TEXT,"
617                + "sync_data9 TEXT,"
618                + "sync_data10 TEXT);");
619
620        // **When updating this be sure to also update LAST_SYNCED_EVENT_COLUMNS
621
622        db.execSQL("CREATE INDEX eventsCalendarIdIndex ON Events (calendar_id);");
623    }
624
625    // TODO Remove this method after merging all ICS upgrades
626    private void createEventsTable300(SQLiteDatabase db) {
627        db.execSQL("CREATE TABLE Events (" +
628                "_id INTEGER PRIMARY KEY," +
629                "_sync_id TEXT," +
630                "_sync_version TEXT," +
631                // sync time in UTC
632                "_sync_time TEXT,"  +
633                "_sync_local_id INTEGER," +
634                "dirty INTEGER," +
635                // sync mark to filter out new rows
636                "_sync_mark INTEGER," +
637                "calendar_id INTEGER NOT NULL," +
638                "htmlUri TEXT," +
639                "title TEXT," +
640                "eventLocation TEXT," +
641                "description TEXT," +
642                "eventStatus INTEGER," +
643                "selfAttendeeStatus INTEGER NOT NULL DEFAULT 0," +
644                "commentsUri TEXT," +
645                // dtstart in millis since epoch
646                "dtstart INTEGER," +
647                // dtend in millis since epoch
648                "dtend INTEGER," +
649                // timezone for event
650                "eventTimezone TEXT," +
651                "duration TEXT," +
652                "allDay INTEGER NOT NULL DEFAULT 0," +
653                "accessLevel INTEGER NOT NULL DEFAULT 0," +
654                "availability INTEGER NOT NULL DEFAULT 0," +
655                "hasAlarm INTEGER NOT NULL DEFAULT 0," +
656                "hasExtendedProperties INTEGER NOT NULL DEFAULT 0," +
657                "rrule TEXT," +
658                "rdate TEXT," +
659                "exrule TEXT," +
660                "exdate TEXT," +
661                // originalEvent is the _sync_id of recurring event
662                "original_sync_id TEXT," +
663                // originalInstanceTime is in millis since epoch
664                "originalInstanceTime INTEGER," +
665                "originalAllDay INTEGER," +
666                // lastDate is in millis since epoch
667                "lastDate INTEGER," +
668                "hasAttendeeData INTEGER NOT NULL DEFAULT 0," +
669                "guestsCanModify INTEGER NOT NULL DEFAULT 0," +
670                "guestsCanInviteOthers INTEGER NOT NULL DEFAULT 1," +
671                "guestsCanSeeGuests INTEGER NOT NULL DEFAULT 1," +
672                "organizer STRING," +
673                "deleted INTEGER NOT NULL DEFAULT 0," +
674                // timezone for event with allDay events are in local timezone
675                "eventEndTimezone TEXT," +
676                // syncAdapterData is available for use by sync adapters
677                "sync_data1 TEXT);");
678
679        db.execSQL("CREATE INDEX eventsCalendarIdIndex ON Events (calendar_id);");
680    }
681
682    private void createCalendarsTable303(SQLiteDatabase db) {
683        db.execSQL("CREATE TABLE " + Tables.CALENDARS + " (" +
684                "_id INTEGER PRIMARY KEY," +
685                "account_name TEXT," +
686                "account_type TEXT," +
687                "_sync_id TEXT," +
688                "_sync_version TEXT," +
689                "_sync_time TEXT," +  // UTC
690                "dirty INTEGER," +
691                "name TEXT," +
692                "displayName TEXT," +
693                "calendar_color INTEGER," +
694                "access_level INTEGER," +
695                "visible INTEGER NOT NULL DEFAULT 1," +
696                "sync_events INTEGER NOT NULL DEFAULT 0," +
697                "calendar_location TEXT," +
698                "calendar_timezone TEXT," +
699                "ownerAccount TEXT, " +
700                "canOrganizerRespond INTEGER NOT NULL DEFAULT 1," +
701                "canModifyTimeZone INTEGER DEFAULT 1," +
702                "maxReminders INTEGER DEFAULT 5," +
703                "allowedReminders TEXT DEFAULT '0,1'," +
704                "deleted INTEGER NOT NULL DEFAULT 0," +
705                "cal_sync1 TEXT," +
706                "cal_sync2 TEXT," +
707                "cal_sync3 TEXT," +
708                "cal_sync4 TEXT," +
709                "cal_sync5 TEXT," +
710                "cal_sync6 TEXT" +
711                ");");
712
713        // Trigger to remove a calendar's events when we delete the calendar
714        db.execSQL("CREATE TRIGGER calendar_cleanup DELETE ON " + Tables.CALENDARS + " " +
715                "BEGIN " +
716                CALENDAR_CLEANUP_TRIGGER_SQL +
717                "END");
718    }
719
720    private void createColorsTable(SQLiteDatabase db) {
721
722        db.execSQL("CREATE TABLE " + Tables.COLORS + " (" +
723                CalendarContract.Colors._ID + " INTEGER PRIMARY KEY," +
724                CalendarContract.Colors.ACCOUNT_NAME + " TEXT NOT NULL," +
725                CalendarContract.Colors.ACCOUNT_TYPE + " TEXT NOT NULL," +
726                CalendarContract.Colors.DATA + " TEXT," +
727                CalendarContract.Colors.COLOR_TYPE + " INTEGER NOT NULL," +
728                CalendarContract.Colors.COLOR_KEY + " TEXT NOT NULL," +
729                CalendarContract.Colors.COLOR + " INTEGER NOT NULL" +
730                ");");
731    }
732
733    public void createColorsTriggers(SQLiteDatabase db) {
734        db.execSQL(CREATE_EVENT_COLOR_UPDATE_TRIGGER);
735        db.execSQL(CREATE_CALENDAR_COLOR_UPDATE_TRIGGER);
736    }
737
738    private void createCalendarsTable(SQLiteDatabase db) {
739        db.execSQL("CREATE TABLE " + Tables.CALENDARS + " (" +
740                CalendarContract.Calendars._ID + " INTEGER PRIMARY KEY," +
741                CalendarContract.Calendars.ACCOUNT_NAME + " TEXT," +
742                CalendarContract.Calendars.ACCOUNT_TYPE + " TEXT," +
743                CalendarContract.Calendars._SYNC_ID + " TEXT," +
744                CalendarContract.Calendars.DIRTY + " INTEGER," +
745                CalendarContract.Calendars.NAME + " TEXT," +
746                CalendarContract.Calendars.CALENDAR_DISPLAY_NAME + " TEXT," +
747                CalendarContract.Calendars.CALENDAR_COLOR + " INTEGER," +
748                CalendarContract.Calendars.CALENDAR_COLOR_KEY + " TEXT," +
749                CalendarContract.Calendars.CALENDAR_ACCESS_LEVEL + " INTEGER," +
750                CalendarContract.Calendars.VISIBLE + " INTEGER NOT NULL DEFAULT 1," +
751                CalendarContract.Calendars.SYNC_EVENTS + " INTEGER NOT NULL DEFAULT 0," +
752                CalendarContract.Calendars.CALENDAR_LOCATION + " TEXT," +
753                CalendarContract.Calendars.CALENDAR_TIME_ZONE + " TEXT," +
754                CalendarContract.Calendars.OWNER_ACCOUNT + " TEXT, " +
755                CalendarContract.Calendars.CAN_ORGANIZER_RESPOND + " INTEGER NOT NULL DEFAULT 1," +
756                CalendarContract.Calendars.CAN_MODIFY_TIME_ZONE + " INTEGER DEFAULT 1," +
757                CalendarContract.Calendars.CAN_PARTIALLY_UPDATE + " INTEGER DEFAULT 0," +
758                CalendarContract.Calendars.MAX_REMINDERS + " INTEGER DEFAULT 5," +
759                CalendarContract.Calendars.ALLOWED_REMINDERS + " TEXT DEFAULT '0,1'," +
760                CalendarContract.Calendars.ALLOWED_AVAILABILITY + " TEXT DEFAULT '0,1'," +
761                CalendarContract.Calendars.ALLOWED_ATTENDEE_TYPES + " TEXT DEFAULT '0,1,2'," +
762                CalendarContract.Calendars.DELETED + " INTEGER NOT NULL DEFAULT 0," +
763                CalendarContract.Calendars.CAL_SYNC1 + " TEXT," +
764                CalendarContract.Calendars.CAL_SYNC2 + " TEXT," +
765                CalendarContract.Calendars.CAL_SYNC3 + " TEXT," +
766                CalendarContract.Calendars.CAL_SYNC4 + " TEXT," +
767                CalendarContract.Calendars.CAL_SYNC5 + " TEXT," +
768                CalendarContract.Calendars.CAL_SYNC6 + " TEXT," +
769                CalendarContract.Calendars.CAL_SYNC7 + " TEXT," +
770                CalendarContract.Calendars.CAL_SYNC8 + " TEXT," +
771                CalendarContract.Calendars.CAL_SYNC9 + " TEXT," +
772                CalendarContract.Calendars.CAL_SYNC10 + " TEXT" +
773                ");");
774
775        // Trigger to remove a calendar's events when we delete the calendar
776        db.execSQL("CREATE TRIGGER calendar_cleanup DELETE ON " + Tables.CALENDARS + " " +
777                "BEGIN " +
778                CALENDAR_CLEANUP_TRIGGER_SQL +
779                "END");
780    }
781
782    private void createCalendarsTable305(SQLiteDatabase db) {
783        db.execSQL("CREATE TABLE Calendars (" +
784                "_id INTEGER PRIMARY KEY," +
785                "account_name TEXT," +
786                "account_type TEXT," +
787                "_sync_id TEXT," +
788                "dirty INTEGER," +
789                "name TEXT," +
790                "calendar_displayName TEXT," +
791                "calendar_color INTEGER," +
792                "calendar_access_level INTEGER," +
793                "visible INTEGER NOT NULL DEFAULT 1," +
794                "sync_events INTEGER NOT NULL DEFAULT 0," +
795                "calendar_location TEXT," +
796                "calendar_timezone TEXT," +
797                "ownerAccount TEXT, " +
798                "canOrganizerRespond INTEGER NOT NULL DEFAULT 1," +
799                "canModifyTimeZone INTEGER DEFAULT 1," +
800                "canPartiallyUpdate INTEGER DEFAULT 0," +
801                "maxReminders INTEGER DEFAULT 5," +
802                "allowedReminders TEXT DEFAULT '0,1'," +
803                "deleted INTEGER NOT NULL DEFAULT 0," +
804                "cal_sync1 TEXT," +
805                "cal_sync2 TEXT," +
806                "cal_sync3 TEXT," +
807                "cal_sync4 TEXT," +
808                "cal_sync5 TEXT," +
809                "cal_sync6 TEXT," +
810                "cal_sync7 TEXT," +
811                "cal_sync8 TEXT," +
812                "cal_sync9 TEXT," +
813                "cal_sync10 TEXT" +
814                ");");
815
816        // Trigger to remove a calendar's events when we delete the calendar
817        db.execSQL("CREATE TRIGGER calendar_cleanup DELETE ON Calendars " +
818                "BEGIN " +
819                "DELETE FROM Events WHERE calendar_id=old._id;" +
820                "END");
821    }
822
823    private void createCalendarsTable300(SQLiteDatabase db) {
824        db.execSQL("CREATE TABLE " + Tables.CALENDARS + " (" +
825                "_id INTEGER PRIMARY KEY," +
826                "account_name TEXT," +
827                "account_type TEXT," +
828                "_sync_id TEXT," +
829                "_sync_version TEXT," +
830                "_sync_time TEXT," +  // UTC
831                "dirty INTEGER," +
832                "name TEXT," +
833                "displayName TEXT," +
834                "calendar_color INTEGER," +
835                "access_level INTEGER," +
836                "visible INTEGER NOT NULL DEFAULT 1," +
837                "sync_events INTEGER NOT NULL DEFAULT 0," +
838                "calendar_location TEXT," +
839                "calendar_timezone TEXT," +
840                "ownerAccount TEXT, " +
841                "canOrganizerRespond INTEGER NOT NULL DEFAULT 1," +
842                "canModifyTimeZone INTEGER DEFAULT 1," +
843                "maxReminders INTEGER DEFAULT 5," +
844                "allowedReminders TEXT DEFAULT '0,1,2'," +
845                "deleted INTEGER NOT NULL DEFAULT 0," +
846                "sync1 TEXT," +
847                "sync2 TEXT," +
848                "sync3 TEXT," +
849                "sync4 TEXT," +
850                "sync5 TEXT," +
851                "sync6 TEXT" +
852                ");");
853
854        // Trigger to remove a calendar's events when we delete the calendar
855        db.execSQL("CREATE TRIGGER calendar_cleanup DELETE ON " + Tables.CALENDARS + " " +
856                "BEGIN " +
857                CALENDAR_CLEANUP_TRIGGER_SQL +
858                "END");
859    }
860
861    private void createCalendarsTable205(SQLiteDatabase db) {
862        db.execSQL("CREATE TABLE Calendars (" +
863                "_id INTEGER PRIMARY KEY," +
864                "_sync_account TEXT," +
865                "_sync_account_type TEXT," +
866                "_sync_id TEXT," +
867                "_sync_version TEXT," +
868                "_sync_time TEXT," +  // UTC
869                "_sync_dirty INTEGER," +
870                "name TEXT," +
871                "displayName TEXT," +
872                "color INTEGER," +
873                "access_level INTEGER," +
874                "visible INTEGER NOT NULL DEFAULT 1," +
875                "sync_events INTEGER NOT NULL DEFAULT 0," +
876                "location TEXT," +
877                "timezone TEXT," +
878                "ownerAccount TEXT, " +
879                "canOrganizerRespond INTEGER NOT NULL DEFAULT 1," +
880                "canModifyTimeZone INTEGER DEFAULT 1, " +
881                "maxReminders INTEGER DEFAULT 5," +
882                "deleted INTEGER NOT NULL DEFAULT 0," +
883                "sync1 TEXT," +
884                "sync2 TEXT," +
885                "sync3 TEXT," +
886                "sync4 TEXT," +
887                "sync5 TEXT," +
888                "sync6 TEXT" +
889                ");");
890
891        createCalendarsCleanup200(db);
892    }
893
894    private void createCalendarsTable202(SQLiteDatabase db) {
895        db.execSQL("CREATE TABLE Calendars (" +
896                "_id INTEGER PRIMARY KEY," +
897                "_sync_account TEXT," +
898                "_sync_account_type TEXT," +
899                "_sync_id TEXT," +
900                "_sync_version TEXT," +
901                "_sync_time TEXT," +  // UTC
902                "_sync_local_id INTEGER," +
903                "_sync_dirty INTEGER," +
904                "_sync_mark INTEGER," + // Used to filter out new rows
905                "name TEXT," +
906                "displayName TEXT," +
907                "color INTEGER," +
908                "access_level INTEGER," +
909                "selected INTEGER NOT NULL DEFAULT 1," +
910                "sync_events INTEGER NOT NULL DEFAULT 0," +
911                "location TEXT," +
912                "timezone TEXT," +
913                "ownerAccount TEXT, " +
914                "organizerCanRespond INTEGER NOT NULL DEFAULT 1," +
915                "deleted INTEGER NOT NULL DEFAULT 0," +
916                "sync1 TEXT," +
917                "sync2 TEXT," +
918                "sync3 TEXT," +
919                "sync4 TEXT," +
920                "sync5 TEXT" +
921                ");");
922
923        createCalendarsCleanup200(db);
924    }
925
926    private void createCalendarsTable200(SQLiteDatabase db) {
927        db.execSQL("CREATE TABLE Calendars (" +
928                "_id INTEGER PRIMARY KEY," +
929                "_sync_account TEXT," +
930                "_sync_account_type TEXT," +
931                "_sync_id TEXT," +
932                "_sync_version TEXT," +
933                "_sync_time TEXT," +  // UTC
934                "_sync_local_id INTEGER," +
935                "_sync_dirty INTEGER," +
936                "_sync_mark INTEGER," + // Used to filter out new rows
937                "name TEXT," +
938                "displayName TEXT," +
939                "hidden INTEGER NOT NULL DEFAULT 0," +
940                "color INTEGER," +
941                "access_level INTEGER," +
942                "selected INTEGER NOT NULL DEFAULT 1," +
943                "sync_events INTEGER NOT NULL DEFAULT 0," +
944                "location TEXT," +
945                "timezone TEXT," +
946                "ownerAccount TEXT, " +
947                "organizerCanRespond INTEGER NOT NULL DEFAULT 1," +
948                "deleted INTEGER NOT NULL DEFAULT 0," +
949                "sync1 TEXT," +
950                "sync2 TEXT," +
951                "sync3 TEXT" +
952                ");");
953
954        createCalendarsCleanup200(db);
955    }
956
957    /** Trigger to remove a calendar's events when we delete the calendar */
958    private void createCalendarsCleanup200(SQLiteDatabase db) {
959        db.execSQL("CREATE TRIGGER calendar_cleanup DELETE ON Calendars " +
960                "BEGIN " +
961                "DELETE FROM Events WHERE calendar_id=old._id;" +
962                "END");
963    }
964
965    private void createCalendarMetaDataTable(SQLiteDatabase db) {
966        db.execSQL("CREATE TABLE " + Tables.CALENDAR_META_DATA + " (" +
967                CalendarContract.CalendarMetaData._ID + " INTEGER PRIMARY KEY," +
968                CalendarContract.CalendarMetaData.LOCAL_TIMEZONE + " TEXT," +
969                CalendarContract.CalendarMetaData.MIN_INSTANCE + " INTEGER," +      // UTC millis
970                CalendarContract.CalendarMetaData.MAX_INSTANCE + " INTEGER" +       // UTC millis
971                ");");
972    }
973
974    private void createCalendarMetaDataTable59(SQLiteDatabase db) {
975        db.execSQL("CREATE TABLE CalendarMetaData (" +
976                "_id INTEGER PRIMARY KEY," +
977                "localTimezone TEXT," +
978                "minInstance INTEGER," +      // UTC millis
979                "maxInstance INTEGER" +       // UTC millis
980                ");");
981    }
982
983    private void createCalendarCacheTable(SQLiteDatabase db, String oldTimezoneDbVersion) {
984        // This is a hack because versioning skipped version number 61 of schema
985        // TODO after version 70 this can be removed
986        db.execSQL("DROP TABLE IF EXISTS " + Tables.CALENDAR_CACHE + ";");
987
988        // IF NOT EXISTS should be normal pattern for table creation
989        db.execSQL("CREATE TABLE IF NOT EXISTS " + Tables.CALENDAR_CACHE + " (" +
990                CalendarCache.COLUMN_NAME_ID + " INTEGER PRIMARY KEY," +
991                CalendarCache.COLUMN_NAME_KEY + " TEXT NOT NULL," +
992                CalendarCache.COLUMN_NAME_VALUE + " TEXT" +
993                ");");
994
995        initCalendarCacheTable(db, oldTimezoneDbVersion);
996        updateCalendarCacheTable(db);
997    }
998
999    private void initCalendarCacheTable(SQLiteDatabase db, String oldTimezoneDbVersion) {
1000        String timezoneDbVersion = (oldTimezoneDbVersion != null) ?
1001                oldTimezoneDbVersion : CalendarCache.DEFAULT_TIMEZONE_DATABASE_VERSION;
1002
1003        // Set the default timezone database version
1004        db.execSQL("INSERT OR REPLACE INTO " + Tables.CALENDAR_CACHE +
1005                " (" + CalendarCache.COLUMN_NAME_ID + ", " +
1006                CalendarCache.COLUMN_NAME_KEY + ", " +
1007                CalendarCache.COLUMN_NAME_VALUE + ") VALUES (" +
1008                CalendarCache.KEY_TIMEZONE_DATABASE_VERSION.hashCode() + "," +
1009                "'" + CalendarCache.KEY_TIMEZONE_DATABASE_VERSION + "'," +
1010                "'" + timezoneDbVersion + "'" +
1011                ");");
1012    }
1013
1014    private void updateCalendarCacheTable(SQLiteDatabase db) {
1015        // Define the default timezone type for Instances timezone management
1016        db.execSQL("INSERT INTO " + Tables.CALENDAR_CACHE +
1017                " (" + CalendarCache.COLUMN_NAME_ID + ", " +
1018                CalendarCache.COLUMN_NAME_KEY + ", " +
1019                CalendarCache.COLUMN_NAME_VALUE + ") VALUES (" +
1020                CalendarCache.KEY_TIMEZONE_TYPE.hashCode() + "," +
1021                "'" + CalendarCache.KEY_TIMEZONE_TYPE + "',"  +
1022                "'" + CalendarCache.TIMEZONE_TYPE_AUTO + "'" +
1023                ");");
1024
1025        String defaultTimezone = TimeZone.getDefault().getID();
1026
1027        // Define the default timezone for Instances
1028        db.execSQL("INSERT INTO " + Tables.CALENDAR_CACHE +
1029                " (" + CalendarCache.COLUMN_NAME_ID + ", " +
1030                CalendarCache.COLUMN_NAME_KEY + ", " +
1031                CalendarCache.COLUMN_NAME_VALUE + ") VALUES (" +
1032                CalendarCache.KEY_TIMEZONE_INSTANCES.hashCode() + "," +
1033                "'" + CalendarCache.KEY_TIMEZONE_INSTANCES + "',"  +
1034                "'" + defaultTimezone + "'" +
1035                ");");
1036
1037        // Define the default previous timezone for Instances
1038        db.execSQL("INSERT INTO " + Tables.CALENDAR_CACHE +
1039                " (" + CalendarCache.COLUMN_NAME_ID + ", " +
1040                CalendarCache.COLUMN_NAME_KEY + ", " +
1041                CalendarCache.COLUMN_NAME_VALUE + ") VALUES (" +
1042                CalendarCache.KEY_TIMEZONE_INSTANCES_PREVIOUS.hashCode() + "," +
1043                "'" + CalendarCache.KEY_TIMEZONE_INSTANCES_PREVIOUS + "',"  +
1044                "'" + defaultTimezone + "'" +
1045                ");");
1046    }
1047
1048    private void initCalendarCacheTable203(SQLiteDatabase db, String oldTimezoneDbVersion) {
1049        String timezoneDbVersion = (oldTimezoneDbVersion != null) ?
1050                oldTimezoneDbVersion : "2009s";
1051
1052        // Set the default timezone database version
1053        db.execSQL("INSERT OR REPLACE INTO CalendarCache" +
1054                " (_id, " +
1055                "key, " +
1056                "value) VALUES (" +
1057                "timezoneDatabaseVersion".hashCode() + "," +
1058                "'timezoneDatabaseVersion',"  +
1059                "'" + timezoneDbVersion + "'" +
1060                ");");
1061    }
1062
1063    private void updateCalendarCacheTableTo203(SQLiteDatabase db) {
1064        // Define the default timezone type for Instances timezone management
1065        db.execSQL("INSERT INTO CalendarCache" +
1066                " (_id, key, value) VALUES (" +
1067                "timezoneType".hashCode() + "," +
1068                "'timezoneType',"  +
1069                "'auto'" +
1070                ");");
1071
1072        String defaultTimezone = TimeZone.getDefault().getID();
1073
1074        // Define the default timezone for Instances
1075        db.execSQL("INSERT INTO CalendarCache" +
1076                " (_id, key, value) VALUES (" +
1077                "timezoneInstances".hashCode() + "," +
1078                "'timezoneInstances',"  +
1079                "'" + defaultTimezone + "'" +
1080                ");");
1081
1082        // Define the default previous timezone for Instances
1083        db.execSQL("INSERT INTO CalendarCache" +
1084                " (_id, key, value) VALUES (" +
1085                "timezoneInstancesPrevious".hashCode() + "," +
1086                "'timezoneInstancesPrevious',"  +
1087                "'" + defaultTimezone + "'" +
1088                ");");
1089    }
1090
1091    /**
1092     * Removes orphaned data from the database.  Specifically:
1093     * <ul>
1094     * <li>Attendees with an event_id for a nonexistent Event
1095     * <li>Reminders with an event_id for a nonexistent Event
1096     * </ul>
1097     */
1098    static void removeOrphans(SQLiteDatabase db) {
1099        if (false) {        // debug mode
1100            String SELECT_ATTENDEES_ORPHANS = "SELECT " +
1101                    Attendees._ID + ", " + Attendees.EVENT_ID + " FROM " + Tables.ATTENDEES +
1102                    " WHERE " + WHERE_ATTENDEES_ORPHANS;
1103
1104            Cursor cursor = null;
1105            try {
1106                Log.i(TAG, "Attendees orphans:");
1107                cursor = db.rawQuery(SELECT_ATTENDEES_ORPHANS, null);
1108                DatabaseUtils.dumpCursor(cursor);
1109            } finally {
1110                if (cursor != null) {
1111                    cursor.close();
1112                }
1113            }
1114
1115            String SELECT_REMINDERS_ORPHANS = "SELECT " +
1116                    Attendees._ID + ", " + Reminders.EVENT_ID + " FROM " + Tables.REMINDERS +
1117                    " WHERE " + WHERE_REMINDERS_ORPHANS;
1118            cursor = null;
1119            try {
1120                Log.i(TAG, "Reminders orphans:");
1121                cursor = db.rawQuery(SELECT_REMINDERS_ORPHANS, null);
1122                DatabaseUtils.dumpCursor(cursor);
1123            } finally {
1124                if (cursor != null) {
1125                    cursor.close();
1126                }
1127            }
1128
1129            return;
1130        }
1131
1132        Log.d(TAG, "Checking for orphaned entries");
1133        int count;
1134
1135        count = db.delete(Tables.ATTENDEES, WHERE_ATTENDEES_ORPHANS, null);
1136        if (count != 0) {
1137            Log.i(TAG, "Deleted " + count + " orphaned Attendees");
1138        }
1139
1140        count = db.delete(Tables.REMINDERS, WHERE_REMINDERS_ORPHANS, null);
1141        if (count != 0) {
1142            Log.i(TAG, "Deleted " + count + " orphaned Reminders");
1143        }
1144    }
1145
1146
1147    @Override
1148    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
1149        Log.i(TAG, "Upgrading DB from version " + oldVersion + " to " + newVersion);
1150        long startWhen = System.nanoTime();
1151
1152        if (oldVersion < 49) {
1153            dropTables(db);
1154            mSyncState.createDatabase(db);
1155            return; // this was lossy
1156        }
1157
1158        // From schema versions 59 to version 66, the CalendarMetaData table definition had lost
1159        // the primary key leading to having the CalendarMetaData with multiple rows instead of
1160        // only one. The Instance table was then corrupted (during Instance expansion we are using
1161        // the localTimezone, minInstance and maxInstance from CalendarMetaData table.
1162        // This boolean helps us tracking the need to recreate the CalendarMetaData table and
1163        // clear the Instance table (and thus force an Instance expansion).
1164        boolean recreateMetaDataAndInstances = (oldVersion >= 59 && oldVersion <= 66);
1165        boolean createEventsView = false;
1166
1167        try {
1168            if (oldVersion < 51) {
1169                upgradeToVersion51(db); // From 50 or 51
1170                oldVersion = 51;
1171            }
1172            if (oldVersion == 51) {
1173                upgradeToVersion52(db);
1174                oldVersion += 1;
1175            }
1176            if (oldVersion == 52) {
1177                upgradeToVersion53(db);
1178                oldVersion += 1;
1179            }
1180            if (oldVersion == 53) {
1181                upgradeToVersion54(db);
1182                oldVersion += 1;
1183            }
1184            if (oldVersion == 54) {
1185                upgradeToVersion55(db);
1186                oldVersion += 1;
1187            }
1188            if (oldVersion == 55 || oldVersion == 56) {
1189                // Both require resync, so just schedule it once
1190                upgradeResync(db);
1191            }
1192            if (oldVersion == 55) {
1193                upgradeToVersion56(db);
1194                oldVersion += 1;
1195            }
1196            if (oldVersion == 56) {
1197                upgradeToVersion57(db);
1198                oldVersion += 1;
1199            }
1200            if (oldVersion == 57) {
1201                // Changes are undone upgrading to 60, so don't do anything.
1202                oldVersion += 1;
1203            }
1204            if (oldVersion == 58) {
1205                upgradeToVersion59(db);
1206                oldVersion += 1;
1207            }
1208            if (oldVersion == 59) {
1209                upgradeToVersion60(db);
1210                createEventsView = true;
1211                oldVersion += 1;
1212            }
1213            if (oldVersion == 60) {
1214                upgradeToVersion61(db);
1215                oldVersion += 1;
1216            }
1217            if (oldVersion == 61) {
1218                upgradeToVersion62(db);
1219                oldVersion += 1;
1220            }
1221            if (oldVersion == 62) {
1222                createEventsView = true;
1223                oldVersion += 1;
1224            }
1225            if (oldVersion == 63) {
1226                upgradeToVersion64(db);
1227                oldVersion += 1;
1228            }
1229            if (oldVersion == 64) {
1230                createEventsView = true;
1231                oldVersion += 1;
1232            }
1233            if (oldVersion == 65) {
1234                upgradeToVersion66(db);
1235                oldVersion += 1;
1236            }
1237            if (oldVersion == 66) {
1238                // Changes are done thru recreateMetaDataAndInstances() method
1239                oldVersion += 1;
1240            }
1241            if (recreateMetaDataAndInstances) {
1242                recreateMetaDataAndInstances67(db);
1243            }
1244            if (oldVersion == 67 || oldVersion == 68) {
1245                upgradeToVersion69(db);
1246                oldVersion = 69;
1247            }
1248            // 69. 70 are for Froyo/old Gingerbread only and 100s are for Gingerbread only
1249            // 70 and 71 have been for Honeycomb but no more used
1250            // 72 and 73 and 74 were for Honeycomb only but are considered as obsolete for enabling
1251            // room for Froyo version numbers
1252            if(oldVersion == 69) {
1253                upgradeToVersion200(db);
1254                createEventsView = true;
1255                oldVersion = 200;
1256            }
1257            if (oldVersion == 70) {
1258                upgradeToVersion200(db);
1259                oldVersion = 200;
1260            }
1261            if (oldVersion == 100) {
1262                // note we skip past v101 and v102
1263                upgradeToVersion200(db);
1264                oldVersion = 200;
1265            }
1266            boolean need203Update = true;
1267            if (oldVersion == 101 || oldVersion == 102) {
1268                // v101 is v100 plus updateCalendarCacheTableTo203().
1269                // v102 is v101 with Event._id changed to autoincrement.
1270                // Upgrade to 200 and skip the 203 update.
1271                upgradeToVersion200(db);
1272                oldVersion = 200;
1273                need203Update = false;
1274            }
1275            if (oldVersion == 200) {
1276                upgradeToVersion201(db);
1277                oldVersion += 1;
1278            }
1279            if (oldVersion == 201) {
1280                upgradeToVersion202(db);
1281                createEventsView = true;
1282                oldVersion += 1;
1283            }
1284            if (oldVersion == 202) {
1285                if (need203Update) {
1286                    upgradeToVersion203(db);
1287                }
1288                oldVersion += 1;
1289            }
1290            if (oldVersion == 203) {
1291                createEventsView = true;
1292                oldVersion += 1;
1293            }
1294            if (oldVersion == 206) {
1295                // v206 exists only in HC (change Event._id to autoincrement).  Otherwise
1296                // identical to v204, so back it up and let the upgrade path continue.
1297                oldVersion -= 2;
1298            }
1299            if (oldVersion == 204) {
1300                // This is an ICS update, all following use 300+ versions.
1301                upgradeToVersion205(db);
1302                createEventsView = true;
1303                oldVersion += 1;
1304            }
1305            if (oldVersion == 205) {
1306                // Move ICS updates to 300 range
1307                upgradeToVersion300(db);
1308                createEventsView = true;
1309                oldVersion = 300;
1310            }
1311            if (oldVersion == 300) {
1312                upgradeToVersion301(db);
1313                createEventsView = true;
1314                oldVersion++;
1315            }
1316            if (oldVersion == 301) {
1317                upgradeToVersion302(db);
1318                oldVersion++;
1319            }
1320            if (oldVersion == 302) {
1321                upgradeToVersion303(db);
1322                oldVersion++;
1323                createEventsView = true;
1324            }
1325            if (oldVersion == 303) {
1326                upgradeToVersion304(db);
1327                oldVersion++;
1328                createEventsView = true;
1329            }
1330            if (oldVersion == 304) {
1331                upgradeToVersion305(db);
1332                oldVersion++;
1333                createEventsView = true;
1334            }
1335            if (oldVersion == 305) {
1336                upgradeToVersion306(db);
1337                // force a sync to update edit url and etag
1338                scheduleSync(null /* all accounts */, false, null);
1339                oldVersion++;
1340            }
1341            if (oldVersion == 306) {
1342                upgradeToVersion307(db);
1343                oldVersion++;
1344            }
1345            if (oldVersion == 307) {
1346                upgradeToVersion308(db);
1347                oldVersion++;
1348                createEventsView = true;
1349            }
1350            if (createEventsView) {
1351                createEventsView(db);
1352            }
1353            if (oldVersion != DATABASE_VERSION) {
1354                Log.e(TAG, "Need to recreate Calendar schema because of "
1355                        + "unknown Calendar database version: " + oldVersion);
1356                dropTables(db);
1357                bootstrapDB(db);
1358                oldVersion = DATABASE_VERSION;
1359            } else {
1360                removeOrphans(db);
1361            }
1362        } catch (SQLiteException e) {
1363            Log.e(TAG, "onUpgrade: SQLiteException, recreating db. ", e);
1364            Log.e(TAG, "(oldVersion was " + oldVersion + ")");
1365            dropTables(db);
1366            bootstrapDB(db);
1367            return; // this was lossy
1368        }
1369
1370        long endWhen = System.nanoTime();
1371        Log.d(TAG, "Calendar upgrade took " + ((endWhen - startWhen) / 1000000) + "ms");
1372
1373        /**
1374         * db versions < 100 correspond to Froyo and earlier. Gingerbread bumped
1375         * the db versioning to 100. Honeycomb bumped it to 200. ICS will begin
1376         * in 300. At each major release we should jump to the next
1377         * centiversion.
1378         */
1379    }
1380
1381    /**
1382     * If the user_version of the database if between 59 and 66 (those versions has been deployed
1383     * with no primary key for the CalendarMetaData table)
1384     */
1385    private void recreateMetaDataAndInstances67(SQLiteDatabase db) {
1386        // Recreate the CalendarMetaData table with correct primary key
1387        db.execSQL("DROP TABLE CalendarMetaData;");
1388        createCalendarMetaDataTable59(db);
1389
1390        // Also clean the Instance table as this table may be corrupted
1391        db.execSQL("DELETE FROM Instances;");
1392    }
1393
1394    private static boolean fixAllDayTime(Time time, String timezone, Long timeInMillis) {
1395        time.set(timeInMillis);
1396        if(time.hour != 0 || time.minute != 0 || time.second != 0) {
1397            time.hour = 0;
1398            time.minute = 0;
1399            time.second = 0;
1400            return true;
1401        }
1402        return false;
1403    }
1404
1405    @VisibleForTesting
1406    void upgradeToVersion308(SQLiteDatabase db) {
1407        /*
1408         * Changes from version 307 to 308:
1409         * - add Colors table to db
1410         * - add eventColor_index to Events table
1411         * - add calendar_color_index to Calendars table
1412         * - add allowedAttendeeTypes to Calendars table
1413         * - add allowedAvailability to Calendars table
1414         */
1415        createColorsTable(db);
1416
1417        db.execSQL("ALTER TABLE Calendars ADD COLUMN allowedAvailability TEXT DEFAULT '0,1';");
1418        db.execSQL("ALTER TABLE Calendars ADD COLUMN allowedAttendeeTypes TEXT DEFAULT '0,1,2';");
1419        db.execSQL("ALTER TABLE Calendars ADD COLUMN calendar_color_index TEXT;");
1420        db.execSQL("ALTER TABLE Events ADD COLUMN eventColor_index TEXT;");
1421
1422        // Default Exchange calendars to be supporting the 'tentative'
1423        // availability as well
1424        db.execSQL("UPDATE Calendars SET allowedAvailability='0,1,2' WHERE _id IN "
1425                + "(SELECT _id FROM Calendars WHERE account_type='com.android.exchange');");
1426
1427        // Triggers to update the color stored in an event or a calendar when
1428        // the color_index is changed.
1429        createColorsTriggers(db);
1430    }
1431
1432    @VisibleForTesting
1433    void upgradeToVersion307(SQLiteDatabase db) {
1434        /*
1435         * Changes from version 306 to 307:
1436         * - Changed _id field to AUTOINCREMENT
1437         */
1438        db.execSQL("ALTER TABLE Events RENAME TO Events_Backup;");
1439        db.execSQL("DROP TRIGGER IF EXISTS events_cleanup_delete");
1440        db.execSQL("DROP TRIGGER IF EXISTS original_sync_update");
1441        db.execSQL("DROP INDEX IF EXISTS eventsCalendarIdIndex");
1442        createEventsTable307(db);
1443
1444        String FIELD_LIST =
1445            "_id, " +
1446            "_sync_id, " +
1447            "dirty, " +
1448            "lastSynced," +
1449            "calendar_id, " +
1450            "title, " +
1451            "eventLocation, " +
1452            "description, " +
1453            "eventColor, " +
1454            "eventStatus, " +
1455            "selfAttendeeStatus, " +
1456            "dtstart, " +
1457            "dtend, " +
1458            "eventTimezone, " +
1459            "duration, " +
1460            "allDay, " +
1461            "accessLevel, " +
1462            "availability, " +
1463            "hasAlarm, " +
1464            "hasExtendedProperties, " +
1465            "rrule, " +
1466            "rdate, " +
1467            "exrule, " +
1468            "exdate, " +
1469            "original_id," +
1470            "original_sync_id, " +
1471            "originalInstanceTime, " +
1472            "originalAllDay, " +
1473            "lastDate, " +
1474            "hasAttendeeData, " +
1475            "guestsCanModify, " +
1476            "guestsCanInviteOthers, " +
1477            "guestsCanSeeGuests, " +
1478            "organizer, " +
1479            "deleted, " +
1480            "eventEndTimezone, " +
1481            "sync_data1," +
1482            "sync_data2," +
1483            "sync_data3," +
1484            "sync_data4," +
1485            "sync_data5," +
1486            "sync_data6," +
1487            "sync_data7," +
1488            "sync_data8," +
1489            "sync_data9," +
1490            "sync_data10 ";
1491
1492        // copy fields from old to new
1493        db.execSQL("INSERT INTO Events (" + FIELD_LIST + ") SELECT " + FIELD_LIST +
1494                "FROM Events_Backup;");
1495
1496        db.execSQL("DROP TABLE Events_Backup;");
1497
1498        // Trigger to remove data tied to an event when we delete that event.
1499        db.execSQL("CREATE TRIGGER events_cleanup_delete DELETE ON " + Tables.EVENTS + " " +
1500                "BEGIN " + EVENTS_CLEANUP_TRIGGER_SQL + "END");
1501
1502        // Trigger to update exceptions when an original event updates its
1503        // _sync_id
1504        db.execSQL(CREATE_SYNC_ID_UPDATE_TRIGGER);
1505    }
1506
1507    @VisibleForTesting
1508    void upgradeToVersion306(SQLiteDatabase db) {
1509        /*
1510        * The following changes are for google.com accounts only.
1511        *
1512        * Change event id's from ".../private/full/... to .../events/...
1513        * Set Calendars.canPartiallyUpdate to 1 to support partial updates
1514        * Nuke sync state so we re-sync with a fresh etag and edit url
1515        */
1516        db.execSQL("UPDATE Events SET "
1517                + "_sync_id = REPLACE(_sync_id, '/private/full/', '/events/'), "
1518                + "original_sync_id = REPLACE(original_sync_id, '/private/full/', '/events/') "
1519                + "WHERE _id IN (SELECT Events._id FROM Events "
1520                +    "JOIN Calendars ON Events.calendar_id = Calendars._id "
1521                +    "WHERE account_type = 'com.google')"
1522        );
1523
1524        db.execSQL("UPDATE Calendars SET canPartiallyUpdate = 1 WHERE account_type = 'com.google'");
1525
1526        db.execSQL("DELETE FROM _sync_state WHERE account_type = 'com.google'");
1527    }
1528
1529    @VisibleForTesting
1530    void upgradeToVersion305(SQLiteDatabase db) {
1531        /*
1532         * Changes from version 304 to 305:
1533         * -Add CAL_SYNC columns up to 10
1534         * -Rename Calendars.access_level to calendar_access_level
1535         * -Rename calendars _sync_version to cal_sync7
1536         * -Rename calendars _sync_time to cal_sync8
1537         * -Rename displayName to calendar_displayName
1538         * -Rename _sync_local_id to sync_data2
1539         * -Rename htmlUri to sync_data3
1540         * -Rename events _sync_version to sync_data4
1541         * -Rename events _sync_time to sync_data5
1542         * -Rename commentsUri to sync_data6
1543         * -Migrate Events _sync_mark to sync_data8
1544         * -Change sync_data2 from INTEGER to TEXT
1545         * -Change sync_data8 from INTEGER to TEXT
1546         * -Add SYNC_DATA columns up to 10
1547         * -Add EVENT_COLOR to Events table
1548         */
1549
1550        // rename old table, create new table with updated layout
1551        db.execSQL("ALTER TABLE Calendars RENAME TO Calendars_Backup;");
1552        db.execSQL("DROP TRIGGER IF EXISTS calendar_cleanup");
1553        createCalendarsTable305(db);
1554
1555        // copy fields from old to new
1556        db.execSQL("INSERT INTO Calendars (" +
1557                "_id, " +
1558                "account_name, " +
1559                "account_type, " +
1560                "_sync_id, " +
1561                "cal_sync7, " +             // rename from _sync_version
1562                "cal_sync8, " +             // rename from _sync_time
1563                "dirty, " +
1564                "name, " +
1565                "calendar_displayName, " +  // rename from displayName
1566                "calendar_color, " +
1567                "calendar_access_level, " + // rename from access_level
1568                "visible, " +
1569                "sync_events, " +
1570                "calendar_location, " +
1571                "calendar_timezone, " +
1572                "ownerAccount, " +
1573                "canOrganizerRespond, " +
1574                "canModifyTimeZone, " +
1575                "maxReminders, " +
1576                "allowedReminders, " +
1577                "deleted, " +
1578                "canPartiallyUpdate," +
1579                "cal_sync1, " +
1580                "cal_sync2, " +
1581                "cal_sync3, " +
1582                "cal_sync4, " +
1583                "cal_sync5, " +
1584                "cal_sync6) " +
1585                "SELECT " +
1586                "_id, " +
1587                "account_name, " +
1588                "account_type, " +
1589                "_sync_id, " +
1590                "_sync_version, " +
1591                "_sync_time, " +
1592                "dirty, " +
1593                "name, " +
1594                "displayName, " +
1595                "calendar_color, " +
1596                "access_level, " +
1597                "visible, " +
1598                "sync_events, " +
1599                "calendar_location, " +
1600                "calendar_timezone, " +
1601                "ownerAccount, " +
1602                "canOrganizerRespond, " +
1603                "canModifyTimeZone, " +
1604                "maxReminders, " +
1605                "allowedReminders, " +
1606                "deleted, " +
1607                "canPartiallyUpdate," +
1608                "cal_sync1, " +
1609                "cal_sync2, " +
1610                "cal_sync3, " +
1611                "cal_sync4, " +
1612                "cal_sync5, " +
1613                "cal_sync6 " +
1614                "FROM Calendars_Backup;");
1615
1616        // drop the old table
1617        db.execSQL("DROP TABLE Calendars_Backup;");
1618
1619        db.execSQL("ALTER TABLE Events RENAME TO Events_Backup;");
1620        db.execSQL("DROP TRIGGER IF EXISTS events_cleanup_delete");
1621        db.execSQL("DROP INDEX IF EXISTS eventsCalendarIdIndex");
1622        // 305 and 307 can share the same createEventsTable implementation, because the
1623        // addition of "autoincrement" to _ID doesn't affect the upgrade path.  (Note that
1624        // much older databases may also already have autoincrement set because the change
1625        // was back-ported.)
1626        createEventsTable307(db);
1627
1628        // copy fields from old to new
1629        db.execSQL("INSERT INTO Events (" +
1630                "_id, " +
1631                "_sync_id, " +
1632                "sync_data4, " +        // renamed from _sync_version
1633                "sync_data5, " +        // renamed from _sync_time
1634                "sync_data2, " +        // renamed from _sync_local_id
1635                "dirty, " +
1636                "sync_data8, " +        // renamed from _sync_mark
1637                "calendar_id, " +
1638                "sync_data3, " +        // renamed from htmlUri
1639                "title, " +
1640                "eventLocation, " +
1641                "description, " +
1642                "eventStatus, " +
1643                "selfAttendeeStatus, " +
1644                "sync_data6, " +        // renamed from commentsUri
1645                "dtstart, " +
1646                "dtend, " +
1647                "eventTimezone, " +
1648                "eventEndTimezone, " +
1649                "duration, " +
1650                "allDay, " +
1651                "accessLevel, " +
1652                "availability, " +
1653                "hasAlarm, " +
1654                "hasExtendedProperties, " +
1655                "rrule, " +
1656                "rdate, " +
1657                "exrule, " +
1658                "exdate, " +
1659                "original_id," +
1660                "original_sync_id, " +
1661                "originalInstanceTime, " +
1662                "originalAllDay, " +
1663                "lastDate, " +
1664                "hasAttendeeData, " +
1665                "guestsCanModify, " +
1666                "guestsCanInviteOthers, " +
1667                "guestsCanSeeGuests, " +
1668                "organizer, " +
1669                "deleted, " +
1670                "sync_data7," +
1671                "lastSynced," +
1672                "sync_data1) " +
1673
1674                "SELECT " +
1675                "_id, " +
1676                "_sync_id, " +
1677                "_sync_version, " +
1678                "_sync_time, " +
1679                "_sync_local_id, " +
1680                "dirty, " +
1681                "_sync_mark, " +
1682                "calendar_id, " +
1683                "htmlUri, " +
1684                "title, " +
1685                "eventLocation, " +
1686                "description, " +
1687                "eventStatus, " +
1688                "selfAttendeeStatus, " +
1689                "commentsUri, " +
1690                "dtstart, " +
1691                "dtend, " +
1692                "eventTimezone, " +
1693                "eventEndTimezone, " +
1694                "duration, " +
1695                "allDay, " +
1696                "accessLevel, " +
1697                "availability, " +
1698                "hasAlarm, " +
1699                "hasExtendedProperties, " +
1700                "rrule, " +
1701                "rdate, " +
1702                "exrule, " +
1703                "exdate, " +
1704                "original_id," +
1705                "original_sync_id, " +
1706                "originalInstanceTime, " +
1707                "originalAllDay, " +
1708                "lastDate, " +
1709                "hasAttendeeData, " +
1710                "guestsCanModify, " +
1711                "guestsCanInviteOthers, " +
1712                "guestsCanSeeGuests, " +
1713                "organizer, " +
1714                "deleted, " +
1715                "sync_data7," +
1716                "lastSynced," +
1717                "sync_data1 " +
1718
1719                "FROM Events_Backup;"
1720        );
1721
1722        db.execSQL("DROP TABLE Events_Backup;");
1723
1724        // Trigger to remove data tied to an event when we delete that event.
1725        db.execSQL("CREATE TRIGGER events_cleanup_delete DELETE ON " + Tables.EVENTS + " " +
1726                "BEGIN " +
1727                EVENTS_CLEANUP_TRIGGER_SQL +
1728                "END");
1729
1730        // Trigger to update exceptions when an original event updates its
1731        // _sync_id
1732        db.execSQL(CREATE_SYNC_ID_UPDATE_TRIGGER);
1733    }
1734
1735    @VisibleForTesting
1736    void upgradeToVersion304(SQLiteDatabase db) {
1737        /*
1738         * Changes from version 303 to 304:
1739         * - add canPartiallyUpdate to Calendars table
1740         * - add sync_data7 to Calendars to Events table
1741         * - add lastSynced to Calendars to Events table
1742         */
1743        db.execSQL("ALTER TABLE Calendars ADD COLUMN canPartiallyUpdate INTEGER DEFAULT 0;");
1744        db.execSQL("ALTER TABLE Events ADD COLUMN sync_data7 TEXT;");
1745        db.execSQL("ALTER TABLE Events ADD COLUMN lastSynced INTEGER DEFAULT 0;");
1746    }
1747
1748    @VisibleForTesting
1749    void upgradeToVersion303(SQLiteDatabase db) {
1750        /*
1751         * Changes from version 302 to 303:
1752         * - change SYNCx columns to CAL_SYNCx
1753         */
1754
1755        // rename old table, create new table with updated layout
1756        db.execSQL("ALTER TABLE Calendars RENAME TO Calendars_Backup;");
1757        db.execSQL("DROP TRIGGER IF EXISTS calendar_cleanup");
1758        createCalendarsTable303(db);
1759
1760        // copy fields from old to new
1761        db.execSQL("INSERT INTO Calendars (" +
1762                "_id, " +
1763                "account_name, " +
1764                "account_type, " +
1765                "_sync_id, " +
1766                "_sync_version, " +
1767                "_sync_time, " +
1768                "dirty, " +
1769                "name, " +
1770                "displayName, " +
1771                "calendar_color, " +
1772                "access_level, " +
1773                "visible, " +
1774                "sync_events, " +
1775                "calendar_location, " +
1776                "calendar_timezone, " +
1777                "ownerAccount, " +
1778                "canOrganizerRespond, " +
1779                "canModifyTimeZone, " +
1780                "maxReminders, " +
1781                "allowedReminders, " +
1782                "deleted, " +
1783                "cal_sync1, " +     // rename from sync1
1784                "cal_sync2, " +     // rename from sync2
1785                "cal_sync3, " +     // rename from sync3
1786                "cal_sync4, " +     // rename from sync4
1787                "cal_sync5, " +     // rename from sync5
1788                "cal_sync6) " +     // rename from sync6
1789                "SELECT " +
1790                "_id, " +
1791                "account_name, " +
1792                "account_type, " +
1793                "_sync_id, " +
1794                "_sync_version, " +
1795                "_sync_time, " +
1796                "dirty, " +
1797                "name, " +
1798                "displayName, " +
1799                "calendar_color, " +
1800                "access_level, " +
1801                "visible, " +
1802                "sync_events, " +
1803                "calendar_location, " +
1804                "calendar_timezone, " +
1805                "ownerAccount, " +
1806                "canOrganizerRespond, " +
1807                "canModifyTimeZone, " +
1808                "maxReminders, " +
1809                "allowedReminders," +
1810                "deleted, " +
1811                "sync1, " +
1812                "sync2, " +
1813                "sync3, " +
1814                "sync4," +
1815                "sync5," +
1816                "sync6 " +
1817                "FROM Calendars_Backup;"
1818        );
1819
1820        // drop the old table
1821        db.execSQL("DROP TABLE Calendars_Backup;");
1822    }
1823
1824    @VisibleForTesting
1825    void upgradeToVersion302(SQLiteDatabase db) {
1826        /*
1827         * Changes from version 301 to 302
1828         * - Move Exchange eventEndTimezone values to SYNC_DATA1
1829         */
1830        db.execSQL("UPDATE Events SET sync_data1=eventEndTimezone WHERE calendar_id IN "
1831                + "(SELECT _id FROM Calendars WHERE account_type='com.android.exchange');");
1832
1833        db.execSQL("UPDATE Events SET eventEndTimezone=NULL WHERE calendar_id IN "
1834                + "(SELECT _id FROM Calendars WHERE account_type='com.android.exchange');");
1835    }
1836
1837    @VisibleForTesting
1838    void upgradeToVersion301(SQLiteDatabase db) {
1839        /*
1840         * Changes from version 300 to 301
1841         * - Added original_id column to Events table
1842         * - Added triggers to keep original_id and original_sync_id in sync
1843         */
1844
1845        db.execSQL("DROP TRIGGER IF EXISTS " + SYNC_ID_UPDATE_TRIGGER_NAME + ";");
1846
1847        db.execSQL("ALTER TABLE Events ADD COLUMN original_id INTEGER;");
1848
1849        // Fill in the original_id for all events that have an original_sync_id
1850        db.execSQL("UPDATE Events set original_id=" +
1851                "(SELECT Events2._id FROM Events AS Events2 " +
1852                        "WHERE Events2._sync_id=Events.original_sync_id) " +
1853                "WHERE Events.original_sync_id NOT NULL");
1854        // Trigger to update exceptions when an original event updates its
1855        // _sync_id
1856        db.execSQL(CREATE_SYNC_ID_UPDATE_TRIGGER);
1857    }
1858
1859    @VisibleForTesting
1860    void upgradeToVersion300(SQLiteDatabase db) {
1861
1862        /*
1863         * Changes from version 205 to 300:
1864         * - rename _sync_account to account_name in Calendars table
1865         * - remove _sync_account from Events table
1866         * - rename _sync_account_type to account_type in Calendars table
1867         * - remove _sync_account_type from Events table
1868         * - rename _sync_dirty to dirty in Calendars/Events table
1869         * - rename color to calendar_color in Calendars table
1870         * - rename location to calendar_location in Calendars table
1871         * - rename timezone to calendar_timezone in Calendars table
1872         * - add allowedReminders in Calendars table
1873         * - rename visibility to accessLevel in Events table
1874         * - rename transparency to availability in Events table
1875         * - rename originalEvent to original_sync_id in Events table
1876         * - remove dtstart2 and dtend2 from Events table
1877         * - rename syncAdapterData to sync_data1 in Events table
1878         */
1879
1880        // rename old table, create new table with updated layout
1881        db.execSQL("ALTER TABLE Calendars RENAME TO Calendars_Backup;");
1882        db.execSQL("DROP TRIGGER IF EXISTS calendar_cleanup;");
1883        createCalendarsTable300(db);
1884
1885        // copy fields from old to new
1886        db.execSQL("INSERT INTO Calendars (" +
1887                "_id, " +
1888                "account_name, " +          // rename from _sync_account
1889                "account_type, " +          // rename from _sync_account_type
1890                "_sync_id, " +
1891                "_sync_version, " +
1892                "_sync_time, " +
1893                "dirty, " +                 // rename from _sync_dirty
1894                "name, " +
1895                "displayName, " +
1896                "calendar_color, " +        // rename from color
1897                "access_level, " +
1898                "visible, " +
1899                "sync_events, " +
1900                "calendar_location, " +     // rename from location
1901                "calendar_timezone, " +     // rename from timezone
1902                "ownerAccount, " +
1903                "canOrganizerRespond, " +
1904                "canModifyTimeZone, " +
1905                "maxReminders, " +
1906                "allowedReminders," +
1907                "deleted, " +
1908                "sync1, " +
1909                "sync2, " +
1910                "sync3, " +
1911                "sync4," +
1912                "sync5," +
1913                "sync6) " +
1914
1915                "SELECT " +
1916                "_id, " +
1917                "_sync_account, " +
1918                "_sync_account_type, " +
1919                "_sync_id, " +
1920                "_sync_version, " +
1921                "_sync_time, " +
1922                "_sync_dirty, " +
1923                "name, " +
1924                "displayName, " +
1925                "color, " +
1926                "access_level, " +
1927                "visible, " +
1928                "sync_events, " +
1929                "location, " +
1930                "timezone, " +
1931                "ownerAccount, " +
1932                "canOrganizerRespond, " +
1933                "canModifyTimeZone, " +
1934                "maxReminders, " +
1935                "'0,1,2,3'," +
1936                "deleted, " +
1937                "sync1, " +
1938                "sync2, " +
1939                "sync3, " +
1940                "sync4, " +
1941                "sync5, " +
1942                "sync6 " +
1943                "FROM Calendars_Backup;"
1944        );
1945
1946        /* expand the set of allowed reminders for Google calendars to include email */
1947        db.execSQL("UPDATE Calendars SET allowedReminders = '0,1,2' " +
1948                "WHERE account_type = 'com.google'");
1949
1950        // drop the old table
1951        db.execSQL("DROP TABLE Calendars_Backup;");
1952
1953        db.execSQL("ALTER TABLE Events RENAME TO Events_Backup;");
1954        db.execSQL("DROP TRIGGER IF EXISTS events_insert");
1955        db.execSQL("DROP TRIGGER IF EXISTS events_cleanup_delete");
1956        db.execSQL("DROP INDEX IF EXISTS eventSyncAccountAndIdIndex");
1957        db.execSQL("DROP INDEX IF EXISTS eventsCalendarIdIndex");
1958        createEventsTable300(db);
1959
1960        // copy fields from old to new
1961        db.execSQL("INSERT INTO Events (" +
1962                "_id, " +
1963                "_sync_id, " +
1964                "_sync_version, " +
1965                "_sync_time, " +
1966                "_sync_local_id, " +
1967                "dirty, " +                 // renamed from _sync_dirty
1968                "_sync_mark, " +
1969                "calendar_id, " +
1970                "htmlUri, " +
1971                "title, " +
1972                "eventLocation, " +
1973                "description, " +
1974                "eventStatus, " +
1975                "selfAttendeeStatus, " +
1976                "commentsUri, " +
1977                "dtstart, " +
1978                "dtend, " +
1979                "eventTimezone, " +
1980                "eventEndTimezone, " +      // renamed from eventTimezone2
1981                "duration, " +
1982                "allDay, " +
1983                "accessLevel, " +           // renamed from visibility
1984                "availability, " +          // renamed from transparency
1985                "hasAlarm, " +
1986                "hasExtendedProperties, " +
1987                "rrule, " +
1988                "rdate, " +
1989                "exrule, " +
1990                "exdate, " +
1991                "original_sync_id, " +      // renamed from originalEvent
1992                "originalInstanceTime, " +
1993                "originalAllDay, " +
1994                "lastDate, " +
1995                "hasAttendeeData, " +
1996                "guestsCanModify, " +
1997                "guestsCanInviteOthers, " +
1998                "guestsCanSeeGuests, " +
1999                "organizer, " +
2000                "deleted, " +
2001                "sync_data1) " +             // renamed from syncAdapterData
2002
2003                "SELECT " +
2004                "_id, " +
2005                "_sync_id, " +
2006                "_sync_version, " +
2007                "_sync_time, " +
2008                "_sync_local_id, " +
2009                "_sync_dirty, " +
2010                "_sync_mark, " +
2011                "calendar_id, " +
2012                "htmlUri, " +
2013                "title, " +
2014                "eventLocation, " +
2015                "description, " +
2016                "eventStatus, " +
2017                "selfAttendeeStatus, " +
2018                "commentsUri, " +
2019                "dtstart, " +
2020                "dtend, " +
2021                "eventTimezone, " +
2022                "eventTimezone2, " +
2023                "duration, " +
2024                "allDay, " +
2025                "visibility, " +
2026                "transparency, " +
2027                "hasAlarm, " +
2028                "hasExtendedProperties, " +
2029                "rrule, " +
2030                "rdate, " +
2031                "exrule, " +
2032                "exdate, " +
2033                "originalEvent, " +
2034                "originalInstanceTime, " +
2035                "originalAllDay, " +
2036                "lastDate, " +
2037                "hasAttendeeData, " +
2038                "guestsCanModify, " +
2039                "guestsCanInviteOthers, " +
2040                "guestsCanSeeGuests, " +
2041                "organizer, " +
2042                "deleted, " +
2043                "syncAdapterData " +
2044
2045                "FROM Events_Backup;"
2046        );
2047
2048        db.execSQL("DROP TABLE Events_Backup;");
2049
2050        // Trigger to remove data tied to an event when we delete that event.
2051        db.execSQL("CREATE TRIGGER events_cleanup_delete DELETE ON " + Tables.EVENTS + " " +
2052                "BEGIN " +
2053                EVENTS_CLEANUP_TRIGGER_SQL +
2054                "END");
2055
2056    }
2057
2058    @VisibleForTesting
2059    void upgradeToVersion205(SQLiteDatabase db) {
2060        /*
2061         * Changes from version 204 to 205:
2062         * - rename+reorder "_sync_mark" to "sync6" (and change type from INTEGER to TEXT)
2063         * - rename "selected" to "visible"
2064         * - rename "organizerCanRespond" to "canOrganizerRespond"
2065         * - add "canModifyTimeZone"
2066         * - add "maxReminders"
2067         * - remove "_sync_local_id" (a/k/a _SYNC_DATA)
2068         */
2069
2070        // rename old table, create new table with updated layout
2071        db.execSQL("ALTER TABLE Calendars RENAME TO Calendars_Backup;");
2072        db.execSQL("DROP TRIGGER IF EXISTS calendar_cleanup");
2073        createCalendarsTable205(db);
2074
2075        // copy fields from old to new
2076        db.execSQL("INSERT INTO Calendars (" +
2077                "_id, " +
2078                "_sync_account, " +
2079                "_sync_account_type, " +
2080                "_sync_id, " +
2081                "_sync_version, " +
2082                "_sync_time, " +
2083                "_sync_dirty, " +
2084                "name, " +
2085                "displayName, " +
2086                "color, " +
2087                "access_level, " +
2088                "visible, " +                   // rename from "selected"
2089                "sync_events, " +
2090                "location, " +
2091                "timezone, " +
2092                "ownerAccount, " +
2093                "canOrganizerRespond, " +       // rename from "organizerCanRespond"
2094                "canModifyTimeZone, " +
2095                "maxReminders, " +
2096                "deleted, " +
2097                "sync1, " +
2098                "sync2, " +
2099                "sync3, " +
2100                "sync4," +
2101                "sync5," +
2102                "sync6) " +                     // rename/reorder from _sync_mark
2103                "SELECT " +
2104                "_id, " +
2105                "_sync_account, " +
2106                "_sync_account_type, " +
2107                "_sync_id, " +
2108                "_sync_version, " +
2109                "_sync_time, " +
2110                "_sync_dirty, " +
2111                "name, " +
2112                "displayName, " +
2113                "color, " +
2114                "access_level, " +
2115                "selected, " +
2116                "sync_events, " +
2117                "location, " +
2118                "timezone, " +
2119                "ownerAccount, " +
2120                "organizerCanRespond, " +
2121                "1, " +
2122                "5, " +
2123                "deleted, " +
2124                "sync1, " +
2125                "sync2, " +
2126                "sync3, " +
2127                "sync4, " +
2128                "sync5, " +
2129                "_sync_mark " +
2130                "FROM Calendars_Backup;"
2131        );
2132
2133        // set these fields appropriately for Exchange events
2134        db.execSQL("UPDATE Calendars SET canModifyTimeZone=0, maxReminders=1 " +
2135                "WHERE _sync_account_type='com.android.exchange'");
2136
2137        // drop the old table
2138        db.execSQL("DROP TABLE Calendars_Backup;");
2139    }
2140
2141    @VisibleForTesting
2142    void upgradeToVersion203(SQLiteDatabase db) {
2143        // Same as Gingerbread version 100
2144        Cursor cursor = db.rawQuery("SELECT value FROM CalendarCache WHERE key=?",
2145                new String[] {"timezoneDatabaseVersion"});
2146
2147        String oldTimezoneDbVersion = null;
2148        if (cursor != null && cursor.moveToNext()) {
2149            try {
2150                oldTimezoneDbVersion = cursor.getString(0);
2151            } finally {
2152                cursor.close();
2153            }
2154            // Also clean the CalendarCache table
2155            db.execSQL("DELETE FROM CalendarCache;");
2156        }
2157        initCalendarCacheTable203(db, oldTimezoneDbVersion);
2158
2159        // Same as Gingerbread version 101
2160        updateCalendarCacheTableTo203(db);
2161    }
2162
2163    @VisibleForTesting
2164    void upgradeToVersion202(SQLiteDatabase db) {
2165        // We will drop the "hidden" column from the calendar schema and add the "sync5" column
2166        db.execSQL("ALTER TABLE Calendars RENAME TO Calendars_Backup;");
2167
2168        db.execSQL("DROP TRIGGER IF EXISTS calendar_cleanup");
2169        createCalendarsTable202(db);
2170
2171        // Populate the new Calendars table and put into the "sync5" column the value of the
2172        // old "hidden" column
2173        db.execSQL("INSERT INTO Calendars (" +
2174                "_id, " +
2175                "_sync_account, " +
2176                "_sync_account_type, " +
2177                "_sync_id, " +
2178                "_sync_version, " +
2179                "_sync_time, " +
2180                "_sync_local_id, " +
2181                "_sync_dirty, " +
2182                "_sync_mark, " +
2183                "name, " +
2184                "displayName, " +
2185                "color, " +
2186                "access_level, " +
2187                "selected, " +
2188                "sync_events, " +
2189                "location, " +
2190                "timezone, " +
2191                "ownerAccount, " +
2192                "organizerCanRespond, " +
2193                "deleted, " +
2194                "sync1, " +
2195                "sync2, " +
2196                "sync3, " +
2197                "sync4," +
2198                "sync5) " +
2199                "SELECT " +
2200                "_id, " +
2201                "_sync_account, " +
2202                "_sync_account_type, " +
2203                "_sync_id, " +
2204                "_sync_version, " +
2205                "_sync_time, " +
2206                "_sync_local_id, " +
2207                "_sync_dirty, " +
2208                "_sync_mark, " +
2209                "name, " +
2210                "displayName, " +
2211                "color, " +
2212                "access_level, " +
2213                "selected, " +
2214                "sync_events, " +
2215                "location, " +
2216                "timezone, " +
2217                "ownerAccount, " +
2218                "organizerCanRespond, " +
2219                "deleted, " +
2220                "sync1, " +
2221                "sync2, " +
2222                "sync3, " +
2223                "sync4, " +
2224                "hidden " +
2225                "FROM Calendars_Backup;"
2226        );
2227
2228        // Drop the backup table
2229        db.execSQL("DROP TABLE Calendars_Backup;");
2230    }
2231
2232    @VisibleForTesting
2233    void upgradeToVersion201(SQLiteDatabase db) {
2234        db.execSQL("ALTER TABLE Calendars ADD COLUMN sync4 TEXT;");
2235    }
2236
2237    @VisibleForTesting
2238    void upgradeToVersion200(SQLiteDatabase db) {
2239        // we cannot use here a Calendar.Calendars,URL constant for "url" as we are trying to make
2240        // it disappear so we are keeping the hardcoded name "url" in all the SQLs
2241        db.execSQL("ALTER TABLE Calendars RENAME TO Calendars_Backup;");
2242
2243        db.execSQL("DROP TRIGGER IF EXISTS calendar_cleanup");
2244        createCalendarsTable200(db);
2245
2246        // Populate the new Calendars table except the SYNC2 / SYNC3 columns
2247        db.execSQL("INSERT INTO Calendars (" +
2248                "_id, " +
2249                "_sync_account, " +
2250                "_sync_account_type, " +
2251                "_sync_id, " +
2252                "_sync_version, " +
2253                "_sync_time, " +
2254                "_sync_local_id, " +
2255                "_sync_dirty, " +
2256                "_sync_mark, " +
2257                "name, " +
2258                "displayName, " +
2259                "color, " +
2260                "access_level, " +
2261                "selected, " +
2262                "sync_events, " +
2263                "location, " +
2264                "timezone, " +
2265                "ownerAccount, " +
2266                "organizerCanRespond, " +
2267                "deleted, " +
2268                "sync1) " +
2269                "SELECT " +
2270                "_id, " +
2271                "_sync_account, " +
2272                "_sync_account_type, " +
2273                "_sync_id, " +
2274                "_sync_version, " +
2275                "_sync_time, " +
2276                "_sync_local_id, " +
2277                "_sync_dirty, " +
2278                "_sync_mark, " +
2279                "name, " +
2280                "displayName, " +
2281                "color, " +
2282                "access_level, " +
2283                "selected, " +
2284                "sync_events, " +
2285                "location, " +
2286                "timezone, " +
2287                "ownerAccount, " +
2288                "organizerCanRespond, " +
2289                "0, " +
2290                "url " +
2291                "FROM Calendars_Backup;"
2292        );
2293
2294        // Populate SYNC2 and SYNC3 columns - SYNC1 represent the old "url" column
2295        // We will need to iterate over all the "com.google" type of calendars
2296        String selectSql = "SELECT _id, url" +
2297                " FROM Calendars_Backup" +
2298                " WHERE _sync_account_type='com.google'" +
2299                " AND url IS NOT NULL;";
2300
2301        String updateSql = "UPDATE Calendars SET " +
2302                "sync2=?, " + // edit Url
2303                "sync3=? " + // self Url
2304                "WHERE _id=?;";
2305
2306        Cursor cursor = db.rawQuery(selectSql, null /* selection args */);
2307        if (cursor != null && cursor.getCount() > 0) {
2308            try {
2309                Object[] bindArgs = new Object[3];
2310
2311                while (cursor.moveToNext()) {
2312                    Long id = cursor.getLong(0);
2313                    String url = cursor.getString(1);
2314                    String selfUrl = getSelfUrlFromEventsUrl(url);
2315                    String editUrl = getEditUrlFromEventsUrl(url);
2316
2317                    bindArgs[0] = editUrl;
2318                    bindArgs[1] = selfUrl;
2319                    bindArgs[2] = id;
2320
2321                    db.execSQL(updateSql, bindArgs);
2322                }
2323            } finally {
2324                cursor.close();
2325            }
2326        }
2327
2328        // Drop the backup table
2329        db.execSQL("DROP TABLE Calendars_Backup;");
2330    }
2331
2332    @VisibleForTesting
2333    static void upgradeToVersion69(SQLiteDatabase db) {
2334        // Clean up allDay events which could be in an invalid state from an earlier version
2335        // Some allDay events had hour, min, sec not set to zero, which throws elsewhere. This
2336        // will go through the allDay events and make sure they have proper values and are in the
2337        // correct timezone. Verifies that dtstart and dtend are in UTC and at midnight, that
2338        // eventTimezone is set to UTC, tries to make sure duration is in days, and that dtstart2
2339        // and dtend2 are at midnight in their timezone.
2340        final String sql = "SELECT _id, " +
2341                "dtstart, " +
2342                "dtend, " +
2343                "duration, " +
2344                "dtstart2, " +
2345                "dtend2, " +
2346                "eventTimezone, " +
2347                "eventTimezone2, " +
2348                "rrule " +
2349                "FROM Events " +
2350                "WHERE allDay=?";
2351        Cursor cursor = db.rawQuery(sql, new String[] {"1"});
2352        if (cursor != null) {
2353            try {
2354                String timezone;
2355                String timezone2;
2356                String duration;
2357                Long dtstart;
2358                Long dtstart2;
2359                Long dtend;
2360                Long dtend2;
2361                Time time = new Time();
2362                Long id;
2363                // some things need to be in utc so we call this frequently, cache to make faster
2364                final String utc = Time.TIMEZONE_UTC;
2365                while (cursor.moveToNext()) {
2366                    String rrule = cursor.getString(8);
2367                    id = cursor.getLong(0);
2368                    dtstart = cursor.getLong(1);
2369                    dtstart2 = null;
2370                    timezone = cursor.getString(6);
2371                    timezone2 = cursor.getString(7);
2372                    duration = cursor.getString(3);
2373
2374                    if (TextUtils.isEmpty(rrule)) {
2375                        // For non-recurring events dtstart and dtend should both have values
2376                        // and duration should be null.
2377                        dtend = cursor.getLong(2);
2378                        dtend2 = null;
2379                        // Since we made all three of these at the same time if timezone2 exists
2380                        // so should dtstart2 and dtend2.
2381                        if(!TextUtils.isEmpty(timezone2)) {
2382                            dtstart2 = cursor.getLong(4);
2383                            dtend2 = cursor.getLong(5);
2384                        }
2385
2386                        boolean update = false;
2387                        if (!TextUtils.equals(timezone, utc)) {
2388                            update = true;
2389                            timezone = utc;
2390                        }
2391
2392                        time.clear(timezone);
2393                        update |= fixAllDayTime(time, timezone, dtstart);
2394                        dtstart = time.normalize(false);
2395
2396                        time.clear(timezone);
2397                        update |= fixAllDayTime(time, timezone, dtend);
2398                        dtend = time.normalize(false);
2399
2400                        if (dtstart2 != null) {
2401                            time.clear(timezone2);
2402                            update |= fixAllDayTime(time, timezone2, dtstart2);
2403                            dtstart2 = time.normalize(false);
2404                        }
2405
2406                        if (dtend2 != null) {
2407                            time.clear(timezone2);
2408                            update |= fixAllDayTime(time, timezone2, dtend2);
2409                            dtend2 = time.normalize(false);
2410                        }
2411
2412                        if (!TextUtils.isEmpty(duration)) {
2413                            update = true;
2414                        }
2415
2416                        if (update) {
2417                            // enforce duration being null
2418                            db.execSQL("UPDATE Events SET " +
2419                                    "dtstart=?, " +
2420                                    "dtend=?, " +
2421                                    "dtstart2=?, " +
2422                                    "dtend2=?, " +
2423                                    "duration=?, " +
2424                                    "eventTimezone=?, " +
2425                                    "eventTimezone2=? " +
2426                                    "WHERE _id=?",
2427                                    new Object[] {
2428                                            dtstart,
2429                                            dtend,
2430                                            dtstart2,
2431                                            dtend2,
2432                                            null,
2433                                            timezone,
2434                                            timezone2,
2435                                            id}
2436                            );
2437                        }
2438
2439                    } else {
2440                        // For recurring events only dtstart and duration should be used.
2441                        // We ignore dtend since it will be overwritten if the event changes to a
2442                        // non-recurring event and won't be used otherwise.
2443                        if(!TextUtils.isEmpty(timezone2)) {
2444                            dtstart2 = cursor.getLong(4);
2445                        }
2446
2447                        boolean update = false;
2448                        if (!TextUtils.equals(timezone, utc)) {
2449                            update = true;
2450                            timezone = utc;
2451                        }
2452
2453                        time.clear(timezone);
2454                        update |= fixAllDayTime(time, timezone, dtstart);
2455                        dtstart = time.normalize(false);
2456
2457                        if (dtstart2 != null) {
2458                            time.clear(timezone2);
2459                            update |= fixAllDayTime(time, timezone2, dtstart2);
2460                            dtstart2 = time.normalize(false);
2461                        }
2462
2463                        if (TextUtils.isEmpty(duration)) {
2464                            // If duration was missing assume a 1 day duration
2465                            duration = "P1D";
2466                            update = true;
2467                        } else {
2468                            int len = duration.length();
2469                            // TODO fix durations in other formats as well
2470                            if (duration.charAt(0) == 'P' &&
2471                                    duration.charAt(len - 1) == 'S') {
2472                                int seconds = Integer.parseInt(duration.substring(1, len - 1));
2473                                int days = (seconds + DAY_IN_SECONDS - 1) / DAY_IN_SECONDS;
2474                                duration = "P" + days + "D";
2475                                update = true;
2476                            }
2477                        }
2478
2479                        if (update) {
2480                            // If there were other problems also enforce dtend being null
2481                            db.execSQL("UPDATE Events SET " +
2482                                    "dtstart=?, " +
2483                                    "dtend=?, " +
2484                                    "dtstart2=?, " +
2485                                    "dtend2=?, " +
2486                                    "duration=?," +
2487                                    "eventTimezone=?, " +
2488                                    "eventTimezone2=? " +
2489                                    "WHERE _id=?",
2490                                    new Object[] {
2491                                            dtstart,
2492                                            null,
2493                                            dtstart2,
2494                                            null,
2495                                            duration,
2496                                            timezone,
2497                                            timezone2,
2498                                            id}
2499                            );
2500                        }
2501                    }
2502                }
2503            } finally {
2504                cursor.close();
2505            }
2506        }
2507    }
2508
2509    private void upgradeToVersion66(SQLiteDatabase db) {
2510        // Add a column to indicate whether the event organizer can respond to his own events
2511        // The UI should not show attendee status for events in calendars with this column = 0
2512        db.execSQL("ALTER TABLE Calendars" +
2513                " ADD COLUMN organizerCanRespond INTEGER NOT NULL DEFAULT 1;");
2514    }
2515
2516    private void upgradeToVersion64(SQLiteDatabase db) {
2517        // Add a column that may be used by sync adapters
2518        db.execSQL("ALTER TABLE Events" +
2519                " ADD COLUMN syncAdapterData TEXT;");
2520    }
2521
2522    private void upgradeToVersion62(SQLiteDatabase db) {
2523        // New columns are to transition to having allDay events in the local timezone
2524        db.execSQL("ALTER TABLE Events" +
2525                " ADD COLUMN dtstart2 INTEGER;");
2526        db.execSQL("ALTER TABLE Events" +
2527                " ADD COLUMN dtend2 INTEGER;");
2528        db.execSQL("ALTER TABLE Events" +
2529                " ADD COLUMN eventTimezone2 TEXT;");
2530
2531        String[] allDayBit = new String[] {"0"};
2532        // Copy over all the data that isn't an all day event.
2533        db.execSQL("UPDATE Events SET " +
2534                "dtstart2=dtstart," +
2535                "dtend2=dtend," +
2536                "eventTimezone2=eventTimezone " +
2537                "WHERE allDay=?;",
2538                allDayBit /* selection args */);
2539
2540        // "cursor" iterates over all the calendars
2541        allDayBit[0] = "1";
2542        Cursor cursor = db.rawQuery("SELECT Events._id," +
2543                "dtstart," +
2544                "dtend," +
2545                "eventTimezone," +
2546                "timezone " +
2547                "FROM Events INNER JOIN Calendars " +
2548                "WHERE Events.calendar_id=Calendars._id" +
2549                " AND allDay=?",
2550                allDayBit /* selection args */);
2551
2552        Time oldTime = new Time();
2553        Time newTime = new Time();
2554        // Update the allday events in the new columns
2555        if (cursor != null) {
2556            try {
2557                String[] newData = new String[4];
2558                cursor.moveToPosition(-1);
2559                while (cursor.moveToNext()) {
2560                    long id = cursor.getLong(0); // Order from query above
2561                    long dtstart = cursor.getLong(1);
2562                    long dtend = cursor.getLong(2);
2563                    String eTz = cursor.getString(3); // current event timezone
2564                    String tz = cursor.getString(4); // Calendar timezone
2565                    //If there's no timezone for some reason use UTC by default.
2566                    if(eTz == null) {
2567                        eTz = Time.TIMEZONE_UTC;
2568                    }
2569
2570                    // Convert start time for all day events into the timezone of their calendar
2571                    oldTime.clear(eTz);
2572                    oldTime.set(dtstart);
2573                    newTime.clear(tz);
2574                    newTime.set(oldTime.monthDay, oldTime.month, oldTime.year);
2575                    newTime.normalize(false);
2576                    dtstart = newTime.toMillis(false /*ignoreDst*/);
2577
2578                    // Convert end time for all day events into the timezone of their calendar
2579                    oldTime.clear(eTz);
2580                    oldTime.set(dtend);
2581                    newTime.clear(tz);
2582                    newTime.set(oldTime.monthDay, oldTime.month, oldTime.year);
2583                    newTime.normalize(false);
2584                    dtend = newTime.toMillis(false /*ignoreDst*/);
2585
2586                    newData[0] = String.valueOf(dtstart);
2587                    newData[1] = String.valueOf(dtend);
2588                    newData[2] = tz;
2589                    newData[3] = String.valueOf(id);
2590                    db.execSQL("UPDATE Events SET " +
2591                            "dtstart2=?, " +
2592                            "dtend2=?, " +
2593                            "eventTimezone2=? " +
2594                            "WHERE _id=?",
2595                            newData);
2596                }
2597            } finally {
2598                cursor.close();
2599            }
2600        }
2601    }
2602
2603    private void upgradeToVersion61(SQLiteDatabase db) {
2604        db.execSQL("DROP TABLE IF EXISTS CalendarCache;");
2605
2606        // IF NOT EXISTS should be normal pattern for table creation
2607        db.execSQL("CREATE TABLE IF NOT EXISTS CalendarCache (" +
2608                "_id INTEGER PRIMARY KEY," +
2609                "key TEXT NOT NULL," +
2610                "value TEXT" +
2611                ");");
2612
2613        db.execSQL("INSERT INTO CalendarCache (" +
2614                "key, " +
2615                "value) VALUES (" +
2616                "'timezoneDatabaseVersion',"  +
2617                "'2009s'" +
2618                ");");
2619    }
2620
2621    private void upgradeToVersion60(SQLiteDatabase db) {
2622        // Switch to CalendarProvider2
2623        upgradeSyncState(db);
2624        db.execSQL("DROP TRIGGER IF EXISTS calendar_cleanup");
2625        db.execSQL("CREATE TRIGGER calendar_cleanup DELETE ON Calendars " +
2626                "BEGIN " +
2627                ("DELETE FROM Events" +
2628                        " WHERE calendar_id=old._id;") +
2629                "END");
2630        db.execSQL("ALTER TABLE Events" +
2631                " ADD COLUMN deleted INTEGER NOT NULL DEFAULT 0;");
2632        db.execSQL("DROP TRIGGER IF EXISTS events_insert");
2633        // Trigger to set event's sync_account
2634        db.execSQL("CREATE TRIGGER events_insert AFTER INSERT ON Events " +
2635                "BEGIN " +
2636                "UPDATE Events" +
2637                " SET _sync_account=" +
2638                " (SELECT _sync_account FROM Calendars" +
2639                " WHERE Calendars._id=new.calendar_id)," +
2640                "_sync_account_type=" +
2641                " (SELECT _sync_account_type FROM Calendars" +
2642                " WHERE Calendars._id=new.calendar_id) " +
2643                "WHERE Events._id=new._id;" +
2644                "END");
2645        db.execSQL("DROP TABLE IF EXISTS DeletedEvents;");
2646        db.execSQL("DROP TRIGGER IF EXISTS events_cleanup_delete");
2647        // Trigger to remove data tied to an event when we delete that event.
2648        db.execSQL("CREATE TRIGGER events_cleanup_delete DELETE ON Events " +
2649                "BEGIN " +
2650                ("DELETE FROM Instances" +
2651                    " WHERE event_id=old._id;" +
2652                "DELETE FROM EventsRawTimes" +
2653                    " WHERE event_id=old._id;" +
2654                "DELETE FROM Attendees" +
2655                    " WHERE event_id=old._id;" +
2656                "DELETE FROM Reminders" +
2657                    " WHERE event_id=old._id;" +
2658                "DELETE FROM CalendarAlerts" +
2659                    " WHERE event_id=old._id;" +
2660                "DELETE FROM ExtendedProperties" +
2661                    " WHERE event_id=old._id;") +
2662                "END");
2663        db.execSQL("DROP TRIGGER IF EXISTS attendees_update");
2664        db.execSQL("DROP TRIGGER IF EXISTS attendees_insert");
2665        db.execSQL("DROP TRIGGER IF EXISTS attendees_delete");
2666        db.execSQL("DROP TRIGGER IF EXISTS reminders_update");
2667        db.execSQL("DROP TRIGGER IF EXISTS reminders_insert");
2668        db.execSQL("DROP TRIGGER IF EXISTS reminders_delete");
2669        db.execSQL("DROP TRIGGER IF EXISTS extended_properties_update");
2670        db.execSQL("DROP TRIGGER IF EXISTS extended_properties_insert");
2671        db.execSQL("DROP TRIGGER IF EXISTS extended_properties_delete");
2672    }
2673
2674    private void upgradeToVersion59(SQLiteDatabase db) {
2675        db.execSQL("DROP TABLE IF EXISTS BusyBits;");
2676        db.execSQL("CREATE TEMPORARY TABLE CalendarMetaData_Backup(" +
2677                "_id," +
2678                "localTimezone," +
2679                "minInstance," +
2680                "maxInstance" +
2681                ");");
2682        db.execSQL("INSERT INTO CalendarMetaData_Backup " +
2683                "SELECT " +
2684                "_id," +
2685                "localTimezone," +
2686                "minInstance," +
2687                "maxInstance" +
2688                " FROM CalendarMetaData;");
2689        db.execSQL("DROP TABLE CalendarMetaData;");
2690        createCalendarMetaDataTable59(db);
2691        db.execSQL("INSERT INTO CalendarMetaData " +
2692                "SELECT " +
2693                "_id," +
2694                "localTimezone," +
2695                "minInstance," +
2696                "maxInstance" +
2697                " FROM CalendarMetaData_Backup;");
2698        db.execSQL("DROP TABLE CalendarMetaData_Backup;");
2699    }
2700
2701    private void upgradeToVersion57(SQLiteDatabase db) {
2702        db.execSQL("ALTER TABLE Events" +
2703                " ADD COLUMN guestsCanModify" +
2704                " INTEGER NOT NULL DEFAULT 0;");
2705        db.execSQL("ALTER TABLE Events" +
2706                " ADD COLUMN guestsCanInviteOthers" +
2707                " INTEGER NOT NULL DEFAULT 1;");
2708        db.execSQL("ALTER TABLE Events" +
2709                " ADD COLUMN guestsCanSeeGuests" +
2710                " INTEGER NOT NULL DEFAULT 1;");
2711        db.execSQL("ALTER TABLE Events" +
2712                " ADD COLUMN organizer" +
2713                " STRING;");
2714        db.execSQL("UPDATE Events SET organizer=" +
2715                "(SELECT attendeeEmail" +
2716                " FROM Attendees"  +
2717                " WHERE " +
2718                "Attendees.event_id=" +
2719                "Events._id" +
2720                " AND " +
2721                "Attendees.attendeeRelationship=2);");
2722    }
2723
2724    private void upgradeToVersion56(SQLiteDatabase db) {
2725        db.execSQL("ALTER TABLE Calendars" +
2726                " ADD COLUMN ownerAccount TEXT;");
2727        db.execSQL("ALTER TABLE Events" +
2728                " ADD COLUMN hasAttendeeData INTEGER NOT NULL DEFAULT 0;");
2729
2730        // Clear _sync_dirty to avoid a client-to-server sync that could blow away
2731        // server attendees.
2732        // Clear _sync_version to pull down the server's event (with attendees)
2733        // Change the URLs from full-selfattendance to full
2734        db.execSQL("UPDATE Events"
2735                + " SET _sync_dirty=0, "
2736                + "_sync_version=NULL, "
2737                + "_sync_id="
2738                + "REPLACE(_sync_id, " +
2739                    "'/private/full-selfattendance', '/private/full'),"
2740                + "commentsUri="
2741                + "REPLACE(commentsUri, " +
2742                    "'/private/full-selfattendance', '/private/full');");
2743
2744        db.execSQL("UPDATE Calendars"
2745                + " SET url="
2746                + "REPLACE(url, '/private/full-selfattendance', '/private/full');");
2747
2748        // "cursor" iterates over all the calendars
2749        Cursor cursor = db.rawQuery("SELECT _id, " +
2750                "url FROM Calendars",
2751                null /* selection args */);
2752        // Add the owner column.
2753        if (cursor != null) {
2754            try {
2755                final String updateSql = "UPDATE Calendars" +
2756                        " SET ownerAccount=?" +
2757                        " WHERE _id=?";
2758                while (cursor.moveToNext()) {
2759                    Long id = cursor.getLong(0);
2760                    String url = cursor.getString(1);
2761                    String owner = calendarEmailAddressFromFeedUrl(url);
2762                    db.execSQL(updateSql, new Object[] {owner, id});
2763                }
2764            } finally {
2765                cursor.close();
2766            }
2767        }
2768    }
2769
2770    private void upgradeResync(SQLiteDatabase db) {
2771        // Delete sync state, so all records will be re-synced.
2772        db.execSQL("DELETE FROM _sync_state;");
2773
2774        // "cursor" iterates over all the calendars
2775        Cursor cursor = db.rawQuery("SELECT _sync_account," +
2776                "_sync_account_type,url FROM Calendars",
2777                null /* selection args */);
2778        if (cursor != null) {
2779            try {
2780                while (cursor.moveToNext()) {
2781                    String accountName = cursor.getString(0);
2782                    String accountType = cursor.getString(1);
2783                    final Account account = new Account(accountName, accountType);
2784                    String calendarUrl = cursor.getString(2);
2785                    scheduleSync(account, false /* two-way sync */, calendarUrl);
2786                }
2787            } finally {
2788                cursor.close();
2789            }
2790        }
2791    }
2792
2793    private void upgradeToVersion55(SQLiteDatabase db) {
2794        db.execSQL("ALTER TABLE Calendars ADD COLUMN " +
2795                "_sync_account_type TEXT;");
2796        db.execSQL("ALTER TABLE Events ADD COLUMN " +
2797                "_sync_account_type TEXT;");
2798        db.execSQL("ALTER TABLE DeletedEvents ADD COLUMN _sync_account_type TEXT;");
2799        db.execSQL("UPDATE Calendars"
2800                + " SET _sync_account_type='com.google'"
2801                + " WHERE _sync_account IS NOT NULL");
2802        db.execSQL("UPDATE Events"
2803                + " SET _sync_account_type='com.google'"
2804                + " WHERE _sync_account IS NOT NULL");
2805        db.execSQL("UPDATE DeletedEvents"
2806                + " SET _sync_account_type='com.google'"
2807                + " WHERE _sync_account IS NOT NULL");
2808        Log.w(TAG, "re-creating eventSyncAccountAndIdIndex");
2809        db.execSQL("DROP INDEX eventSyncAccountAndIdIndex");
2810        db.execSQL("CREATE INDEX eventSyncAccountAndIdIndex ON Events ("
2811                + "_sync_account_type, "
2812                + "_sync_account, "
2813                + "_sync_id);");
2814    }
2815
2816    private void upgradeToVersion54(SQLiteDatabase db) {
2817        Log.w(TAG, "adding eventSyncAccountAndIdIndex");
2818        db.execSQL("CREATE INDEX eventSyncAccountAndIdIndex ON Events ("
2819                + "_sync_account, _sync_id);");
2820    }
2821
2822    private void upgradeToVersion53(SQLiteDatabase db) {
2823        Log.w(TAG, "Upgrading CalendarAlerts table");
2824        db.execSQL("ALTER TABLE CalendarAlerts ADD COLUMN " +
2825                "creationTime INTEGER NOT NULL DEFAULT 0;");
2826        db.execSQL("ALTER TABLE CalendarAlerts ADD COLUMN " +
2827                "receivedTime INTEGER NOT NULL DEFAULT 0;");
2828        db.execSQL("ALTER TABLE CalendarAlerts ADD COLUMN " +
2829                "notifyTime INTEGER NOT NULL DEFAULT 0;");
2830    }
2831
2832    private void upgradeToVersion52(SQLiteDatabase db) {
2833        // We added "originalAllDay" to the Events table to keep track of
2834        // the allDay status of the original recurring event for entries
2835        // that are exceptions to that recurring event.  We need this so
2836        // that we can format the date correctly for the "originalInstanceTime"
2837        // column when we make a change to the recurrence exception and
2838        // send it to the server.
2839        db.execSQL("ALTER TABLE Events ADD COLUMN " +
2840                "originalAllDay INTEGER;");
2841
2842        // Iterate through the Events table and for each recurrence
2843        // exception, fill in the correct value for "originalAllDay",
2844        // if possible.  The only times where this might not be possible
2845        // are (1) the original recurring event no longer exists, or
2846        // (2) the original recurring event does not yet have a _sync_id
2847        // because it was created on the phone and hasn't been synced to the
2848        // server yet.  In both cases the originalAllDay field will be set
2849        // to null.  In the first case we don't care because the recurrence
2850        // exception will not be displayed and we won't be able to make
2851        // any changes to it (and even if we did, the server should ignore
2852        // them, right?).  In the second case, the calendar client already
2853        // disallows making changes to an instance of a recurring event
2854        // until the recurring event has been synced to the server so the
2855        // second case should never occur.
2856
2857        // "cursor" iterates over all the recurrences exceptions.
2858        Cursor cursor = db.rawQuery("SELECT _id," +
2859                "originalEvent" +
2860                " FROM Events" +
2861                " WHERE originalEvent IS NOT NULL",
2862                null /* selection args */);
2863        if (cursor != null) {
2864            try {
2865                while (cursor.moveToNext()) {
2866                    long id = cursor.getLong(0);
2867                    String originalEvent = cursor.getString(1);
2868
2869                    // Find the original recurring event (if it exists)
2870                    Cursor recur = db.rawQuery("SELECT allDay" +
2871                            " FROM Events" +
2872                            " WHERE _sync_id=?",
2873                            new String[] {originalEvent});
2874                    if (recur == null) {
2875                        continue;
2876                    }
2877
2878                    try {
2879                        // Fill in the "originalAllDay" field of the
2880                        // recurrence exception with the "allDay" value
2881                        // from the recurring event.
2882                        if (recur.moveToNext()) {
2883                            int allDay = recur.getInt(0);
2884                            db.execSQL("UPDATE Events" +
2885                                    " SET originalAllDay=" + allDay +
2886                                    " WHERE _id="+id);
2887                        }
2888                    } finally {
2889                        recur.close();
2890                    }
2891                }
2892            } finally {
2893                cursor.close();
2894            }
2895        }
2896    }
2897
2898    private void upgradeToVersion51(SQLiteDatabase db) {
2899        Log.w(TAG, "Upgrading DeletedEvents table");
2900
2901        // We don't have enough information to fill in the correct
2902        // value of the calendar_id for old rows in the DeletedEvents
2903        // table, but rows in that table are transient so it is unlikely
2904        // that there are any rows.  Plus, the calendar_id is used only
2905        // when deleting a calendar, which is a rare event.  All new rows
2906        // will have the correct calendar_id.
2907        db.execSQL("ALTER TABLE DeletedEvents ADD COLUMN calendar_id INTEGER;");
2908
2909        // Trigger to remove a calendar's events when we delete the calendar
2910        db.execSQL("DROP TRIGGER IF EXISTS calendar_cleanup");
2911        db.execSQL("CREATE TRIGGER calendar_cleanup DELETE ON Calendars " +
2912                "BEGIN " +
2913                "DELETE FROM Events WHERE calendar_id=" +
2914                    "old._id;" +
2915                "DELETE FROM DeletedEvents WHERE calendar_id = old._id;" +
2916                "END");
2917        db.execSQL("DROP TRIGGER IF EXISTS event_to_deleted");
2918    }
2919
2920    private void dropTables(SQLiteDatabase db) {
2921        db.execSQL("DROP TABLE IF EXISTS " + Tables.COLORS + ";");
2922        db.execSQL("DROP TABLE IF EXISTS " + Tables.CALENDARS + ";");
2923        db.execSQL("DROP TABLE IF EXISTS " + Tables.EVENTS + ";");
2924        db.execSQL("DROP TABLE IF EXISTS " + Tables.EVENTS_RAW_TIMES + ";");
2925        db.execSQL("DROP TABLE IF EXISTS " + Tables.INSTANCES + ";");
2926        db.execSQL("DROP TABLE IF EXISTS " + Tables.CALENDAR_META_DATA + ";");
2927        db.execSQL("DROP TABLE IF EXISTS " + Tables.CALENDAR_CACHE + ";");
2928        db.execSQL("DROP TABLE IF EXISTS " + Tables.ATTENDEES + ";");
2929        db.execSQL("DROP TABLE IF EXISTS " + Tables.REMINDERS + ";");
2930        db.execSQL("DROP TABLE IF EXISTS " + Tables.CALENDAR_ALERTS + ";");
2931        db.execSQL("DROP TABLE IF EXISTS " + Tables.EXTENDED_PROPERTIES + ";");
2932    }
2933
2934    @Override
2935    public synchronized SQLiteDatabase getWritableDatabase() {
2936        SQLiteDatabase db = super.getWritableDatabase();
2937        return db;
2938    }
2939
2940    public SyncStateContentProviderHelper getSyncState() {
2941        return mSyncState;
2942    }
2943
2944    /**
2945     * Schedule a calendar sync for the account.
2946     * @param account the account for which to schedule a sync
2947     * @param uploadChangesOnly if set, specify that the sync should only send
2948     *   up local changes.  This is typically used for a local sync, a user override of
2949     *   too many deletions, or a sync after a calendar is unselected.
2950     * @param url the url feed for the calendar to sync (may be null, in which case a poll of
2951     *   all feeds is done.)
2952     */
2953    void scheduleSync(Account account, boolean uploadChangesOnly, String url) {
2954        Bundle extras = new Bundle();
2955        if (uploadChangesOnly) {
2956            extras.putBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, uploadChangesOnly);
2957        }
2958        if (url != null) {
2959            extras.putString("feed", url);
2960            extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
2961        }
2962        ContentResolver.requestSync(account, CalendarContract.Calendars.CONTENT_URI.getAuthority(),
2963                extras);
2964    }
2965
2966    private static void createEventsView(SQLiteDatabase db) {
2967        db.execSQL("DROP VIEW IF EXISTS " + Views.EVENTS + ";");
2968        String eventsSelect = "SELECT "
2969                + Tables.EVENTS + "." + CalendarContract.Events._ID
2970                        + " AS " + CalendarContract.Events._ID + ","
2971                + CalendarContract.Events.TITLE + ","
2972                + CalendarContract.Events.DESCRIPTION + ","
2973                + CalendarContract.Events.EVENT_LOCATION + ","
2974                + CalendarContract.Events.EVENT_COLOR + ","
2975                + CalendarContract.Events.EVENT_COLOR_KEY + ","
2976                + CalendarContract.Events.STATUS + ","
2977                + CalendarContract.Events.SELF_ATTENDEE_STATUS + ","
2978                + CalendarContract.Events.DTSTART + ","
2979                + CalendarContract.Events.DTEND + ","
2980                + CalendarContract.Events.DURATION + ","
2981                + CalendarContract.Events.EVENT_TIMEZONE + ","
2982                + CalendarContract.Events.EVENT_END_TIMEZONE + ","
2983                + CalendarContract.Events.ALL_DAY + ","
2984                + CalendarContract.Events.ACCESS_LEVEL + ","
2985                + CalendarContract.Events.AVAILABILITY + ","
2986                + CalendarContract.Events.HAS_ALARM + ","
2987                + CalendarContract.Events.HAS_EXTENDED_PROPERTIES + ","
2988                + CalendarContract.Events.RRULE + ","
2989                + CalendarContract.Events.RDATE + ","
2990                + CalendarContract.Events.EXRULE + ","
2991                + CalendarContract.Events.EXDATE + ","
2992                + CalendarContract.Events.ORIGINAL_SYNC_ID + ","
2993                + CalendarContract.Events.ORIGINAL_ID + ","
2994                + CalendarContract.Events.ORIGINAL_INSTANCE_TIME + ","
2995                + CalendarContract.Events.ORIGINAL_ALL_DAY + ","
2996                + CalendarContract.Events.LAST_DATE + ","
2997                + CalendarContract.Events.HAS_ATTENDEE_DATA + ","
2998                + CalendarContract.Events.CALENDAR_ID + ","
2999                + CalendarContract.Events.GUESTS_CAN_INVITE_OTHERS + ","
3000                + CalendarContract.Events.GUESTS_CAN_MODIFY + ","
3001                + CalendarContract.Events.GUESTS_CAN_SEE_GUESTS + ","
3002                + CalendarContract.Events.ORGANIZER + ","
3003                + CalendarContract.Events.SYNC_DATA1 + ","
3004                + CalendarContract.Events.SYNC_DATA2 + ","
3005                + CalendarContract.Events.SYNC_DATA3 + ","
3006                + CalendarContract.Events.SYNC_DATA4 + ","
3007                + CalendarContract.Events.SYNC_DATA5 + ","
3008                + CalendarContract.Events.SYNC_DATA6 + ","
3009                + CalendarContract.Events.SYNC_DATA7 + ","
3010                + CalendarContract.Events.SYNC_DATA8 + ","
3011                + CalendarContract.Events.SYNC_DATA9 + ","
3012                + CalendarContract.Events.SYNC_DATA10 + ","
3013                + Tables.EVENTS + "." + CalendarContract.Events.DELETED
3014                + " AS " + CalendarContract.Events.DELETED + ","
3015                + Tables.EVENTS + "." + CalendarContract.Events._SYNC_ID
3016                + " AS " + CalendarContract.Events._SYNC_ID + ","
3017                + Tables.EVENTS + "." + CalendarContract.Events.DIRTY
3018                + " AS " + CalendarContract.Events.DIRTY + ","
3019                + CalendarContract.Events.LAST_SYNCED + ","
3020                + Tables.CALENDARS + "." + CalendarContract.Calendars.ACCOUNT_NAME
3021                + " AS " + CalendarContract.Events.ACCOUNT_NAME + ","
3022                + Tables.CALENDARS + "." + CalendarContract.Calendars.ACCOUNT_TYPE
3023                + " AS " + CalendarContract.Events.ACCOUNT_TYPE + ","
3024                + CalendarContract.Calendars.CALENDAR_TIME_ZONE + ","
3025                + CalendarContract.Calendars.CALENDAR_DISPLAY_NAME + ","
3026                + CalendarContract.Calendars.CALENDAR_LOCATION + ","
3027                + CalendarContract.Calendars.VISIBLE + ","
3028                + CalendarContract.Calendars.CALENDAR_COLOR + ","
3029                + CalendarContract.Calendars.CALENDAR_COLOR_KEY + ","
3030                + CalendarContract.Calendars.CALENDAR_ACCESS_LEVEL + ","
3031                + CalendarContract.Calendars.MAX_REMINDERS + ","
3032                + CalendarContract.Calendars.ALLOWED_REMINDERS + ","
3033                + CalendarContract.Calendars.ALLOWED_ATTENDEE_TYPES + ","
3034                + CalendarContract.Calendars.ALLOWED_AVAILABILITY + ","
3035                + CalendarContract.Calendars.CAN_ORGANIZER_RESPOND + ","
3036                + CalendarContract.Calendars.CAN_MODIFY_TIME_ZONE + ","
3037                + CalendarContract.Calendars.CAN_PARTIALLY_UPDATE + ","
3038                + CalendarContract.Calendars.CAL_SYNC1 + ","
3039                + CalendarContract.Calendars.CAL_SYNC2 + ","
3040                + CalendarContract.Calendars.CAL_SYNC3 + ","
3041                + CalendarContract.Calendars.CAL_SYNC4 + ","
3042                + CalendarContract.Calendars.CAL_SYNC5 + ","
3043                + CalendarContract.Calendars.CAL_SYNC6 + ","
3044                + CalendarContract.Calendars.CAL_SYNC7 + ","
3045                + CalendarContract.Calendars.CAL_SYNC8 + ","
3046                + CalendarContract.Calendars.CAL_SYNC9 + ","
3047                + CalendarContract.Calendars.CAL_SYNC10 + ","
3048                + CalendarContract.Calendars.OWNER_ACCOUNT + ","
3049                + CalendarContract.Calendars.SYNC_EVENTS
3050                + " FROM " + Tables.EVENTS + " JOIN " + Tables.CALENDARS
3051                + " ON (" + Tables.EVENTS + "." + CalendarContract.Events.CALENDAR_ID
3052                + "=" + Tables.CALENDARS + "." + CalendarContract.Calendars._ID
3053                + ")";
3054
3055        db.execSQL("CREATE VIEW " + Views.EVENTS + " AS " + eventsSelect);
3056    }
3057
3058    /**
3059     * Extracts the calendar email from a calendar feed url.
3060     * @param feed the calendar feed url
3061     * @return the calendar email that is in the feed url or null if it can't
3062     * find the email address.
3063     * TODO: this is duplicated in CalendarSyncAdapter; move to a library
3064     */
3065    public static String calendarEmailAddressFromFeedUrl(String feed) {
3066        // Example feed url:
3067        // https://www.google.com/calendar/feeds/foo%40gmail.com/private/full-noattendees
3068        String[] pathComponents = feed.split("/");
3069        if (pathComponents.length > 5 && "feeds".equals(pathComponents[4])) {
3070            try {
3071                return URLDecoder.decode(pathComponents[5], "UTF-8");
3072            } catch (UnsupportedEncodingException e) {
3073                Log.e(TAG, "unable to url decode the email address in calendar " + feed);
3074                return null;
3075            }
3076        }
3077
3078        Log.e(TAG, "unable to find the email address in calendar " + feed);
3079        return null;
3080    }
3081
3082    /**
3083     * Get a "allcalendars" url from a "private/full" or "private/free-busy" url
3084     * @param url
3085     * @return the rewritten Url
3086     *
3087     * For example:
3088     *
3089     *      http://www.google.com/calendar/feeds/joe%40joe.com/private/full
3090     *      http://www.google.com/calendar/feeds/joe%40joe.com/private/free-busy
3091     *
3092     * will be rewriten into:
3093     *
3094     *      http://www.google.com/calendar/feeds/default/allcalendars/full/joe%40joe.com
3095     *      http://www.google.com/calendar/feeds/default/allcalendars/full/joe%40joe.com
3096     */
3097    @VisibleForTesting
3098    private static String getAllCalendarsUrlFromEventsUrl(String url) {
3099        if (url == null) {
3100            if (Log.isLoggable(TAG, Log.DEBUG)) {
3101                Log.d(TAG, "Cannot get AllCalendars url from a NULL url");
3102            }
3103            return null;
3104        }
3105        if (url.contains("/private/full")) {
3106            return url.replace("/private/full", "").
3107                    replace("/calendar/feeds", "/calendar/feeds/default/allcalendars/full");
3108        }
3109        if (url.contains("/private/free-busy")) {
3110            return url.replace("/private/free-busy", "").
3111                    replace("/calendar/feeds", "/calendar/feeds/default/allcalendars/full");
3112        }
3113        // Just log as we dont recognize the provided Url
3114        if (Log.isLoggable(TAG, Log.DEBUG)) {
3115            Log.d(TAG, "Cannot get AllCalendars url from the following url: " + url);
3116        }
3117        return null;
3118    }
3119
3120    /**
3121     * Get "selfUrl" from "events url"
3122     * @param url the Events url (either "private/full" or "private/free-busy"
3123     * @return the corresponding allcalendar url
3124     */
3125    private static String getSelfUrlFromEventsUrl(String url) {
3126        return rewriteUrlFromHttpToHttps(getAllCalendarsUrlFromEventsUrl(url));
3127    }
3128
3129    /**
3130     * Get "editUrl" from "events url"
3131     * @param url the Events url (either "private/full" or "private/free-busy"
3132     * @return the corresponding allcalendar url
3133     */
3134    private static String getEditUrlFromEventsUrl(String url) {
3135        return rewriteUrlFromHttpToHttps(getAllCalendarsUrlFromEventsUrl(url));
3136    }
3137
3138    /**
3139     * Rewrite the url from "http" to "https" scheme
3140     * @param url the url to rewrite
3141     * @return the rewritten URL
3142     */
3143    private static String rewriteUrlFromHttpToHttps(String url) {
3144        if (url == null) {
3145            if (Log.isLoggable(TAG, Log.DEBUG)) {
3146                Log.d(TAG, "Cannot rewrite a NULL url");
3147            }
3148            return null;
3149        }
3150        if (url.startsWith(SCHEMA_HTTPS)) {
3151            return url;
3152        }
3153        if (!url.startsWith(SCHEMA_HTTP)) {
3154            throw new IllegalArgumentException("invalid url parameter, unknown scheme: " + url);
3155        }
3156        return SCHEMA_HTTPS + url.substring(SCHEMA_HTTP.length());
3157    }
3158
3159    /**
3160     * Duplicates an event and its associated tables (Attendees, Reminders, ExtendedProperties).
3161     * <p>
3162     * Does not create a duplicate if the Calendar's "canPartiallyUpdate" is 0 or the Event's
3163     * "dirty" is 1 (so we don't create more than one duplicate).
3164     *
3165     * @param id The _id of the event to duplicate.
3166     */
3167    protected void duplicateEvent(final long id) {
3168        final SQLiteDatabase db = getWritableDatabase();
3169        final long canPartiallyUpdate = DatabaseUtils.longForQuery(db, "SELECT "
3170                + CalendarContract.Calendars.CAN_PARTIALLY_UPDATE + " FROM " + Views.EVENTS
3171                + " WHERE " + Events._ID + " = ?", new String[] {
3172            String.valueOf(id)
3173        });
3174        if (canPartiallyUpdate == 0) {
3175            return;
3176        }
3177
3178        db.execSQL("INSERT INTO " + CalendarDatabaseHelper.Tables.EVENTS
3179                + "  (" + LAST_SYNCED_EVENT_COLUMNS + ","
3180                +         Events.DIRTY + "," + Events.LAST_SYNCED + ")"
3181                + " SELECT " + LAST_SYNCED_EVENT_COLUMNS + ", 0, 1"
3182                + " FROM " + Tables.EVENTS
3183                + " WHERE "  + Events._ID + " = ? AND " + Events.DIRTY + " = ?",
3184                new Object[]{
3185                        id,
3186                        0, // Events.DIRTY
3187                });
3188        final long newId = DatabaseUtils.longForQuery(
3189                db, "SELECT CASE changes() WHEN 0 THEN -1 ELSE last_insert_rowid() END", null);
3190        if (newId < 0) {
3191            return;
3192        }
3193
3194        if (Log.isLoggable(TAG, Log.VERBOSE)) {
3195            Log.v(TAG, "Duplicating event " + id + " into new event " + newId);
3196        }
3197
3198        copyEventRelatedTables(db, newId, id);
3199    }
3200
3201    /**
3202     * Makes a copy of the Attendees, Reminders, and ExtendedProperties rows associated with
3203     * a specific event.
3204     *
3205     * @param db The database.
3206     * @param newId The ID of the new event.
3207     * @param id The ID of the old event.
3208     */
3209    static void copyEventRelatedTables(SQLiteDatabase db, long newId, long id) {
3210        db.execSQL("INSERT INTO " + Tables.REMINDERS
3211                + " ( "  + CalendarContract.Reminders.EVENT_ID + ", "
3212                        + LAST_SYNCED_REMINDER_COLUMNS + ") "
3213                + "SELECT ?," + LAST_SYNCED_REMINDER_COLUMNS
3214                + " FROM " + Tables.REMINDERS
3215                + " WHERE " + CalendarContract.Reminders.EVENT_ID + " = ?",
3216                new Object[] {newId, id});
3217        db.execSQL("INSERT INTO "
3218                + Tables.ATTENDEES
3219                + " (" + CalendarContract.Attendees.EVENT_ID + ","
3220                        + LAST_SYNCED_ATTENDEE_COLUMNS + ") "
3221                + "SELECT ?," + LAST_SYNCED_ATTENDEE_COLUMNS + " FROM " + Tables.ATTENDEES
3222                + " WHERE " + CalendarContract.Attendees.EVENT_ID + " = ?",
3223                new Object[] {newId, id});
3224        db.execSQL("INSERT INTO " + Tables.EXTENDED_PROPERTIES
3225                + " (" + CalendarContract.ExtendedProperties.EVENT_ID + ","
3226                + LAST_SYNCED_EXTENDED_PROPERTY_COLUMNS + ") "
3227                + "SELECT ?, " + LAST_SYNCED_EXTENDED_PROPERTY_COLUMNS
3228                + " FROM " + Tables.EXTENDED_PROPERTIES
3229                + " WHERE " + CalendarContract.ExtendedProperties.EVENT_ID + " = ?",
3230                new Object[]{newId, id});
3231    }
3232
3233    protected void removeDuplicateEvent(final long id) {
3234        final SQLiteDatabase db = getWritableDatabase();
3235        final Cursor cursor = db.rawQuery("SELECT " + Events._ID + " FROM " + Tables.EVENTS
3236                + " WHERE " + Events._SYNC_ID
3237                + " = (SELECT " + Events._SYNC_ID
3238                + " FROM " + Tables.EVENTS
3239                + " WHERE " + Events._ID + " = ?) "
3240                + "AND " + Events.LAST_SYNCED + " = ?",
3241                new String[]{
3242                        String.valueOf(id),
3243                        "1", // Events.LAST_SYNCED
3244                });
3245        try {
3246            // there should only be at most one but this can't hurt
3247            if (cursor.moveToNext()) {
3248                final long dupId = cursor.getLong(0);
3249
3250                if (Log.isLoggable(TAG, Log.VERBOSE)) {
3251                    Log.v(TAG, "Removing duplicate event " + dupId + " of original event " + id);
3252                }
3253                // triggers will clean up related tables.
3254                db.execSQL("DELETE FROM Events WHERE " + Events._ID + " = ?", new Object[]{dupId});
3255            }
3256        } finally {
3257          cursor.close();
3258        }
3259    }
3260}
3261