1 /* 2 * Copyright (C) 2015 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 package com.android.providers.contacts; 17 18 import android.annotation.Nullable; 19 import android.content.ContentValues; 20 import android.content.Context; 21 import android.content.SharedPreferences; 22 import android.database.Cursor; 23 import android.database.DatabaseUtils; 24 import android.database.SQLException; 25 import android.database.sqlite.SQLiteCantOpenDatabaseException; 26 import android.database.sqlite.SQLiteDatabase; 27 import android.database.sqlite.SQLiteOpenHelper; 28 import android.preference.PreferenceManager; 29 import android.provider.CallLog.Calls; 30 import android.provider.VoicemailContract; 31 import android.provider.VoicemailContract.Status; 32 import android.provider.VoicemailContract.Voicemails; 33 import android.telephony.SubscriptionInfo; 34 import android.telephony.SubscriptionManager; 35 import android.text.TextUtils; 36 import android.util.ArraySet; 37 import android.util.Log; 38 39 import com.android.internal.annotations.VisibleForTesting; 40 import com.android.providers.contacts.util.PhoneAccountHandleMigrationUtils; 41 import com.android.providers.contacts.util.PropertyUtils; 42 43 import java.util.HashMap; 44 import java.util.List; 45 import java.util.Map; 46 47 /** 48 * SQLite database (helper) for {@link CallLogProvider} and {@link VoicemailContentProvider}. 49 */ 50 public class CallLogDatabaseHelper { 51 private static final String TAG = "CallLogDatabaseHelper"; 52 53 @VisibleForTesting 54 static final int DATABASE_VERSION = 12; 55 56 private static final boolean DEBUG = false; // DON'T SUBMIT WITH TRUE 57 58 private static final String DATABASE_NAME = "calllog.db"; 59 60 private static final String SHADOW_DATABASE_NAME = "calllog_shadow.db"; 61 62 private static final int IDLE_CONNECTION_TIMEOUT_MS = 30000; 63 64 private static CallLogDatabaseHelper sInstance; 65 66 /** Instance for the "shadow" provider. */ 67 private static CallLogDatabaseHelper sInstanceForShadow; 68 69 private final Context mContext; 70 71 private final OpenHelper mOpenHelper; 72 73 @VisibleForTesting 74 final PhoneAccountHandleMigrationUtils mPhoneAccountHandleMigrationUtils; 75 76 public interface Tables { 77 String CALLS = "calls"; 78 String VOICEMAIL_STATUS = "voicemail_status"; 79 } 80 81 public interface DbProperties { 82 String CALL_LOG_LAST_SYNCED = "call_log_last_synced"; 83 String CALL_LOG_LAST_SYNCED_FOR_SHADOW = "call_log_last_synced_for_shadow"; 84 String DATA_MIGRATED = "migrated"; 85 } 86 87 /** 88 * Constants used in the contacts DB helper, which are needed for migration. 89 * 90 * DO NOT CHANCE ANY OF THE CONSTANTS. 91 */ 92 public interface LegacyConstants { 93 /** Table name used in the contacts DB.*/ 94 String CALLS_LEGACY = "calls"; 95 96 /** Table name used in the contacts DB.*/ 97 String VOICEMAIL_STATUS_LEGACY = "voicemail_status"; 98 99 /** Prop name used in the contacts DB.*/ 100 String CALL_LOG_LAST_SYNCED_LEGACY = "call_log_last_synced"; 101 } 102 103 @VisibleForTesting 104 public class OpenHelper extends SQLiteOpenHelper { OpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version)105 public OpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, 106 int version) { 107 super(context, name, factory, version); 108 // Memory optimization - close idle connections after 30s of inactivity 109 setIdleConnectionTimeout(IDLE_CONNECTION_TIMEOUT_MS); 110 } 111 112 @Override onCreate(SQLiteDatabase db)113 public void onCreate(SQLiteDatabase db) { 114 if (DEBUG) { 115 Log.d(TAG, "onCreate"); 116 } 117 118 PropertyUtils.createPropertiesTable(db); 119 120 // *** NOTE ABOUT CHANGING THE DB SCHEMA *** 121 // 122 // The CALLS and VOICEMAIL_STATUS table used to be in the contacts2.db. So we need to 123 // migrate from these legacy tables, if exist, after creating the calllog DB, which is 124 // done in migrateFromLegacyTables(). 125 // 126 // This migration is slightly different from a regular upgrade step, because it's always 127 // performed from the legacy schema (of the latest version -- because the migration 128 // source is always the latest DB after all the upgrade steps) to the *latest* schema 129 // at once. 130 // 131 // This means certain kind of changes are not doable without changing the 132 // migration logic. For example, if you rename a column in the DB, the migration step 133 // will need to be updated to handle the column name change. 134 135 db.execSQL("CREATE TABLE " + Tables.CALLS + " (" + 136 Calls._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + 137 Calls.NUMBER + " TEXT," + 138 Calls.NUMBER_PRESENTATION + " INTEGER NOT NULL DEFAULT " + 139 Calls.PRESENTATION_ALLOWED + "," + 140 Calls.POST_DIAL_DIGITS + " TEXT NOT NULL DEFAULT ''," + 141 Calls.VIA_NUMBER + " TEXT NOT NULL DEFAULT ''," + 142 Calls.DATE + " INTEGER," + 143 Calls.DURATION + " INTEGER," + 144 Calls.DATA_USAGE + " INTEGER," + 145 Calls.TYPE + " INTEGER," + 146 Calls.FEATURES + " INTEGER NOT NULL DEFAULT 0," + 147 Calls.PHONE_ACCOUNT_COMPONENT_NAME + " TEXT," + 148 Calls.PHONE_ACCOUNT_ID + " TEXT," + 149 Calls.PHONE_ACCOUNT_ADDRESS + " TEXT," + 150 Calls.PHONE_ACCOUNT_HIDDEN + " INTEGER NOT NULL DEFAULT 0," + 151 Calls.SUB_ID + " INTEGER DEFAULT -1," + 152 Calls.NEW + " INTEGER," + 153 Calls.CACHED_NAME + " TEXT," + 154 Calls.CACHED_NUMBER_TYPE + " INTEGER," + 155 Calls.CACHED_NUMBER_LABEL + " TEXT," + 156 Calls.COUNTRY_ISO + " TEXT," + 157 Calls.VOICEMAIL_URI + " TEXT," + 158 Calls.IS_READ + " INTEGER," + 159 Calls.GEOCODED_LOCATION + " TEXT," + 160 Calls.CACHED_LOOKUP_URI + " TEXT," + 161 Calls.CACHED_MATCHED_NUMBER + " TEXT," + 162 Calls.CACHED_NORMALIZED_NUMBER + " TEXT," + 163 Calls.CACHED_PHOTO_ID + " INTEGER NOT NULL DEFAULT 0," + 164 Calls.CACHED_PHOTO_URI + " TEXT," + 165 Calls.CACHED_FORMATTED_NUMBER + " TEXT," + 166 Calls.ADD_FOR_ALL_USERS + " INTEGER NOT NULL DEFAULT 1," + 167 Calls.LAST_MODIFIED + " INTEGER DEFAULT 0," + 168 Calls.CALL_SCREENING_COMPONENT_NAME + " TEXT," + 169 Calls.CALL_SCREENING_APP_NAME + " TEXT," + 170 Calls.BLOCK_REASON + " INTEGER NOT NULL DEFAULT 0," + 171 Calls.MISSED_REASON + " INTEGER NOT NULL DEFAULT 0," + 172 Calls.PRIORITY + " INTEGER NOT NULL DEFAULT 0," + 173 Calls.SUBJECT + " TEXT," + 174 Calls.LOCATION + " TEXT," + 175 Calls.COMPOSER_PHOTO_URI + " TEXT," + 176 Calls.IS_PHONE_ACCOUNT_MIGRATION_PENDING + " INTEGER NOT NULL DEFAULT 0," + 177 Calls.IS_BUSINESS_CALL + " INTEGER NOT NULL DEFAULT 0," + 178 Calls.ASSERTED_DISPLAY_NAME + " TEXT," + 179 180 Voicemails._DATA + " TEXT," + 181 Voicemails.HAS_CONTENT + " INTEGER," + 182 Voicemails.MIME_TYPE + " TEXT," + 183 Voicemails.SOURCE_DATA + " TEXT," + 184 Voicemails.SOURCE_PACKAGE + " TEXT," + 185 Voicemails.TRANSCRIPTION + " TEXT," + 186 Voicemails.TRANSCRIPTION_STATE + " INTEGER NOT NULL DEFAULT 0," + 187 Voicemails.STATE + " INTEGER," + 188 Voicemails.DIRTY + " INTEGER NOT NULL DEFAULT 0," + 189 Voicemails.DELETED + " INTEGER NOT NULL DEFAULT 0," + 190 Voicemails.BACKED_UP + " INTEGER NOT NULL DEFAULT 0," + 191 Voicemails.RESTORED + " INTEGER NOT NULL DEFAULT 0," + 192 Voicemails.ARCHIVED + " INTEGER NOT NULL DEFAULT 0," + 193 Voicemails.IS_OMTP_VOICEMAIL + " INTEGER NOT NULL DEFAULT 0" + 194 ");"); 195 196 db.execSQL("CREATE TABLE " + Tables.VOICEMAIL_STATUS + " (" + 197 VoicemailContract.Status._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + 198 VoicemailContract.Status.SOURCE_PACKAGE + " TEXT NOT NULL," + 199 VoicemailContract.Status.PHONE_ACCOUNT_COMPONENT_NAME + " TEXT," + 200 VoicemailContract.Status.PHONE_ACCOUNT_ID + " TEXT," + 201 VoicemailContract.Status.SETTINGS_URI + " TEXT," + 202 VoicemailContract.Status.VOICEMAIL_ACCESS_URI + " TEXT," + 203 VoicemailContract.Status.CONFIGURATION_STATE + " INTEGER," + 204 VoicemailContract.Status.DATA_CHANNEL_STATE + " INTEGER," + 205 VoicemailContract.Status.NOTIFICATION_CHANNEL_STATE + " INTEGER," + 206 VoicemailContract.Status.QUOTA_OCCUPIED + " INTEGER DEFAULT -1," + 207 VoicemailContract.Status.QUOTA_TOTAL + " INTEGER DEFAULT -1," + 208 VoicemailContract.Status.SOURCE_TYPE + " TEXT" + 209 ");"); 210 211 } 212 213 @Override onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)214 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 215 if (DEBUG) { 216 Log.d(TAG, "onUpgrade"); 217 } 218 219 if (oldVersion < 2) { 220 upgradeToVersion2(db); 221 } 222 223 if (oldVersion < 3) { 224 upgradeToVersion3(db); 225 } 226 227 if (oldVersion < 4) { 228 upgradeToVersion4(db); 229 } 230 231 if (oldVersion < 5) { 232 upgradeToVersion5(db); 233 } 234 235 if (oldVersion < 6) { 236 upgradeToVersion6(db); 237 } 238 239 if (oldVersion < 7) { 240 upgradeToVersion7(db); 241 } 242 243 if (oldVersion < 8) { 244 upgradetoVersion8(db); 245 } 246 247 if (oldVersion < 9) { 248 upgradeToVersion9(db); 249 } 250 251 if (oldVersion < 10) { 252 upgradeToVersion10(db); 253 } 254 255 if (oldVersion < 11) { 256 upgradeToVersion11(db); 257 } 258 259 if (oldVersion < 12) { 260 upgradeToVersion12(db); 261 } 262 } 263 264 @Override onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion)265 public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { 266 // Ignore 267 } 268 } 269 270 @VisibleForTesting CallLogDatabaseHelper(Context context, String databaseName)271 CallLogDatabaseHelper(Context context, String databaseName) { 272 mContext = context; 273 mPhoneAccountHandleMigrationUtils = new PhoneAccountHandleMigrationUtils( 274 context, PhoneAccountHandleMigrationUtils.TYPE_CALL_LOG); 275 mOpenHelper = new OpenHelper(mContext, databaseName, /* factory=*/ null, DATABASE_VERSION); 276 } 277 getInstance(Context context)278 public static synchronized CallLogDatabaseHelper getInstance(Context context) { 279 if (sInstance == null) { 280 sInstance = new CallLogDatabaseHelper(context, DATABASE_NAME); 281 } 282 return sInstance; 283 } 284 getInstanceForShadow(Context context)285 public static synchronized CallLogDatabaseHelper getInstanceForShadow(Context context) { 286 if (sInstanceForShadow == null) { 287 // Shadow provider is always encryption-aware. 288 sInstanceForShadow = new CallLogDatabaseHelper( 289 context.createDeviceProtectedStorageContext(), SHADOW_DATABASE_NAME); 290 } 291 return sInstanceForShadow; 292 } 293 getReadableDatabase()294 public SQLiteDatabase getReadableDatabase() { 295 return mOpenHelper.getReadableDatabase(); 296 } 297 getWritableDatabase()298 public SQLiteDatabase getWritableDatabase() { 299 return mOpenHelper.getWritableDatabase(); 300 } 301 getProperty(String key, String defaultValue)302 public String getProperty(String key, String defaultValue) { 303 return PropertyUtils.getProperty(getReadableDatabase(), key, defaultValue); 304 } 305 setProperty(String key, String value)306 public void setProperty(String key, String value) { 307 PropertyUtils.setProperty(getWritableDatabase(), key, value); 308 } 309 310 /** 311 * Updates phone account migration pending status, indicating if there is any phone account 312 * handle that need to migrate. Called in CallLogProvider. 313 */ updatePhoneAccountHandleMigrationPendingStatus()314 void updatePhoneAccountHandleMigrationPendingStatus() { 315 mPhoneAccountHandleMigrationUtils.updatePhoneAccountHandleMigrationPendingStatus( 316 getWritableDatabase()); 317 } 318 319 /** 320 * Migrate all the pending phone account handles based on the given iccId and subId. Used 321 * by CallLogProvider. 322 */ migratePendingPhoneAccountHandles(String iccId, String subId)323 void migratePendingPhoneAccountHandles(String iccId, String subId) { 324 mPhoneAccountHandleMigrationUtils.migratePendingPhoneAccountHandles( 325 iccId, subId, getWritableDatabase()); 326 } 327 328 /** 329 * Try to migrate any PhoneAccountId to SubId from IccId. Used by CallLogProvider. 330 */ migrateIccIdToSubId()331 void migrateIccIdToSubId() { 332 mPhoneAccountHandleMigrationUtils.migrateIccIdToSubId(getWritableDatabase()); 333 } 334 335 /** 336 * Add the {@link Calls.VIA_NUMBER} Column to the CallLog Database. 337 */ upgradeToVersion2(SQLiteDatabase db)338 private void upgradeToVersion2(SQLiteDatabase db) { 339 db.execSQL("ALTER TABLE " + Tables.CALLS + " ADD " + Calls.VIA_NUMBER + 340 " TEXT NOT NULL DEFAULT ''"); 341 } 342 343 /** 344 * Add the {@link Status.SOURCE_TYPE} Column to the VoicemailStatus Database. 345 */ upgradeToVersion3(SQLiteDatabase db)346 private void upgradeToVersion3(SQLiteDatabase db) { 347 db.execSQL("ALTER TABLE " + Tables.VOICEMAIL_STATUS + " ADD " + Status.SOURCE_TYPE + 348 " TEXT"); 349 } 350 351 /** 352 * Add {@link Voicemails.BACKED_UP} {@link Voicemails.ARCHIVE} {@link 353 * Voicemails.IS_OMTP_VOICEMAIL} column to the CallLog database. 354 */ upgradeToVersion4(SQLiteDatabase db)355 private void upgradeToVersion4(SQLiteDatabase db) { 356 db.execSQL("ALTER TABLE calls ADD backed_up INTEGER NOT NULL DEFAULT 0"); 357 db.execSQL("ALTER TABLE calls ADD restored INTEGER NOT NULL DEFAULT 0"); 358 db.execSQL("ALTER TABLE calls ADD archived INTEGER NOT NULL DEFAULT 0"); 359 db.execSQL("ALTER TABLE calls ADD is_omtp_voicemail INTEGER NOT NULL DEFAULT 0"); 360 } 361 362 /** 363 * Add {@link Voicemails.TRANSCRIPTION_STATE} column to the CallLog database. 364 */ upgradeToVersion5(SQLiteDatabase db)365 private void upgradeToVersion5(SQLiteDatabase db) { 366 db.execSQL("ALTER TABLE calls ADD transcription_state INTEGER NOT NULL DEFAULT 0"); 367 } 368 369 /** 370 * Add {@link CallLog.Calls#CALL_SCREENING_COMPONENT_NAME} 371 * {@link CallLog.Calls#CALL_SCREENING_APP_NAME} 372 * {@link CallLog.Calls#BLOCK_REASON} column to the CallLog database. 373 */ upgradeToVersion6(SQLiteDatabase db)374 private void upgradeToVersion6(SQLiteDatabase db) { 375 db.execSQL("ALTER TABLE calls ADD call_screening_component_name TEXT"); 376 db.execSQL("ALTER TABLE calls ADD call_screening_app_name TEXT"); 377 db.execSQL("ALTER TABLE calls ADD block_reason INTEGER NOT NULL DEFAULT 0"); 378 } 379 380 /** 381 * Add {@code android.telecom.CallIdentification} columns; these are destined to be removed 382 * in {@link #upgradetoVersion8(SQLiteDatabase)}. 383 * @param db DB to upgrade 384 */ upgradeToVersion7(SQLiteDatabase db)385 private void upgradeToVersion7(SQLiteDatabase db) { 386 db.execSQL("ALTER TABLE calls ADD call_id_package_name TEXT NULL"); 387 db.execSQL("ALTER TABLE calls ADD call_id_app_name TEXT NULL"); 388 db.execSQL("ALTER TABLE calls ADD call_id_name TEXT NULL"); 389 db.execSQL("ALTER TABLE calls ADD call_id_description TEXT NULL"); 390 db.execSQL("ALTER TABLE calls ADD call_id_details TEXT NULL"); 391 db.execSQL("ALTER TABLE calls ADD call_id_nuisance_confidence INTEGER NULL"); 392 } 393 394 /** 395 * Remove the {@code android.telecom.CallIdentification} column. 396 * @param db DB to upgrade 397 */ upgradetoVersion8(SQLiteDatabase db)398 private void upgradetoVersion8(SQLiteDatabase db) { 399 db.beginTransaction(); 400 try { 401 String oldTable = Tables.CALLS + "_old"; 402 // SQLite3 doesn't support altering a column name, so we'll rename the old calls table.. 403 db.execSQL("ALTER TABLE calls RENAME TO " + oldTable); 404 405 // ... create a new one (yes, this seems similar to what is in onCreate, but we can't 406 // assume that one won't change in the future) ... 407 db.execSQL("CREATE TABLE " + Tables.CALLS + " (" + 408 Calls._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + 409 Calls.NUMBER + " TEXT," + 410 Calls.NUMBER_PRESENTATION + " INTEGER NOT NULL DEFAULT " + 411 Calls.PRESENTATION_ALLOWED + "," + 412 Calls.POST_DIAL_DIGITS + " TEXT NOT NULL DEFAULT ''," + 413 Calls.VIA_NUMBER + " TEXT NOT NULL DEFAULT ''," + 414 Calls.DATE + " INTEGER," + 415 Calls.DURATION + " INTEGER," + 416 Calls.DATA_USAGE + " INTEGER," + 417 Calls.TYPE + " INTEGER," + 418 Calls.FEATURES + " INTEGER NOT NULL DEFAULT 0," + 419 Calls.PHONE_ACCOUNT_COMPONENT_NAME + " TEXT," + 420 Calls.PHONE_ACCOUNT_ID + " TEXT," + 421 Calls.PHONE_ACCOUNT_ADDRESS + " TEXT," + 422 Calls.PHONE_ACCOUNT_HIDDEN + " INTEGER NOT NULL DEFAULT 0," + 423 Calls.SUB_ID + " INTEGER DEFAULT -1," + 424 Calls.NEW + " INTEGER," + 425 Calls.CACHED_NAME + " TEXT," + 426 Calls.CACHED_NUMBER_TYPE + " INTEGER," + 427 Calls.CACHED_NUMBER_LABEL + " TEXT," + 428 Calls.COUNTRY_ISO + " TEXT," + 429 Calls.VOICEMAIL_URI + " TEXT," + 430 Calls.IS_READ + " INTEGER," + 431 Calls.GEOCODED_LOCATION + " TEXT," + 432 Calls.CACHED_LOOKUP_URI + " TEXT," + 433 Calls.CACHED_MATCHED_NUMBER + " TEXT," + 434 Calls.CACHED_NORMALIZED_NUMBER + " TEXT," + 435 Calls.CACHED_PHOTO_ID + " INTEGER NOT NULL DEFAULT 0," + 436 Calls.CACHED_PHOTO_URI + " TEXT," + 437 Calls.CACHED_FORMATTED_NUMBER + " TEXT," + 438 Calls.ADD_FOR_ALL_USERS + " INTEGER NOT NULL DEFAULT 1," + 439 Calls.LAST_MODIFIED + " INTEGER DEFAULT 0," + 440 Calls.CALL_SCREENING_COMPONENT_NAME + " TEXT," + 441 Calls.CALL_SCREENING_APP_NAME + " TEXT," + 442 Calls.BLOCK_REASON + " INTEGER NOT NULL DEFAULT 0," + 443 444 Voicemails._DATA + " TEXT," + 445 Voicemails.HAS_CONTENT + " INTEGER," + 446 Voicemails.MIME_TYPE + " TEXT," + 447 Voicemails.SOURCE_DATA + " TEXT," + 448 Voicemails.SOURCE_PACKAGE + " TEXT," + 449 Voicemails.TRANSCRIPTION + " TEXT," + 450 Voicemails.TRANSCRIPTION_STATE + " INTEGER NOT NULL DEFAULT 0," + 451 Voicemails.STATE + " INTEGER," + 452 Voicemails.DIRTY + " INTEGER NOT NULL DEFAULT 0," + 453 Voicemails.DELETED + " INTEGER NOT NULL DEFAULT 0," + 454 Voicemails.BACKED_UP + " INTEGER NOT NULL DEFAULT 0," + 455 Voicemails.RESTORED + " INTEGER NOT NULL DEFAULT 0," + 456 Voicemails.ARCHIVED + " INTEGER NOT NULL DEFAULT 0," + 457 Voicemails.IS_OMTP_VOICEMAIL + " INTEGER NOT NULL DEFAULT 0" + 458 ");"); 459 460 String allTheColumns = Calls._ID + ", " + 461 Calls.NUMBER + ", " + 462 Calls.NUMBER_PRESENTATION + ", " + 463 Calls.POST_DIAL_DIGITS + ", " + 464 Calls.VIA_NUMBER + ", " + 465 Calls.DATE + ", " + 466 Calls.DURATION + ", " + 467 Calls.DATA_USAGE + ", " + 468 Calls.TYPE + ", " + 469 Calls.FEATURES + ", " + 470 Calls.PHONE_ACCOUNT_COMPONENT_NAME + ", " + 471 Calls.PHONE_ACCOUNT_ID + ", " + 472 Calls.PHONE_ACCOUNT_ADDRESS + ", " + 473 Calls.PHONE_ACCOUNT_HIDDEN + ", " + 474 Calls.SUB_ID + ", " + 475 Calls.NEW + ", " + 476 Calls.CACHED_NAME + ", " + 477 Calls.CACHED_NUMBER_TYPE + ", " + 478 Calls.CACHED_NUMBER_LABEL + ", " + 479 Calls.COUNTRY_ISO + ", " + 480 Calls.VOICEMAIL_URI + ", " + 481 Calls.IS_READ + ", " + 482 Calls.GEOCODED_LOCATION + ", " + 483 Calls.CACHED_LOOKUP_URI + ", " + 484 Calls.CACHED_MATCHED_NUMBER + ", " + 485 Calls.CACHED_NORMALIZED_NUMBER + ", " + 486 Calls.CACHED_PHOTO_ID + ", " + 487 Calls.CACHED_PHOTO_URI + ", " + 488 Calls.CACHED_FORMATTED_NUMBER + ", " + 489 Calls.ADD_FOR_ALL_USERS + ", " + 490 Calls.LAST_MODIFIED + ", " + 491 Calls.CALL_SCREENING_COMPONENT_NAME + ", " + 492 Calls.CALL_SCREENING_APP_NAME + ", " + 493 Calls.BLOCK_REASON + ", " + 494 495 Voicemails._DATA + ", " + 496 Voicemails.HAS_CONTENT + ", " + 497 Voicemails.MIME_TYPE + ", " + 498 Voicemails.SOURCE_DATA + ", " + 499 Voicemails.SOURCE_PACKAGE + ", " + 500 Voicemails.TRANSCRIPTION + ", " + 501 Voicemails.TRANSCRIPTION_STATE + ", " + 502 Voicemails.STATE + ", " + 503 Voicemails.DIRTY + ", " + 504 Voicemails.DELETED + ", " + 505 Voicemails.BACKED_UP + ", " + 506 Voicemails.RESTORED + ", " + 507 Voicemails.ARCHIVED + ", " + 508 Voicemails.IS_OMTP_VOICEMAIL; 509 510 // .. so we insert into the new table all the values from the old table ... 511 db.execSQL("INSERT INTO " + Tables.CALLS + " (" + 512 allTheColumns + ") SELECT " + 513 allTheColumns + " FROM " + oldTable); 514 515 // .. and drop the old table we renamed. 516 db.execSQL("DROP TABLE " + oldTable); 517 518 db.setTransactionSuccessful(); 519 } finally { 520 db.endTransaction(); 521 } 522 } 523 upgradeToVersion9(SQLiteDatabase db)524 private void upgradeToVersion9(SQLiteDatabase db) { 525 db.execSQL("ALTER TABLE calls ADD missed_reason INTEGER NOT NULL DEFAULT 0"); 526 } 527 upgradeToVersion10(SQLiteDatabase db)528 private void upgradeToVersion10(SQLiteDatabase db) { 529 db.execSQL("ALTER TABLE calls ADD priority INTEGER NOT NULL DEFAULT 0"); 530 db.execSQL("ALTER TABLE calls ADD subject TEXT"); 531 db.execSQL("ALTER TABLE calls ADD location TEXT"); 532 db.execSQL("ALTER TABLE calls ADD composer_photo_uri TEXT"); 533 } 534 upgradeToVersion11(SQLiteDatabase db)535 private void upgradeToVersion11(SQLiteDatabase db) { 536 // Create colums for IS_PHONE_ACCOUNT_MIGRATION_PENDING 537 db.execSQL("ALTER TABLE calls ADD is_call_log_phone_account_migration_pending" 538 + " INTEGER NOT NULL DEFAULT 0"); 539 mPhoneAccountHandleMigrationUtils.markAllTelephonyPhoneAccountsPendingMigration(db); 540 mPhoneAccountHandleMigrationUtils.migrateIccIdToSubId(db); 541 } 542 upgradeToVersion12(SQLiteDatabase db)543 private void upgradeToVersion12(SQLiteDatabase db) { 544 try { 545 db.execSQL("ALTER TABLE calls ADD is_business_call INTEGER NOT NULL DEFAULT 0"); 546 db.execSQL("ALTER TABLE calls ADD asserted_display_name TEXT"); 547 } catch (SQLException ignore) { 548 Log.i(TAG, String.format("upgradeToVersion12: SQLException occurred e=[%s]", ignore)); 549 } 550 } 551 552 @VisibleForTesting tableExists(SQLiteDatabase db, String table)553 static boolean tableExists(SQLiteDatabase db, String table) { 554 return DatabaseUtils.longForQuery(db, 555 "select count(*) from sqlite_master where type='table' and name=?", 556 new String[] {table}) > 0; 557 } 558 559 @VisibleForTesting 560 @Nullable // We return null during tests when migration is not needed or database 561 // is unavailable. getContactsWritableDatabaseForMigration()562 SQLiteDatabase getContactsWritableDatabaseForMigration() { 563 try { 564 return ContactsDatabaseHelper.getInstance(mContext).getWritableDatabase(); 565 } catch (SQLiteCantOpenDatabaseException e) { 566 Log.i(TAG, "Exception caught during opening database for migration: " + e); 567 return null; 568 } 569 } 570 getPhoneAccountHandleMigrationUtils()571 public PhoneAccountHandleMigrationUtils getPhoneAccountHandleMigrationUtils() { 572 return mPhoneAccountHandleMigrationUtils; 573 } 574 selectDistinctColumn(String table, String column)575 public ArraySet<String> selectDistinctColumn(String table, String column) { 576 final ArraySet<String> ret = new ArraySet<>(); 577 final SQLiteDatabase db = getReadableDatabase(); 578 final Cursor c = db.rawQuery("SELECT DISTINCT " 579 + column 580 + " FROM " + table, null); 581 try { 582 c.moveToPosition(-1); 583 while (c.moveToNext()) { 584 if (c.isNull(0)) { 585 continue; 586 } 587 final String s = c.getString(0); 588 589 if (!TextUtils.isEmpty(s)) { 590 ret.add(s); 591 } 592 } 593 return ret; 594 } finally { 595 c.close(); 596 } 597 } 598 599 @VisibleForTesting closeForTest()600 void closeForTest() { 601 mOpenHelper.close(); 602 } 603 wipeForTest()604 public void wipeForTest() { 605 getWritableDatabase().execSQL("DELETE FROM " + Tables.CALLS); 606 } 607 608 @VisibleForTesting getOpenHelper()609 OpenHelper getOpenHelper() { 610 return mOpenHelper; 611 } 612 } 613