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