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