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