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