aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorShuo Qian <shuoq@google.com>2021-10-19 15:58:44 -0700
committerGrace Jia <xiaotonj@google.com>2022-04-07 09:48:55 -0700
commit809e04f27d64ceb928ec7ad154be7f66937307d2 (patch)
tree50410634de0c5bb5c54e98d39ce80ebca793d0a6
parent3aeb0fbba3aca9aa3f1eaffb24aa4024496188ca (diff)
downloadContactsProvider-809e04f27d64ceb928ec7ad154be7f66937307d2.tar.gz
PhoneAccountHandle without IccId in T
Before Android 13, PhoneAccountHandle#getId() returns the ICCID for Telephony PhoneAccountHandle. Starting from Android 13, PhoneAccountHandle#getId() returns the on-device Subscription ID for Telephony PhoneAccountHandle. This need to migrate the PhoneAccountHandle ID stored in CalllogProvider and ContactsProvider. When the database is created and upgraded, the device finds the mapping between Sub IDs and IccID by looking at the subscription DB in Telephony and migrate. Set pending status if migration is not available yet. The two databases will listen to broadcast "ACTION_PHONE_ACCOUNT_REGISTERED" to identify a new sim event and performing migration for pending status if possible. This need to be registered as a runtime register instead of a manifest broadcast to avoid process started when unnecessary. In multi-user cases, database for each user would be different. A database for each user need to migrate the database when the database is created or initialized. The way to check if the database need to pending is using a sharedPreference. The historical design is based on a logic that the framework unhides the restored PhoneAccountHandle in call log when the PhoneAccountHandle is registered by Telecom. The best time to do the migration based on the sim event is after PhoneAccountHandle is registered by Telecom and before Calllog provider unhides it. Bug: 185235527 Test: atest CallLogMigrationTest; atest ContactsProvider2Test; atest ContactsDatabaseMigrationTest; atest CallLogProviderTest; atest ContactsDatabaseHelperUpgradeTest Change-Id: I9397ad03094da80c4d6c522e1e09a844edfc4231
-rw-r--r--AndroidManifest.xml9
-rw-r--r--src/com/android/providers/contacts/CallLogDatabaseHelper.java76
-rw-r--r--src/com/android/providers/contacts/CallLogProvider.java106
-rw-r--r--src/com/android/providers/contacts/ContactsDatabaseHelper.java51
-rw-r--r--src/com/android/providers/contacts/ContactsProvider2.java94
-rw-r--r--src/com/android/providers/contacts/PhoneAccountRegistrationReceiver.java56
-rw-r--r--src/com/android/providers/contacts/util/PhoneAccountHandleMigrationUtils.java219
-rw-r--r--tests/assets/phoneAccountHandleMigration/calllog_oldversion.dbbin0 -> 57344 bytes
-rw-r--r--tests/assets/phoneAccountHandleMigration/contacts2_oldversion.dbbin0 -> 352256 bytes
-rw-r--r--tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java13
-rw-r--r--tests/src/com/android/providers/contacts/CallLogMigrationTest.java129
-rw-r--r--tests/src/com/android/providers/contacts/CallLogProviderTest.java238
-rw-r--r--tests/src/com/android/providers/contacts/ContactsDatabaseHelperUpgradeTest.java1
-rw-r--r--tests/src/com/android/providers/contacts/ContactsDatabaseMigrationTest.java110
-rw-r--r--tests/src/com/android/providers/contacts/ContactsProvider2Test.java236
-rw-r--r--tests/src/com/android/providers/contacts/ContextWithServiceOverrides.java52
16 files changed, 1297 insertions, 93 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 18e1d3f2..dbc835d3 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -111,15 +111,6 @@
</intent-filter>
</receiver>
- <receiver android:name="PhoneAccountRegistrationReceiver"
- android:exported="true"
- android:permission="android.permission.BROADCAST_PHONE_ACCOUNT_REGISTRATION">
- <!-- Broadcast sent after a phone account is registered in telecom. -->
- <intent-filter>
- <action android:name="android.telecom.action.PHONE_ACCOUNT_REGISTERED"/>
- </intent-filter>
- </receiver>
-
<receiver android:name="LocaleChangeReceiver"
android:exported="true">
<intent-filter>
diff --git a/src/com/android/providers/contacts/CallLogDatabaseHelper.java b/src/com/android/providers/contacts/CallLogDatabaseHelper.java
index 22f1cad4..c5052d70 100644
--- a/src/com/android/providers/contacts/CallLogDatabaseHelper.java
+++ b/src/com/android/providers/contacts/CallLogDatabaseHelper.java
@@ -18,28 +18,38 @@ package com.android.providers.contacts;
import android.annotation.Nullable;
import android.content.ContentValues;
import android.content.Context;
+import android.content.SharedPreferences;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
+import android.preference.PreferenceManager;
import android.provider.CallLog.Calls;
import android.provider.VoicemailContract;
import android.provider.VoicemailContract.Status;
import android.provider.VoicemailContract.Voicemails;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.providers.contacts.util.PhoneAccountHandleMigrationUtils;
import com.android.providers.contacts.util.PropertyUtils;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
/**
* SQLite database (helper) for {@link CallLogProvider} and {@link VoicemailContentProvider}.
*/
public class CallLogDatabaseHelper {
private static final String TAG = "CallLogDatabaseHelper";
- private static final int DATABASE_VERSION = 10;
+ @VisibleForTesting
+ static final int DATABASE_VERSION = 11;
private static final boolean DEBUG = false; // DON'T SUBMIT WITH TRUE
@@ -58,6 +68,9 @@ public class CallLogDatabaseHelper {
private final OpenHelper mOpenHelper;
+ @VisibleForTesting
+ final PhoneAccountHandleMigrationUtils mPhoneAccountHandleMigrationUtils;
+
public interface Tables {
String CALLS = "calls";
String VOICEMAIL_STATUS = "voicemail_status";
@@ -74,7 +87,7 @@ public class CallLogDatabaseHelper {
*
* DO NOT CHANCE ANY OF THE CONSTANTS.
*/
- private interface LegacyConstants {
+ public interface LegacyConstants {
/** Table name used in the contacts DB.*/
String CALLS_LEGACY = "calls";
@@ -85,7 +98,8 @@ public class CallLogDatabaseHelper {
String CALL_LOG_LAST_SYNCED_LEGACY = "call_log_last_synced";
}
- private final class OpenHelper extends SQLiteOpenHelper {
+ @VisibleForTesting
+ public class OpenHelper extends SQLiteOpenHelper {
public OpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory,
int version) {
super(context, name, factory, version);
@@ -157,7 +171,7 @@ public class CallLogDatabaseHelper {
Calls.SUBJECT + " TEXT," +
Calls.LOCATION + " TEXT," +
Calls.COMPOSER_PHOTO_URI + " TEXT," +
-
+ Calls.IS_PHONE_ACCOUNT_MIGRATION_PENDING + " INTEGER NOT NULL DEFAULT 0," +
Voicemails._DATA + " TEXT," +
Voicemails.HAS_CONTENT + " INTEGER," +
Voicemails.MIME_TYPE + " TEXT," +
@@ -233,12 +247,23 @@ public class CallLogDatabaseHelper {
if (oldVersion < 10) {
upgradeToVersion10(db);
}
+
+ if (oldVersion < 11) {
+ upgradeToVersion11(db);
+ }
+ }
+
+ @Override
+ public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ // Ignore
}
}
@VisibleForTesting
CallLogDatabaseHelper(Context context, String databaseName) {
mContext = context;
+ mPhoneAccountHandleMigrationUtils = new PhoneAccountHandleMigrationUtils(
+ context, PhoneAccountHandleMigrationUtils.TYPE_CALL_LOG);
mOpenHelper = new OpenHelper(mContext, databaseName, /* factory=*/ null, DATABASE_VERSION);
}
@@ -275,6 +300,31 @@ public class CallLogDatabaseHelper {
}
/**
+ * Updates phone account migration pending status, indicating if there is any phone account
+ * handle that need to migrate. Called in CallLogProvider.
+ */
+ void updatePhoneAccountHandleMigrationPendingStatus() {
+ mPhoneAccountHandleMigrationUtils.updatePhoneAccountHandleMigrationPendingStatus(
+ getWritableDatabase());
+ }
+
+ /**
+ * Migrate all the pending phone account handles based on the given iccId and subId. Used
+ * by CallLogProvider.
+ */
+ void migratePendingPhoneAccountHandles(String iccId, String subId) {
+ mPhoneAccountHandleMigrationUtils.migratePendingPhoneAccountHandles(
+ iccId, subId, getWritableDatabase());
+ }
+
+ /**
+ * Try to migrate any PhoneAccountId to SubId from IccId. Used by CallLogProvider.
+ */
+ void migrateIccIdToSubId() {
+ mPhoneAccountHandleMigrationUtils.migrateIccIdToSubId(getWritableDatabase());
+ }
+
+ /**
* Add the {@link Calls.VIA_NUMBER} Column to the CallLog Database.
*/
private void upgradeToVersion2(SQLiteDatabase db) {
@@ -473,6 +523,15 @@ public class CallLogDatabaseHelper {
db.execSQL("ALTER TABLE calls ADD location TEXT");
db.execSQL("ALTER TABLE calls ADD composer_photo_uri TEXT");
}
+
+ private void upgradeToVersion11(SQLiteDatabase db) {
+ // Create colums for IS_PHONE_ACCOUNT_MIGRATION_PENDING
+ db.execSQL("ALTER TABLE calls ADD is_call_log_phone_account_migration_pending"
+ + " INTEGER NOT NULL DEFAULT 0");
+ mPhoneAccountHandleMigrationUtils.markAllTelephonyPhoneAccountsPendingMigration(db);
+ mPhoneAccountHandleMigrationUtils.migrateIccIdToSubId(db);
+ }
+
/**
* Perform the migration from the contacts2.db (of the latest version) to the current calllog/
* voicemail status tables.
@@ -567,6 +626,10 @@ public class CallLogDatabaseHelper {
return ContactsDatabaseHelper.getInstance(mContext).getWritableDatabase();
}
+ public PhoneAccountHandleMigrationUtils getPhoneAccountHandleMigrationUtils() {
+ return mPhoneAccountHandleMigrationUtils;
+ }
+
public ArraySet<String> selectDistinctColumn(String table, String column) {
final ArraySet<String> ret = new ArraySet<>();
final SQLiteDatabase db = getReadableDatabase();
@@ -599,4 +662,9 @@ public class CallLogDatabaseHelper {
public void wipeForTest() {
getWritableDatabase().execSQL("DELETE FROM " + Tables.CALLS);
}
+
+ @VisibleForTesting
+ OpenHelper getOpenHelper() {
+ return mOpenHelper;
+ }
}
diff --git a/src/com/android/providers/contacts/CallLogProvider.java b/src/com/android/providers/contacts/CallLogProvider.java
index eaccbb64..948adaa4 100644
--- a/src/com/android/providers/contacts/CallLogProvider.java
+++ b/src/com/android/providers/contacts/CallLogProvider.java
@@ -19,10 +19,12 @@ package com.android.providers.contacts;
import static com.android.providers.contacts.util.DbQueryUtils.checkForSupportedColumns;
import static com.android.providers.contacts.util.DbQueryUtils.getEqualityClause;
import static com.android.providers.contacts.util.DbQueryUtils.getInequalityClause;
+import static com.android.providers.contacts.util.PhoneAccountHandleMigrationUtils.TELEPHONY_COMPONENT_NAME;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AppOpsManager;
+import android.content.BroadcastReceiver;
import android.content.ContentProvider;
import android.content.ContentProviderOperation;
import android.content.ContentProviderResult;
@@ -30,6 +32,8 @@ import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.content.OperationApplicationException;
import android.content.UriMatcher;
import android.database.Cursor;
@@ -49,6 +53,7 @@ import android.provider.CallLog.Calls;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
+import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.ArrayMap;
@@ -58,6 +63,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ProviderAccessStats;
import com.android.providers.contacts.CallLogDatabaseHelper.DbProperties;
import com.android.providers.contacts.CallLogDatabaseHelper.Tables;
+import com.android.providers.contacts.util.NeededForTesting;
import com.android.providers.contacts.util.SelectionBuilder;
import com.android.providers.contacts.util.UserUtils;
@@ -75,7 +81,9 @@ import java.nio.file.StandardOpenOption;
import java.nio.file.attribute.FileTime;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.stream.Collectors;
@@ -88,8 +96,10 @@ public class CallLogProvider extends ContentProvider {
public static final boolean VERBOSE_LOGGING = Log.isLoggable(TAG, Log.VERBOSE);
- private static final int BACKGROUND_TASK_INITIALIZE = 0;
+ @VisibleForTesting
+ protected static final int BACKGROUND_TASK_INITIALIZE = 0;
private static final int BACKGROUND_TASK_ADJUST_PHONE_ACCOUNT = 1;
+ private static final int BACKGROUND_TASK_MIGRATE_PHONE_ACCOUNT_HANDLES = 2;
/** Selection clause for selecting all calls that were made after a certain time */
private static final String MORE_RECENT_THAN_SELECTION = Calls.DATE + "> ?";
@@ -237,8 +247,43 @@ public class CallLogProvider extends ContentProvider {
sCallsProjectionMap.put(Calls.COMPOSER_PHOTO_URI, Calls.COMPOSER_PHOTO_URI);
sCallsProjectionMap.put(Calls.SUBJECT, Calls.SUBJECT);
sCallsProjectionMap.put(Calls.LOCATION, Calls.LOCATION);
+ sCallsProjectionMap.put(Calls.IS_PHONE_ACCOUNT_MIGRATION_PENDING,
+ Calls.IS_PHONE_ACCOUNT_MIGRATION_PENDING);
}
+ /**
+ * Subscription change will trigger ACTION_PHONE_ACCOUNT_REGISTERED that broadcasts new
+ * PhoneAccountHandle that is created based on the new subscription. This receiver is used
+ * for listening new subscription change and migrating phone account handle if any pending.
+ *
+ * It is then used by the call log to un-hide any entries which were previously hidden after
+ * a backup-restore until its associated phone-account is registered with telecom. After a
+ * restore, we hide call log entries until the user inserts the corresponding SIM, registers
+ * the corresponding SIP account, or registers a corresponding alternative phone-account.
+ */
+ private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (TelecomManager.ACTION_PHONE_ACCOUNT_REGISTERED.equals(intent.getAction())) {
+ PhoneAccountHandle phoneAccountHandle =
+ (PhoneAccountHandle) intent.getParcelableExtra(
+ TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);
+ if (mDbHelper.getPhoneAccountHandleMigrationUtils()
+ .isPhoneAccountMigrationPending()
+ && TELEPHONY_COMPONENT_NAME.equals(
+ phoneAccountHandle.getComponentName().flattenToString())
+ && !mMigratedPhoneAccountHandles.contains(phoneAccountHandle)) {
+ mMigratedPhoneAccountHandles.add(phoneAccountHandle);
+ mTaskScheduler.scheduleTask(
+ BACKGROUND_TASK_MIGRATE_PHONE_ACCOUNT_HANDLES, phoneAccountHandle);
+ } else {
+ mTaskScheduler.scheduleTask(BACKGROUND_TASK_ADJUST_PHONE_ACCOUNT,
+ phoneAccountHandle);
+ }
+ }
+ }
+ };
+
private static final String ALLOWED_PACKAGE_FOR_TESTING = "com.android.providers.contacts";
@VisibleForTesting
@@ -254,7 +299,8 @@ public class CallLogProvider extends ContentProvider {
private ContactsTaskScheduler mTaskScheduler;
- private volatile CountDownLatch mReadAccessLatch;
+ @VisibleForTesting
+ protected volatile CountDownLatch mReadAccessLatch;
private CallLogDatabaseHelper mDbHelper;
private DatabaseUtils.InsertHelper mCallsInserter;
@@ -262,10 +308,12 @@ public class CallLogProvider extends ContentProvider {
private int mMinMatch;
private VoicemailPermissions mVoicemailPermissions;
private CallLogInsertionHelper mCallLogInsertionHelper;
+ private SubscriptionManager mSubscriptionManager;
private final ThreadLocal<Boolean> mApplyingBatch = new ThreadLocal<>();
private final ThreadLocal<Integer> mCallingUid = new ThreadLocal<>();
private final ProviderAccessStats mStats = new ProviderAccessStats();
+ private final Set<PhoneAccountHandle> mMigratedPhoneAccountHandles = new HashSet<>();
protected boolean isShadow() {
return false;
@@ -308,6 +356,13 @@ public class CallLogProvider extends ContentProvider {
mTaskScheduler.scheduleTask(BACKGROUND_TASK_INITIALIZE, null);
+ mSubscriptionManager = context.getSystemService(SubscriptionManager.class);
+
+ // Register a receiver to hear sim change event for migrating pending
+ // PhoneAccountHandle ID or/and unhides restored call logs
+ IntentFilter filter = new IntentFilter(TelecomManager.ACTION_PHONE_ACCOUNT_REGISTERED);
+ context.registerReceiver(mBroadcastReceiver, filter);
+
if (Log.isLoggable(Constants.PERFORMANCE_TAG, Log.DEBUG)) {
Log.d(Constants.PERFORMANCE_TAG, getProviderName() + ".onCreate finish");
}
@@ -329,6 +384,25 @@ public class CallLogProvider extends ContentProvider {
return mMinMatch;
}
+ @NeededForTesting
+ public CallLogDatabaseHelper getCallLogDatabaseHelperForTest() {
+ return mDbHelper;
+ }
+
+ @NeededForTesting
+ public void setCallLogDatabaseHelperForTest(CallLogDatabaseHelper callLogDatabaseHelper) {
+ mDbHelper = callLogDatabaseHelper;
+ }
+
+ /**
+ * @return the currently registered BroadcastReceiver for listening
+ * ACTION_PHONE_ACCOUNT_REGISTERED in the current process.
+ */
+ @NeededForTesting
+ public BroadcastReceiver getBroadcastReceiverForTest() {
+ return mBroadcastReceiver;
+ }
+
protected CallLogDatabaseHelper getDatabaseHelper(final Context context) {
return CallLogDatabaseHelper.getInstance(context);
}
@@ -853,10 +927,6 @@ public class CallLogProvider extends ContentProvider {
}
}
- void adjustForNewPhoneAccount(PhoneAccountHandle handle) {
- mTaskScheduler.scheduleTask(BACKGROUND_TASK_ADJUST_PHONE_ACCOUNT, handle);
- }
-
/**
* Returns a {@link DatabaseModifier} that takes care of sending necessary notifications
* after the operation is performed.
@@ -1063,7 +1133,6 @@ public class CallLogProvider extends ContentProvider {
// Keep going and get as many as we can.
}
}
-
}
/**
* Un-hides any hidden call log entries that are associated with the specified handle.
@@ -1103,7 +1172,6 @@ public class CallLogProvider extends ContentProvider {
cursor.close();
}
}
-
}
/**
@@ -1195,16 +1263,34 @@ public class CallLogProvider extends ContentProvider {
}
}
- private void performBackgroundTask(int task, Object arg) {
+ @VisibleForTesting
+ protected void performBackgroundTask(int task, Object arg) {
if (task == BACKGROUND_TASK_INITIALIZE) {
try {
+ mDbHelper.updatePhoneAccountHandleMigrationPendingStatus();
+ if (mDbHelper.getPhoneAccountHandleMigrationUtils()
+ .isPhoneAccountMigrationPending()) {
+ Log.i(TAG, "performBackgroundTask for pending PhoneAccountHandle migration");
+ mDbHelper.migrateIccIdToSubId();
+ }
syncEntries();
} finally {
mReadAccessLatch.countDown();
- mReadAccessLatch = null;
}
} else if (task == BACKGROUND_TASK_ADJUST_PHONE_ACCOUNT) {
+ Log.i(TAG, "performBackgroundTask for unhide PhoneAccountHandles");
adjustForNewPhoneAccountInternal((PhoneAccountHandle) arg);
+ } else if (task == BACKGROUND_TASK_MIGRATE_PHONE_ACCOUNT_HANDLES) {
+ PhoneAccountHandle phoneAccountHandle = (PhoneAccountHandle) arg;
+ String iccId = mSubscriptionManager.getActiveSubscriptionInfo(
+ Integer.parseInt(phoneAccountHandle.getId())).getIccId();
+ if (iccId == null) {
+ Log.i(TAG, "ACTION_PHONE_ACCOUNT_REGISTERED received null IccId.");
+ } else {
+ Log.i(TAG, "ACTION_PHONE_ACCOUNT_REGISTERED received for migrating phone"
+ + " account handle SubId: " + phoneAccountHandle.getId());
+ mDbHelper.migratePendingPhoneAccountHandles(iccId, phoneAccountHandle.getId());
+ }
}
}
diff --git a/src/com/android/providers/contacts/ContactsDatabaseHelper.java b/src/com/android/providers/contacts/ContactsDatabaseHelper.java
index 0102a662..802b2488 100644
--- a/src/com/android/providers/contacts/ContactsDatabaseHelper.java
+++ b/src/com/android/providers/contacts/ContactsDatabaseHelper.java
@@ -43,6 +43,7 @@ import android.os.Binder;
import android.os.Bundle;
import android.os.SystemClock;
import android.os.UserManager;
+import android.preference.PreferenceManager;
import android.provider.BaseColumns;
import android.provider.ContactsContract;
import android.provider.ContactsContract.AggregationExceptions;
@@ -103,6 +104,7 @@ import com.android.providers.contacts.sqlite.DatabaseAnalyzer;
import com.android.providers.contacts.sqlite.SqlChecker;
import com.android.providers.contacts.sqlite.SqlChecker.InvalidSqlException;
import com.android.providers.contacts.util.NeededForTesting;
+import com.android.providers.contacts.util.PhoneAccountHandleMigrationUtils;
import com.android.providers.contacts.util.PropertyUtils;
import com.google.common.base.Strings;
@@ -111,8 +113,10 @@ import java.io.PrintWriter;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
import java.util.Locale;
+import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue;
@@ -150,7 +154,7 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
* 1600-1699 T
* </pre>
*/
- static final int DATABASE_VERSION = 1603;
+ static final int DATABASE_VERSION = 1604;
private static final int MINIMUM_SUPPORTED_VERSION = 700;
@VisibleForTesting
@@ -947,6 +951,7 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
private final boolean mIsTestInstance;
private final SyncStateContentProviderHelper mSyncState;
private final CountryMonitor mCountryMonitor;
+ private final PhoneAccountHandleMigrationUtils mPhoneAccountHandleMigrationUtils;
/**
* Time when the DB was created. It's persisted in {@link DbProperties#DATABASE_TIME_CREATED},
@@ -997,10 +1002,16 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
return new ContactsDatabaseHelper(context, filename, false, /* isTestInstance=*/ true);
}
+ public PhoneAccountHandleMigrationUtils getPhoneAccountHandleMigrationUtils() {
+ return mPhoneAccountHandleMigrationUtils;
+ }
+
protected ContactsDatabaseHelper(
Context context, String databaseName, boolean optimizationEnabled,
boolean isTestInstance) {
super(context, databaseName, null, DATABASE_VERSION, MINIMUM_SUPPORTED_VERSION, null);
+ mPhoneAccountHandleMigrationUtils = new PhoneAccountHandleMigrationUtils(
+ context, PhoneAccountHandleMigrationUtils.TYPE_CONTACTS);
boolean enableWal = android.provider.Settings.Global.getInt(context.getContentResolver(),
android.provider.Settings.Global.CONTACTS_DATABASE_WAL_ENABLED, 1) == 1;
if (dbForProfile() != 0 || ActivityManager.isLowRamDeviceStatic()) {
@@ -1013,7 +1024,6 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
mIsTestInstance = isTestInstance;
mContext = context;
mSyncState = new SyncStateContentProviderHelper();
-
mCountryMonitor = new CountryMonitor(context, this::updateUseStrictPhoneNumberComparison);
startListeningToDeviceConfigUpdates();
@@ -1139,7 +1149,8 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
}
}
- private void createPresenceTables(SQLiteDatabase db) {
+ @VisibleForTesting
+ void createPresenceTables(SQLiteDatabase db) {
db.execSQL("CREATE TABLE IF NOT EXISTS " + Tables.PRESENCE + " ("+
StatusUpdates.DATA_ID + " INTEGER PRIMARY KEY REFERENCES data(_id)," +
StatusUpdates.PROTOCOL + " INTEGER NOT NULL," +
@@ -1439,6 +1450,7 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
Data.SYNC3 + " TEXT, " +
Data.SYNC4 + " TEXT, " +
Data.CARRIER_PRESENCE + " INTEGER NOT NULL DEFAULT 0, " +
+ Data.IS_PHONE_ACCOUNT_MIGRATION_PENDING + " INTEGER NOT NULL DEFAULT 0, " +
Data.PREFERRED_PHONE_ACCOUNT_COMPONENT_NAME + " TEXT, " +
Data.PREFERRED_PHONE_ACCOUNT_ID + " TEXT " +
");");
@@ -2666,6 +2678,12 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
oldVersion = 1603;
}
+ if (isUpgradeRequired(oldVersion, newVersion, 1604)) {
+ upgradeToVersion1604(db);
+ upgradeViewsAndTriggers = true;
+ oldVersion = 1604;
+ }
+
// We extracted "calls" and "voicemail_status" at this point, but we can't remove them here
// yet, until CallLogDatabaseHelper moves the data.
@@ -3504,6 +3522,33 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
}
}
+ @VisibleForTesting
+ public void upgradeToVersion1604(SQLiteDatabase db) {
+ // Create colums for IS_PHONE_ACCOUNT_MIGRATION_PENDING
+ try {
+ db.execSQL("ALTER TABLE data ADD is_preferred_phone_account_migration_pending"
+ + " INTEGER NOT NULL DEFAULT 0;");
+ } catch (SQLException ignore) {
+ Log.v(TAG, "Version 1604: Columns already exist, skipping upgrade steps.");
+ }
+ mPhoneAccountHandleMigrationUtils.markAllTelephonyPhoneAccountsPendingMigration(db);
+ mPhoneAccountHandleMigrationUtils.migrateIccIdToSubId(db);
+ }
+
+ protected void migrateIccIdToSubId() {
+ mPhoneAccountHandleMigrationUtils.migrateIccIdToSubId(getWritableDatabase());
+ }
+
+ protected void migratePendingPhoneAccountHandles(String iccId, String subId) {
+ mPhoneAccountHandleMigrationUtils.migratePendingPhoneAccountHandles(
+ iccId, subId, getWritableDatabase());
+ }
+
+ protected void updatePhoneAccountHandleMigrationPendingStatus() {
+ mPhoneAccountHandleMigrationUtils.updatePhoneAccountHandleMigrationPendingStatus(
+ getWritableDatabase());
+ }
+
/**
* This method is only used in upgradeToVersion1101 method, and should not be used in other
* places now. Because data15 is not used to generate hash_id for photo, and the new generating
diff --git a/src/com/android/providers/contacts/ContactsProvider2.java b/src/com/android/providers/contacts/ContactsProvider2.java
index a66395ad..3f61a15a 100644
--- a/src/com/android/providers/contacts/ContactsProvider2.java
+++ b/src/com/android/providers/contacts/ContactsProvider2.java
@@ -20,6 +20,8 @@ import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static com.android.providers.contacts.util.PhoneAccountHandleMigrationUtils.TELEPHONY_COMPONENT_NAME;
+
import android.os.Looper;
import android.accounts.Account;
import android.accounts.AccountManager;
@@ -28,6 +30,7 @@ import android.annotation.Nullable;
import android.annotation.WorkerThread;
import android.app.AppOpsManager;
import android.app.SearchManager;
+import android.content.BroadcastReceiver;
import android.content.ContentProviderOperation;
import android.content.ContentProviderResult;
import android.content.ContentResolver;
@@ -36,6 +39,7 @@ import android.content.ContentValues;
import android.content.Context;
import android.content.IContentService;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.OperationApplicationException;
import android.content.SharedPreferences;
import android.content.SyncAdapterType;
@@ -116,7 +120,10 @@ import android.provider.OpenableColumns;
import android.provider.Settings.Global;
import android.provider.SyncStateContract;
import android.sysprop.ContactsProperties;
+import android.telecom.PhoneAccountHandle;
+import android.telecom.TelecomManager;
import android.telephony.PhoneNumberUtils;
+import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.ArrayMap;
@@ -172,6 +179,7 @@ import com.android.providers.contacts.util.DbQueryUtils;
import com.android.providers.contacts.util.LogFields;
import com.android.providers.contacts.util.LogUtils;
import com.android.providers.contacts.util.NeededForTesting;
+import com.android.providers.contacts.util.PhoneAccountHandleMigrationUtils;
import com.android.providers.contacts.util.UserUtils;
import com.android.vcard.VCardComposer;
import com.android.vcard.VCardConfig;
@@ -202,6 +210,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
+import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@@ -250,6 +259,8 @@ public class ContactsProvider2 extends AbstractContactsProvider
private static final int BACKGROUND_TASK_CLEAN_DELETE_LOG = 11;
private static final int BACKGROUND_TASK_RESCAN_DIRECTORY = 12;
private static final int BACKGROUND_TASK_CLEANUP_DANGLING_CONTACTS = 13;
+ @VisibleForTesting
+ protected static final int BACKGROUND_TASK_MIGRATE_PHONE_ACCOUNT_HANDLES = 14;
protected static final int STATUS_NORMAL = 0;
protected static final int STATUS_UPGRADING = 1;
@@ -1430,6 +1441,7 @@ public class ContactsProvider2 extends AbstractContactsProvider
private PostalSplitter mPostalSplitter;
private ContactDirectoryManager mContactDirectoryManager;
+ private SubscriptionManager mSubscriptionManager;
private boolean mIsPhoneInitialized;
private boolean mIsPhone;
@@ -1479,6 +1491,36 @@ public class ContactsProvider2 extends AbstractContactsProvider
// Enterprise members
private EnterprisePolicyGuard mEnterprisePolicyGuard;
+ private Set<PhoneAccountHandle> mMigratedPhoneAccountHandles;
+
+ /**
+ * Subscription change will trigger ACTION_PHONE_ACCOUNT_REGISTERED that broadcasts new
+ * PhoneAccountHandle that is created based on the new subscription. This receiver is used
+ * for listening new subscription change and migrating phone account handle if any pending.
+ */
+ private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (TelecomManager.ACTION_PHONE_ACCOUNT_REGISTERED.equals(intent.getAction())) {
+ PhoneAccountHandle phoneAccountHandle =
+ (PhoneAccountHandle) intent.getParcelableExtra(
+ TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);
+ Log.i(TAG, "onReceive ACTION_PHONE_ACCOUNT_REGISTERED pending? "
+ + mContactsHelper.getPhoneAccountHandleMigrationUtils()
+ .isPhoneAccountMigrationPending());
+ if (mContactsHelper.getPhoneAccountHandleMigrationUtils()
+ .isPhoneAccountMigrationPending()
+ && TELEPHONY_COMPONENT_NAME.equals(
+ phoneAccountHandle.getComponentName().flattenToString())
+ && !mMigratedPhoneAccountHandles.contains(phoneAccountHandle)) {
+ mMigratedPhoneAccountHandles.add(phoneAccountHandle);
+ scheduleBackgroundTask(
+ BACKGROUND_TASK_MIGRATE_PHONE_ACCOUNT_HANDLES, phoneAccountHandle);
+ }
+ }
+ }
+ };
+
@Override
public boolean onCreate() {
if (VERBOSE_LOGGING) {
@@ -1525,7 +1567,7 @@ public class ContactsProvider2 extends AbstractContactsProvider
new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().build());
mFastScrollingIndexCache = FastScrollingIndexCache.getInstance(getContext());
-
+ mSubscriptionManager = getContext().getSystemService(SubscriptionManager.class);
mContactsHelper = getDatabaseHelper();
mDbHelper.set(mContactsHelper);
@@ -1535,6 +1577,12 @@ public class ContactsProvider2 extends AbstractContactsProvider
mContactDirectoryManager = new ContactDirectoryManager(this);
mGlobalSearchSupport = new GlobalSearchSupport(this);
+ if (mContactsHelper.getPhoneAccountHandleMigrationUtils()
+ .isPhoneAccountMigrationPending()) {
+ IntentFilter filter = new IntentFilter(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);
+ getContext().registerReceiver(mBroadcastReceiver, filter);
+ }
+
// The provider is closed for business until fully initialized
mReadAccessLatch = new CountDownLatch(1);
mWriteAccessLatch = new CountDownLatch(1);
@@ -1554,12 +1602,14 @@ public class ContactsProvider2 extends AbstractContactsProvider
mProfileProvider.attachInfo(getContext(), profileInfo);
mProfileHelper = mProfileProvider.getDatabaseHelper();
mEnterprisePolicyGuard = new EnterprisePolicyGuard(getContext());
+ mMigratedPhoneAccountHandles = new HashSet<>();
// Initialize the pre-authorized URI duration.
mPreAuthorizedUriDuration = DEFAULT_PREAUTHORIZED_URI_EXPIRATION;
scheduleBackgroundTask(BACKGROUND_TASK_INITIALIZE);
scheduleBackgroundTask(BACKGROUND_TASK_UPDATE_ACCOUNTS);
+ scheduleBackgroundTask(BACKGROUND_TASK_MIGRATE_PHONE_ACCOUNT_HANDLES);
scheduleBackgroundTask(BACKGROUND_TASK_UPDATE_LOCALE);
scheduleBackgroundTask(BACKGROUND_TASK_UPGRADE_AGGREGATION_ALGORITHM);
scheduleBackgroundTask(BACKGROUND_TASK_UPDATE_SEARCH_INDEX);
@@ -1683,6 +1733,7 @@ public class ContactsProvider2 extends AbstractContactsProvider
switchToContactMode();
switch (task) {
case BACKGROUND_TASK_INITIALIZE: {
+ mContactsHelper.updatePhoneAccountHandleMigrationPendingStatus();
initForDefaultLocale();
mReadAccessLatch.countDown();
mReadAccessLatch = null;
@@ -1718,6 +1769,32 @@ public class ContactsProvider2 extends AbstractContactsProvider
break;
}
+ case BACKGROUND_TASK_MIGRATE_PHONE_ACCOUNT_HANDLES: {
+ if (arg == null) {
+ // No phone account handle specified, try to execute all pending migrations.
+ if (mContactsHelper.getPhoneAccountHandleMigrationUtils()
+ .isPhoneAccountMigrationPending()) {
+ mContactsHelper.migrateIccIdToSubId();
+ }
+ } else {
+ // Phone account handle specified, task scheduled when
+ // ACTION_PHONE_ACCOUNT_REGISTERED received.
+ PhoneAccountHandle phoneAccountHandle = (PhoneAccountHandle) arg;
+ String iccId = mSubscriptionManager.getActiveSubscriptionInfo(
+ Integer.parseInt(phoneAccountHandle.getId())).getIccId();
+ if (iccId == null) {
+ Log.i(TAG, "ACTION_PHONE_ACCOUNT_REGISTERED received null IccId.");
+ } else {
+ Log.i(TAG, "ACTION_PHONE_ACCOUNT_REGISTERED received for migrating phone"
+ + " account handle SubId: " + phoneAccountHandle.getId());
+ mContactsHelper.migratePendingPhoneAccountHandles(iccId,
+ phoneAccountHandle.getId());
+ mContactsHelper.updatePhoneAccountHandleMigrationPendingStatus();
+ }
+ }
+ break;
+ }
+
case BACKGROUND_TASK_RESCAN_DIRECTORY: {
updateDirectoriesInBackground(true);
break;
@@ -9925,6 +10002,15 @@ public class ContactsProvider2 extends AbstractContactsProvider
return mDbHelper.get();
}
+ /**
+ * @return the currently registered BroadcastReceiver for listening
+ * ACTION_PHONE_ACCOUNT_REGISTERED in the current process.
+ */
+ @NeededForTesting
+ public BroadcastReceiver getBroadcastReceiverForTest() {
+ return mBroadcastReceiver;
+ }
+
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (mContactAggregator != null) {
@@ -10011,6 +10097,12 @@ public class ContactsProvider2 extends AbstractContactsProvider
return mContactsHelper;
}
+ /** Should be only used in tests. */
+ @NeededForTesting
+ public void setContactsDatabaseHelperForTest(ContactsDatabaseHelper contactsHelper) {
+ mContactsHelper = contactsHelper;
+ }
+
@VisibleForTesting
public ProfileProvider getProfileProviderForTest() {
return mProfileProvider;
diff --git a/src/com/android/providers/contacts/PhoneAccountRegistrationReceiver.java b/src/com/android/providers/contacts/PhoneAccountRegistrationReceiver.java
deleted file mode 100644
index 8a688892..00000000
--- a/src/com/android/providers/contacts/PhoneAccountRegistrationReceiver.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.providers.contacts;
-
-import android.content.BroadcastReceiver;
-import android.content.ContentProvider;
-import android.content.Context;
-import android.content.IContentProvider;
-import android.content.Intent;
-import android.provider.CallLog;
-import android.telecom.PhoneAccountHandle;
-import android.telecom.TelecomManager;
-
-/**
- * This will be launched when a new phone account is registered in telecom. It is used by the call
- * log to un-hide any entries which were previously hidden after a backup-restore until it's
- * associated phone-account is registered with telecom.
- *
- * IOW, after a restore, we hide call log entries until the user inserts the corresponding SIM,
- * registers the corresponding SIP account, or registers a corresponding alternative phone-account.
- */
-public class PhoneAccountRegistrationReceiver extends BroadcastReceiver {
- static final String TAG = "PhoneAccountReceiver";
-
- @Override
- public void onReceive(Context context, Intent intent) {
- // We are now running with the system up, but no apps started,
- // so can do whatever cleanup after an upgrade that we want.
- if (TelecomManager.ACTION_PHONE_ACCOUNT_REGISTERED.equals(intent.getAction())) {
-
- PhoneAccountHandle handle = (PhoneAccountHandle) intent.getParcelableExtra(
- TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);
-
- IContentProvider iprovider =
- context.getContentResolver().acquireProvider(CallLog.AUTHORITY);
- ContentProvider provider = ContentProvider.coerceToLocalContentProvider(iprovider);
- if (provider instanceof CallLogProvider) {
- ((CallLogProvider) provider).adjustForNewPhoneAccount(handle);
- }
- }
- }
-}
diff --git a/src/com/android/providers/contacts/util/PhoneAccountHandleMigrationUtils.java b/src/com/android/providers/contacts/util/PhoneAccountHandleMigrationUtils.java
new file mode 100644
index 00000000..b578314a
--- /dev/null
+++ b/src/com/android/providers/contacts/util/PhoneAccountHandleMigrationUtils.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.providers.contacts.util;
+
+import com.android.providers.contacts.CallLogDatabaseHelper;
+import com.android.providers.contacts.ContactsDatabaseHelper;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.provider.CallLog;
+import android.provider.CallLog.Calls;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.provider.ContactsContract.Data;
+import android.preference.PreferenceManager;
+import android.telephony.SubscriptionManager;
+import android.telephony.SubscriptionInfo;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Utils for PhoneAccountHandle Migration Operations in database providers.
+ *
+ * When the database is created and upgraded, PhoneAccountHandleMigrationUtils helps migrate IccId
+ * to SubId. If the PhoneAccount haven't registered yet, we set the pending status for further
+ * migration. Databases will listen to broadcast
+ * {@link android.telecom.TelecomManager#ACTION_PHONE_ACCOUNT_REGISTERED} to identify a new sim
+ * event and performing migration for pending status if possible.
+ */
+public class PhoneAccountHandleMigrationUtils {
+ /**
+ * Indicates type of ContactsDatabase.
+ */
+ public static final int TYPE_CONTACTS = 0;
+ /**
+ * Indicates type of CallLogDatabase.
+ */
+ public static final int TYPE_CALL_LOG = 1;
+
+ public static final String TELEPHONY_COMPONENT_NAME =
+ "com.android.phone/com.android.services.telephony.TelephonyConnectionService";
+ private static final String[] TAGS = {
+ "PhoneAccountHandleMigrationUtils_ContactsDatabaseHelper",
+ "PhoneAccountHandleMigrationUtils_CallLogDatabaseHelper"};
+ private static final String[] TABLES = {ContactsDatabaseHelper.Tables.DATA,
+ CallLogDatabaseHelper.Tables.CALLS};
+ private static final String[] IDS = {Data._ID, Calls._ID};
+ private static final String[] PENDING_STATUS_FIELDS = {
+ Data.IS_PHONE_ACCOUNT_MIGRATION_PENDING, Calls.IS_PHONE_ACCOUNT_MIGRATION_PENDING};
+ private static final String[] COMPONENT_NAME_FIELDS = {
+ Data.PREFERRED_PHONE_ACCOUNT_COMPONENT_NAME, Calls.PHONE_ACCOUNT_COMPONENT_NAME};
+ private static final String[] PHONE_ACCOUNT_ID_FIELDS = {
+ Data.PREFERRED_PHONE_ACCOUNT_ID, Calls.PHONE_ACCOUNT_ID};
+
+ private int mType;
+ private SubscriptionManager mSubscriptionManager;
+ private SharedPreferences mSharedPreferences;
+
+ /**
+ * Constructor of the util.
+ */
+ public PhoneAccountHandleMigrationUtils(Context context, int type) {
+ mType = type;
+ mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
+ mSubscriptionManager = context.getSystemService(SubscriptionManager.class);
+ }
+
+ /**
+ * Mark all the telephony phone account handles as pending migration.
+ */
+ public void markAllTelephonyPhoneAccountsPendingMigration(SQLiteDatabase db) {
+ ContentValues valuesForTelephonyPending = new ContentValues();
+ valuesForTelephonyPending.put(PENDING_STATUS_FIELDS[mType], 1);
+ String selection = COMPONENT_NAME_FIELDS[mType] + " = ?";
+ String[] selectionArgs = {TELEPHONY_COMPONENT_NAME};
+ db.beginTransaction();
+ try {
+ int count = db.update(
+ TABLES[mType], valuesForTelephonyPending, selection, selectionArgs);
+ Log.i(TAGS[mType], "markAllTelephonyPhoneAccountsPendingMigration count: " + count);
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
+ }
+
+ /**
+ * Set phone account migration pending status, indicating if there is any phone account handle
+ * that need to migrate. Store the value in the SharedPreference to prevent the need to query
+ * the database in the future for pending migration.
+ */
+ public void setPhoneAccountMigrationStatusPending(boolean status) {
+ mSharedPreferences.edit().putBoolean(PENDING_STATUS_FIELDS[mType], status).apply();
+ }
+
+ /**
+ * Checks phone account migration pending status, indicating if there is any phone account
+ * handle that need to migrate. Query the value in the SharedPreference to prevent the need
+ * to query the database in the future for pending migration.
+ */
+ public boolean isPhoneAccountMigrationPending() {
+ return mSharedPreferences.getBoolean(PENDING_STATUS_FIELDS[mType], false);
+ }
+
+ /**
+ * Updates phone account migration pending status, indicating if there is any phone account
+ * handle that need to migrate.
+ */
+ public void updatePhoneAccountHandleMigrationPendingStatus(SQLiteDatabase sqLiteDatabase) {
+ // Check to see if any entries need phone account migration pending.
+ long count = DatabaseUtils.longForQuery(sqLiteDatabase, "SELECT COUNT(DISTINCT "
+ + IDS[mType] + ") FROM " + TABLES[mType] + " WHERE "
+ + PENDING_STATUS_FIELDS[mType] + " == 1", null);
+ if (count > 0) {
+ Log.i(TAGS[mType], "updatePhoneAccountHandleMigrationPendingStatus true");
+ setPhoneAccountMigrationStatusPending(true);
+ } else {
+ Log.i(TAGS[mType], "updatePhoneAccountHandleMigrationPendingStatus false");
+ setPhoneAccountMigrationStatusPending(false);
+ }
+ }
+
+ /**
+ * Migrate all the pending phone account handles based on the given iccId and subId.
+ */
+ public void migratePendingPhoneAccountHandles(String iccId, String subId, SQLiteDatabase db) {
+ ContentValues valuesForPhoneAccountId = new ContentValues();
+ valuesForPhoneAccountId.put(PHONE_ACCOUNT_ID_FIELDS[mType], subId);
+ valuesForPhoneAccountId.put(PENDING_STATUS_FIELDS[mType], 0);
+ String selection = PHONE_ACCOUNT_ID_FIELDS[mType] + " LIKE ? AND "
+ + PENDING_STATUS_FIELDS[mType] + " = ?";
+ String[] selectionArgs = {iccId, "1"};
+ db.beginTransaction();
+ try {
+ int count = db.update(TABLES[mType], valuesForPhoneAccountId, selection, selectionArgs);
+ Log.i(TAGS[mType], "migrated pending PhoneAccountHandle subId: " + subId
+ + " count: " + count);
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
+ updatePhoneAccountHandleMigrationPendingStatus(db);
+ }
+
+ /**
+ * Try to migrate any PhoneAccountId to SubId from IccId.
+ */
+ public void migrateIccIdToSubId(SQLiteDatabase db) {
+ final HashMap<String, String> phoneAccountIdsMigrateNow = new HashMap<>();
+ final Cursor phoneAccountIdsCursor = db.rawQuery(
+ "SELECT DISTINCT " + PHONE_ACCOUNT_ID_FIELDS[mType] + " FROM " + TABLES[mType]
+ + " WHERE " + PENDING_STATUS_FIELDS[mType] + " = 1", null);
+
+ try {
+ List<SubscriptionInfo> subscriptionInfoList = mSubscriptionManager
+ .getAllSubscriptionInfoList();
+ phoneAccountIdsCursor.moveToPosition(-1);
+ while (phoneAccountIdsCursor.moveToNext()) {
+ if (phoneAccountIdsCursor.isNull(0)) {
+ continue;
+ }
+ final String iccId = phoneAccountIdsCursor.getString(0);
+ String subId = null;
+ if (mSubscriptionManager != null) {
+ subId = getSubIdForIccId(iccId, subscriptionInfoList);
+ }
+
+ if (!TextUtils.isEmpty(iccId)) {
+ if (subId != null) {
+ // If there is already a subId that maps to the corresponding iccid
+ // from an old phone account handle, migrate to the new phone account
+ // handle with sub id without pending.
+ phoneAccountIdsMigrateNow.put(iccId, subId);
+ Log.i(TAGS[mType], "migrateIccIdToSubId(db): found subId: " + subId);
+ }
+ }
+ }
+ } finally {
+ phoneAccountIdsCursor.close();
+ }
+ // Migrate to the new phone account handle with its sub ID that is already available.
+ for (Map.Entry<String, String> set : phoneAccountIdsMigrateNow.entrySet()) {
+ migratePendingPhoneAccountHandles(set.getKey(), set.getValue(), db);
+ }
+ }
+
+ // Return a subId that maps to the given iccId, or null if the subId is not available.
+ private String getSubIdForIccId(String iccId, List<SubscriptionInfo> subscriptionInfoList) {
+ for (SubscriptionInfo subscriptionInfo : subscriptionInfoList) {
+ // Some old version callog would store phone account handle id with the IccId
+ // string plus "F", and the getIccId() returns IccId string itself without "F",
+ // so here need to use "startsWith" to match.
+ if (iccId.startsWith(subscriptionInfo.getIccId())) {
+ Log.i(TAGS[mType], "getSubIdForIccId: Found subscription ID to migrate: "
+ + subscriptionInfo.getSubscriptionId());
+ return Integer.toString(subscriptionInfo.getSubscriptionId());
+ }
+ }
+ return null;
+ }
+} \ No newline at end of file
diff --git a/tests/assets/phoneAccountHandleMigration/calllog_oldversion.db b/tests/assets/phoneAccountHandleMigration/calllog_oldversion.db
new file mode 100644
index 00000000..e00b9855
--- /dev/null
+++ b/tests/assets/phoneAccountHandleMigration/calllog_oldversion.db
Binary files differ
diff --git a/tests/assets/phoneAccountHandleMigration/contacts2_oldversion.db b/tests/assets/phoneAccountHandleMigration/contacts2_oldversion.db
new file mode 100644
index 00000000..128da646
--- /dev/null
+++ b/tests/assets/phoneAccountHandleMigration/contacts2_oldversion.db
Binary files differ
diff --git a/tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java b/tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java
index 971656b5..54984d29 100644
--- a/tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java
+++ b/tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java
@@ -56,6 +56,7 @@ import android.provider.ContactsContract.Settings;
import android.provider.ContactsContract.StatusUpdates;
import android.provider.ContactsContract.StreamItems;
import android.provider.VoicemailContract;
+import android.telephony.SubscriptionManager;
import android.test.MoreAsserts;
import android.test.mock.MockContentResolver;
import android.util.Log;
@@ -79,6 +80,9 @@ import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
/**
* A common superclass for {@link ContactsProvider2}-related tests.
*/
@@ -110,6 +114,9 @@ public abstract class BaseContactsProvider2Test extends PhotoLoadingTestCase {
protected final static String NO_STRING = new String("");
protected final static Account NO_ACCOUNT = new Account("a", "b");
+ ContextWithServiceOverrides mTestContext;
+ @Mock SubscriptionManager mSubscriptionManager;
+
/**
* Use {@link MockClock#install()} to start using it.
* It'll be automatically uninstalled by {@link #tearDown()}.
@@ -127,9 +134,13 @@ public abstract class BaseContactsProvider2Test extends PhotoLoadingTestCase {
@Override
protected void setUp() throws Exception {
super.setUp();
+ MockitoAnnotations.initMocks(this);
+
+ mTestContext = new ContextWithServiceOverrides(getContext());
+ mTestContext.injectSystemService(SubscriptionManager.class, mSubscriptionManager);
mActor = new ContactsActor(
- getContext(), getContextPackageName(), getProviderClass(), getAuthority());
+ mTestContext, getContextPackageName(), getProviderClass(), getAuthority());
mResolver = mActor.resolver;
if (mActor.provider instanceof SynchronousContactsProvider2) {
getContactsProvider().wipeData();
diff --git a/tests/src/com/android/providers/contacts/CallLogMigrationTest.java b/tests/src/com/android/providers/contacts/CallLogMigrationTest.java
index 767b62f5..d1e80035 100644
--- a/tests/src/com/android/providers/contacts/CallLogMigrationTest.java
+++ b/tests/src/com/android/providers/contacts/CallLogMigrationTest.java
@@ -15,10 +15,19 @@
*/
package com.android.providers.contacts;
+import static com.android.providers.contacts.CallLogDatabaseHelper.DATABASE_VERSION;
+
+import android.content.ContentValues;
import android.content.Context;
import android.database.DatabaseUtils;
import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.provider.CallLog.Calls;
+import android.provider.VoicemailContract;
import android.test.suitebuilder.annotation.LargeTest;
+import android.util.Log;
+
+import com.android.providers.contacts.util.PhoneAccountHandleMigrationUtils;
import java.io.File;
import java.io.FileOutputStream;
@@ -28,6 +37,12 @@ import java.io.OutputStream;
@LargeTest
public class CallLogMigrationTest extends FixedAndroidTestCase {
+ private final static String TAG = CallLogMigrationTest.class.getSimpleName();
+
+ // Maximum number for database version that need migration
+ public static final int DATABASE_VERSION_NEED_MIGRATION = 10;
+ // Component name for call log entries that don't need migration
+ public static final String NO_MIGRATION_COMPONENT_NAME = "foo/bar";
private void writeAssetFileToDisk(String assetName, File diskPath) throws IOException {
final Context context = getTestContext();
@@ -46,6 +61,38 @@ public class CallLogMigrationTest extends FixedAndroidTestCase {
}
}
+ /** Insert a call log to db with specified phone account component name */
+ private boolean insertCallLog(SQLiteDatabase db, String componentName) {
+ final ContentValues values = new ContentValues();
+ values.put(Calls.PHONE_ACCOUNT_COMPONENT_NAME, componentName);
+ return db.insert(CallLogDatabaseHelper.Tables.CALLS, null, values) != -1;
+ }
+
+ /*
+ * Test onUpgrade() step, check the IS_PHONE_ACCOUNT_MIGRATION_PENDING column is upgraded.
+ */
+ public void testPhoneAccountMigrationMarkingOnUpgrade() throws IOException {
+ SQLiteDatabase db = new InMemoryCallLogProviderDbHelperV1(mContext,
+ DATABASE_VERSION).getWritableDatabase();
+ CallLogDatabaseHelperTestable testable = new CallLogDatabaseHelperTestable(
+ getTestContext(), null);
+ CallLogDatabaseHelper.OpenHelper openHelper = testable.getOpenHelper();
+ // Insert 3 entries that 2 of its is_call_log_phone_account_migration_pending should be set
+ // to 1
+ assertTrue(insertCallLog(db, PhoneAccountHandleMigrationUtils.TELEPHONY_COMPONENT_NAME));
+ assertTrue(insertCallLog(db, PhoneAccountHandleMigrationUtils.TELEPHONY_COMPONENT_NAME));
+ assertTrue(insertCallLog(db, NO_MIGRATION_COMPONENT_NAME));
+
+ openHelper.onUpgrade(db, DATABASE_VERSION_NEED_MIGRATION, DATABASE_VERSION);
+
+ // Check each entry in the CALLS has a new coloumn of
+ // Calls.IS_PHONE_ACCOUNT_MIGRATION_PENDING that has a value of either 0 or 1
+ assertEquals(2, DatabaseUtils.longForQuery(
+ db, "select count(*) from " + CallLogDatabaseHelper.Tables.CALLS
+ + " where " + Calls.IS_PHONE_ACCOUNT_MIGRATION_PENDING
+ + " = 1", null));
+ }
+
public void testMigration() throws IOException {
final File sourceDbFile = new File(getTestContext().getCacheDir(), "contacts2src.db");
writeAssetFileToDisk("calllogmigration/contacts2.db", sourceDbFile);
@@ -57,7 +104,6 @@ public class CallLogMigrationTest extends FixedAndroidTestCase {
// Make sure the source tables exist initially.
assertTrue(CallLogDatabaseHelper.tableExists(sourceDb, "calls"));
assertTrue(CallLogDatabaseHelper.tableExists(sourceDb, "voicemail_status"));
-
// Create the calllog DB to perform the migration.
final CallLogDatabaseHelperTestable dbh =
new CallLogDatabaseHelperTestable(getTestContext(), sourceDb);
@@ -76,6 +122,13 @@ public class CallLogMigrationTest extends FixedAndroidTestCase {
assertEquals("123456",
dbh.getProperty(CallLogDatabaseHelper.DbProperties.CALL_LOG_LAST_SYNCED, ""));
+ // Test onCreate() step, check each entry with TelephonyComponent in the CALLS has
+ // a new coloumn of Calls.IS_PHONE_ACCOUNT_MIGRATION_PENDING.
+ assertEquals(3,
+ DatabaseUtils.longForQuery(db, "select count(*) from "
+ + CallLogDatabaseHelper.Tables.CALLS + " where "
+ + Calls.IS_PHONE_ACCOUNT_MIGRATION_PENDING + " = 0", null));
+
// Also, the source table should have been removed.
assertFalse(CallLogDatabaseHelper.tableExists(sourceDb, "calls"));
assertFalse(CallLogDatabaseHelper.tableExists(sourceDb, "voicemail_status"));
@@ -84,4 +137,78 @@ public class CallLogMigrationTest extends FixedAndroidTestCase {
dbh.getProperty(CallLogDatabaseHelper.DbProperties.DATA_MIGRATED, ""));
}
}
+
+ public static final class InMemoryCallLogProviderDbHelperV1 extends SQLiteOpenHelper {
+ public InMemoryCallLogProviderDbHelperV1(Context context, int databaseVersion) {
+ super(context,
+ null /* "null" DB name to make it an in-memory DB */,
+ null /* CursorFactory is null by default */,
+ databaseVersion);
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ Log.d(TAG, "IN MEMORY DB CREATED");
+
+ db.execSQL("CREATE TABLE " + CallLogDatabaseHelper.Tables.CALLS + " (" +
+ Calls._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
+ Calls.NUMBER + " TEXT," +
+ Calls.NUMBER_PRESENTATION + " INTEGER NOT NULL DEFAULT ''," +
+ Calls.POST_DIAL_DIGITS + " TEXT NOT NULL DEFAULT ''," +
+ Calls.VIA_NUMBER + " TEXT NOT NULL DEFAULT ''," +
+ Calls.DATE + " INTEGER," +
+ Calls.DURATION + " INTEGER," +
+ Calls.DATA_USAGE + " INTEGER," +
+ Calls.TYPE + " INTEGER," +
+ Calls.FEATURES + " INTEGER NOT NULL DEFAULT 0," +
+ Calls.PHONE_ACCOUNT_COMPONENT_NAME + " TEXT," +
+ Calls.PHONE_ACCOUNT_ID + " TEXT," +
+ Calls.PHONE_ACCOUNT_ADDRESS + " TEXT," +
+ Calls.PHONE_ACCOUNT_HIDDEN + " INTEGER NOT NULL DEFAULT 0," +
+ Calls.SUB_ID + " INTEGER DEFAULT -1," +
+ Calls.NEW + " INTEGER," +
+ Calls.CACHED_NAME + " TEXT," +
+ Calls.CACHED_NUMBER_TYPE + " INTEGER," +
+ Calls.CACHED_NUMBER_LABEL + " TEXT," +
+ Calls.COUNTRY_ISO + " TEXT," +
+ Calls.VOICEMAIL_URI + " TEXT," +
+ Calls.IS_READ + " INTEGER," +
+ Calls.GEOCODED_LOCATION + " TEXT," +
+ Calls.CACHED_LOOKUP_URI + " TEXT," +
+ Calls.CACHED_MATCHED_NUMBER + " TEXT," +
+ Calls.CACHED_NORMALIZED_NUMBER + " TEXT," +
+ Calls.CACHED_PHOTO_ID + " INTEGER NOT NULL DEFAULT 0," +
+ Calls.CACHED_PHOTO_URI + " TEXT," +
+ Calls.CACHED_FORMATTED_NUMBER + " TEXT," +
+ Calls.ADD_FOR_ALL_USERS + " INTEGER NOT NULL DEFAULT 1," +
+ Calls.LAST_MODIFIED + " INTEGER DEFAULT 0," +
+ Calls.CALL_SCREENING_COMPONENT_NAME + " TEXT," +
+ Calls.CALL_SCREENING_APP_NAME + " TEXT," +
+ Calls.BLOCK_REASON + " INTEGER NOT NULL DEFAULT 0," +
+ VoicemailContract.Voicemails._DATA + " TEXT," +
+ VoicemailContract.Voicemails.HAS_CONTENT + " INTEGER," +
+ VoicemailContract.Voicemails.MIME_TYPE + " TEXT," +
+ VoicemailContract.Voicemails.SOURCE_DATA + " TEXT," +
+ VoicemailContract.Voicemails.SOURCE_PACKAGE + " TEXT," +
+ VoicemailContract.Voicemails.TRANSCRIPTION + " TEXT," +
+ VoicemailContract.Voicemails.TRANSCRIPTION_STATE + " INTEGER NOT NULL DEFAULT 0," +
+ VoicemailContract.Voicemails.STATE + " INTEGER," +
+ VoicemailContract.Voicemails.DIRTY + " INTEGER NOT NULL DEFAULT 0," +
+ VoicemailContract.Voicemails.DELETED + " INTEGER NOT NULL DEFAULT 0," +
+ VoicemailContract.Voicemails.BACKED_UP + " INTEGER NOT NULL DEFAULT 0," +
+ VoicemailContract.Voicemails.RESTORED + " INTEGER NOT NULL DEFAULT 0," +
+ VoicemailContract.Voicemails.ARCHIVED + " INTEGER NOT NULL DEFAULT 0," +
+ VoicemailContract.Voicemails.IS_OMTP_VOICEMAIL + " INTEGER NOT NULL DEFAULT 0," +
+ Calls.MISSED_REASON + " INTEGER NOT NULL DEFAULT 0," +
+ Calls.PRIORITY + " INTEGER NOT NULL DEFAULT 0," +
+ Calls.SUBJECT + " TEXT," +
+ Calls.LOCATION + " TEXT," +
+ Calls.COMPOSER_PHOTO_URI + " TEXT" +
+ ");");
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ }
+ }
}
diff --git a/tests/src/com/android/providers/contacts/CallLogProviderTest.java b/tests/src/com/android/providers/contacts/CallLogProviderTest.java
index 92b4b171..ebb06556 100644
--- a/tests/src/com/android/providers/contacts/CallLogProviderTest.java
+++ b/tests/src/com/android/providers/contacts/CallLogProviderTest.java
@@ -18,16 +18,24 @@ package com.android.providers.contacts;
import static android.provider.CallLog.Calls.MISSED_REASON_NOT_MISSED;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.when;
+
import android.telecom.CallerInfo;
import com.android.providers.contacts.testutil.CommonDatabaseUtils;
import com.android.providers.contacts.util.ContactsPermissions;
+import com.android.providers.contacts.util.PhoneAccountHandleMigrationUtils;
+import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
+import android.content.Intent;
import android.database.Cursor;
+import android.database.DatabaseUtils;
import android.database.MatrixCursor;
+import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.provider.CallLog;
import android.provider.CallLog.Calls;
@@ -35,10 +43,17 @@ import android.provider.ContactsContract;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.VoicemailContract.Voicemails;
import android.telecom.PhoneAccountHandle;
+import android.telecom.TelecomManager;
+import android.telephony.SubscriptionInfo;
import android.test.suitebuilder.annotation.MediumTest;
+import android.util.Log;
+import java.io.IOException;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
/**
* Unit tests for {@link CallLogProvider}.
@@ -62,13 +77,25 @@ public class CallLogProviderTest extends BaseContactsProvider2Test {
Voicemails.DIRTY,
Voicemails.DELETED};
/** Total number of columns exposed by call_log provider. */
- private static final int NUM_CALLLOG_FIELDS = 40;
+ private static final int NUM_CALLLOG_FIELDS = 41;
private static final int MIN_MATCH = 7;
+ private static final long TEST_TIMEOUT = 5000;
+
+ private static final String TELEPHONY_PACKAGE = "com.android.phone";
+ private static final String TELEPHONY_CLASS
+ = "com.android.services.telephony.TelephonyConnectionService";
+ private static final String TEST_PHONE_ACCOUNT_HANDLE_SUB_ID = "666";
+ private static final int TEST_PHONE_ACCOUNT_HANDLE_SUB_ID_INT = 666;
+ private static final String TEST_PHONE_ACCOUNT_HANDLE_ICC_ID1 = "891004234814455936F";
+ private static final String TEST_PHONE_ACCOUNT_HANDLE_ICC_ID2 = "891004234814455937";
+ private static final String TEST_COMPONENT_NAME = "foo/bar";
+
private int mOldMinMatch;
private CallLogProviderTestable mCallLogProvider;
+ private BroadcastReceiver mBroadcastReceiver;
@Override
protected Class<? extends ContentProvider> getProviderClass() {
@@ -84,6 +111,7 @@ public class CallLogProviderTest extends BaseContactsProvider2Test {
protected void setUp() throws Exception {
super.setUp();
mCallLogProvider = addProvider(CallLogProviderTestable.class, CallLog.AUTHORITY);
+ mBroadcastReceiver = mCallLogProvider.getBroadcastReceiverForTest();
mOldMinMatch = mCallLogProvider.getMinMatchForTest();
mCallLogProvider.setMinMatchForTest(MIN_MATCH);
}
@@ -97,6 +125,211 @@ public class CallLogProviderTest extends BaseContactsProvider2Test {
super.tearDown();
}
+ private CallLogDatabaseHelper getMockCallLogDatabaseHelper(String databaseNameForTesting) {
+ CallLogDatabaseHelper callLogDatabaseHelper = new CallLogDatabaseHelper(
+ mTestContext, databaseNameForTesting);
+ SQLiteDatabase db = callLogDatabaseHelper.getWritableDatabase();
+ // callLogDatabaseHelper.getOpenHelper().onCreate(db);
+ db.execSQL("DELETE FROM " + CallLogDatabaseHelper.Tables.CALLS);
+ {
+ final ContentValues values = new ContentValues();
+ values.put(Calls.PHONE_ACCOUNT_COMPONENT_NAME,
+ PhoneAccountHandleMigrationUtils.TELEPHONY_COMPONENT_NAME);
+ values.put(Calls.IS_PHONE_ACCOUNT_MIGRATION_PENDING, 1);
+ values.put(Calls.PHONE_ACCOUNT_ID, TEST_PHONE_ACCOUNT_HANDLE_ICC_ID1);
+ db.insert(CallLogDatabaseHelper.Tables.CALLS, null, values);
+ }
+ {
+ final ContentValues values = new ContentValues();
+ values.put(Calls.PHONE_ACCOUNT_COMPONENT_NAME,
+ PhoneAccountHandleMigrationUtils.TELEPHONY_COMPONENT_NAME);
+ values.put(Calls.IS_PHONE_ACCOUNT_MIGRATION_PENDING, 1);
+ values.put(Calls.PHONE_ACCOUNT_ID, TEST_PHONE_ACCOUNT_HANDLE_ICC_ID1);
+ db.insert(CallLogDatabaseHelper.Tables.CALLS, null, values);
+ }
+ {
+ final ContentValues values = new ContentValues();
+ values.put(Calls.PHONE_ACCOUNT_COMPONENT_NAME,
+ PhoneAccountHandleMigrationUtils.TELEPHONY_COMPONENT_NAME);
+ values.put(Calls.IS_PHONE_ACCOUNT_MIGRATION_PENDING, 1);
+ values.put(Calls.PHONE_ACCOUNT_ID, TEST_PHONE_ACCOUNT_HANDLE_ICC_ID1);
+ db.insert(CallLogDatabaseHelper.Tables.CALLS, null, values);
+ }
+ {
+ final ContentValues values = new ContentValues();
+ values.put(Calls.PHONE_ACCOUNT_COMPONENT_NAME, TEST_COMPONENT_NAME);
+ values.put(Calls.IS_PHONE_ACCOUNT_MIGRATION_PENDING, 1);
+ values.put(Calls.PHONE_ACCOUNT_ID, TEST_PHONE_ACCOUNT_HANDLE_ICC_ID1);
+ db.insert(CallLogDatabaseHelper.Tables.CALLS, null, values);
+ }
+ {
+ final ContentValues values = new ContentValues();
+ values.put(Calls.PHONE_ACCOUNT_COMPONENT_NAME,
+ PhoneAccountHandleMigrationUtils.TELEPHONY_COMPONENT_NAME);
+ values.put(Calls.IS_PHONE_ACCOUNT_MIGRATION_PENDING, 1);
+ values.put(Calls.PHONE_ACCOUNT_ID, TEST_PHONE_ACCOUNT_HANDLE_ICC_ID2);
+ db.insert(CallLogDatabaseHelper.Tables.CALLS, null, values);
+ }
+ {
+ final ContentValues values = new ContentValues();
+ values.put(Calls.PHONE_ACCOUNT_COMPONENT_NAME,
+ PhoneAccountHandleMigrationUtils.TELEPHONY_COMPONENT_NAME);
+ values.put(Calls.IS_PHONE_ACCOUNT_MIGRATION_PENDING, 1);
+ values.put(Calls.PHONE_ACCOUNT_ID, "FAKE_ICCID");
+ db.insert(CallLogDatabaseHelper.Tables.CALLS, null, values);
+ }
+ return callLogDatabaseHelper;
+ }
+
+ public void testPhoneAccountHandleMigrationSimEvent() throws IOException {
+ CallLogDatabaseHelper originalCallLogDatabaseHelper
+ = mCallLogProvider.getCallLogDatabaseHelperForTest();
+
+ // Mock SubscriptionManager
+ SubscriptionInfo subscriptionInfo = new SubscriptionInfo(
+ TEST_PHONE_ACCOUNT_HANDLE_SUB_ID_INT, TEST_PHONE_ACCOUNT_HANDLE_ICC_ID1,
+ 1, "a", "b", 1, 1, "test", 1, null, null, null, null, false, null, null);
+ when(mSubscriptionManager.getActiveSubscriptionInfo(
+ eq(TEST_PHONE_ACCOUNT_HANDLE_SUB_ID_INT))).thenReturn(subscriptionInfo);
+
+ // Mock CallLogDatabaseHelper
+ CallLogDatabaseHelper callLogDatabaseHelper = getMockCallLogDatabaseHelper(
+ "testCallLogPhoneAccountHandleMigrationSimEvent.db");
+ PhoneAccountHandleMigrationUtils phoneAccountHandleMigrationUtils = callLogDatabaseHelper
+ .getPhoneAccountHandleMigrationUtils();
+
+ // Test setPhoneAccountMigrationStatusPending as false
+ phoneAccountHandleMigrationUtils.setPhoneAccountMigrationStatusPending(false);
+ assertFalse(phoneAccountHandleMigrationUtils.isPhoneAccountMigrationPending());
+
+ // Test CallLogDatabaseHelper.isPhoneAccountMigrationPending as true
+ // and set for testing migration logic
+ phoneAccountHandleMigrationUtils.setPhoneAccountMigrationStatusPending(true);
+ assertTrue(phoneAccountHandleMigrationUtils.isPhoneAccountMigrationPending());
+
+ mCallLogProvider.setCallLogDatabaseHelperForTest(callLogDatabaseHelper);
+ final SQLiteDatabase sqLiteDatabase = callLogDatabaseHelper.getReadableDatabase();
+
+ // Check each entry in the Calls table has a new coloumn of
+ // Calls.IS_PHONE_ACCOUNT_MIGRATION_PENDING of 1
+ assertEquals(6, DatabaseUtils.longForQuery(sqLiteDatabase, "select count(*) from " +
+ CallLogDatabaseHelper.Tables.CALLS + " where " +
+ Calls.IS_PHONE_ACCOUNT_MIGRATION_PENDING + " = 1", null));
+
+ // Prepare PhoneAccountHandle for the new sim event
+ PhoneAccountHandle phoneAccountHandle = new PhoneAccountHandle(
+ new ComponentName(TELEPHONY_PACKAGE, TELEPHONY_CLASS),
+ TEST_PHONE_ACCOUNT_HANDLE_SUB_ID);
+ Intent intent = new Intent(TelecomManager.ACTION_PHONE_ACCOUNT_REGISTERED);
+ intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle);
+
+ mBroadcastReceiver.onReceive(mTestContext, intent);
+
+ // Wait for a while until the migration happens
+ long countMigrated = 0;
+
+ while (countMigrated != 4) {
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ // do nothing
+ }
+ countMigrated = DatabaseUtils.longForQuery(sqLiteDatabase, "select count(*) from " +
+ CallLogDatabaseHelper.Tables.CALLS + " where " +
+ Calls.IS_PHONE_ACCOUNT_MIGRATION_PENDING + " = 0", null);
+ }
+
+ // Check each entry in the CALLS that three coloumns of
+ // Calls.IS_PHONE_ACCOUNT_MIGRATION_PENDING that has migrated
+ assertEquals(4, countMigrated);
+ // Check each entry in the CALLS that one coloumns of
+ // Calls.IS_PHONE_ACCOUNT_MIGRATION_PENDING that is not expected to be migrated
+ assertEquals(2, DatabaseUtils.longForQuery(sqLiteDatabase, "select count(*) from " +
+ CallLogDatabaseHelper.Tables.CALLS + " where " +
+ Calls.IS_PHONE_ACCOUNT_MIGRATION_PENDING + " = 1", null));
+
+ // Verify the pending status of phone account migration.
+ assertTrue(phoneAccountHandleMigrationUtils.isPhoneAccountMigrationPending());
+
+ mCallLogProvider.setCallLogDatabaseHelperForTest(originalCallLogDatabaseHelper);
+ }
+
+
+ public void testPhoneAccountHandleMigrationInitiation() throws Exception {
+ CallLogDatabaseHelper originalCallLogDatabaseHelper
+ = mCallLogProvider.getCallLogDatabaseHelperForTest();
+
+ // Mock SubscriptionManager
+ SubscriptionInfo subscriptionInfo = new SubscriptionInfo(
+ TEST_PHONE_ACCOUNT_HANDLE_SUB_ID_INT, TEST_PHONE_ACCOUNT_HANDLE_ICC_ID1,
+ 1, "a", "b", 1, 1, "test", 1, null, null, null, null, false, null, null);
+ List<SubscriptionInfo> subscriptionInfoList = new ArrayList<>();
+ subscriptionInfoList.add(subscriptionInfo);
+ when(mSubscriptionManager.getAllSubscriptionInfoList()).thenReturn(subscriptionInfoList);
+
+ // Mock CallLogDatabaseHelper
+ CallLogDatabaseHelper callLogDatabaseHelper = getMockCallLogDatabaseHelper(
+ "testCallLogPhoneAccountHandleMigrationInitiation.db");
+ PhoneAccountHandleMigrationUtils phoneAccountHandleMigrationUtils = callLogDatabaseHelper
+ .getPhoneAccountHandleMigrationUtils();
+
+ // Test setPhoneAccountMigrationStatusPending as false
+ phoneAccountHandleMigrationUtils.setPhoneAccountMigrationStatusPending(false);
+ assertFalse(phoneAccountHandleMigrationUtils.isPhoneAccountMigrationPending());
+
+ // Test CallLogDatabaseHelper.isPhoneAccountMigrationPending as true
+ // and set for testing migration logic
+ phoneAccountHandleMigrationUtils.setPhoneAccountMigrationStatusPending(true);
+
+ mCallLogProvider.setCallLogDatabaseHelperForTest(callLogDatabaseHelper);
+ final SQLiteDatabase sqLiteDatabase = callLogDatabaseHelper.getReadableDatabase();
+
+ // Check each entry in the Calls table has a new coloumn of
+ // Calls.IS_PHONE_ACCOUNT_MIGRATION_PENDING as true
+ assertEquals(6, DatabaseUtils.longForQuery(sqLiteDatabase, "select count(*) from " +
+ CallLogDatabaseHelper.Tables.CALLS + " where " +
+ Calls.IS_PHONE_ACCOUNT_MIGRATION_PENDING + " == 1", null));
+
+ // Prepare Task for BACKGROUND_TASK_MIGRATE_PHONE_ACCOUNT_HANDLES
+ mCallLogProvider.mReadAccessLatch = new CountDownLatch(1);
+ mCallLogProvider.performBackgroundTask(mCallLogProvider.BACKGROUND_TASK_INITIALIZE, null);
+ assertTrue(mCallLogProvider.mReadAccessLatch.await(TEST_TIMEOUT, TimeUnit.MILLISECONDS));
+
+ // Check each entry in the CALLS with a coloumn of
+ // Calls.IS_PHONE_ACCOUNT_MIGRATION_PENDING that has migrated
+ Cursor cursor = sqLiteDatabase.query(CallLogDatabaseHelper.Tables.CALLS, null,
+ Calls.IS_PHONE_ACCOUNT_MIGRATION_PENDING + " = 0", null, null, null, null);
+ assertEquals(4, cursor.getCount());
+ while (cursor.moveToNext()) {
+ assertEquals(TEST_PHONE_ACCOUNT_HANDLE_SUB_ID_INT, cursor.getInt(cursor.getColumnIndex(Calls.PHONE_ACCOUNT_ID)));
+ }
+ assertEquals(2, DatabaseUtils.longForQuery(sqLiteDatabase, "select count(*) from " +
+ CallLogDatabaseHelper.Tables.CALLS + " where " +
+ Calls.IS_PHONE_ACCOUNT_MIGRATION_PENDING + " = 1", null));
+
+ // Verify the pending status of phone account migration.
+ assertTrue(phoneAccountHandleMigrationUtils.isPhoneAccountMigrationPending());
+
+ mCallLogProvider.setCallLogDatabaseHelperForTest(originalCallLogDatabaseHelper);
+ }
+
+ public void testPhoneAccountHandleMigrationPendingStatus() {
+ // Mock CallLogDatabaseHelper
+ CallLogDatabaseHelper callLogDatabaseHelper = getMockCallLogDatabaseHelper(
+ "testPhoneAccountHandleMigrationPendingStatus.db");
+ PhoneAccountHandleMigrationUtils phoneAccountHandleMigrationUtils = callLogDatabaseHelper
+ .getPhoneAccountHandleMigrationUtils();
+
+ // Test setPhoneAccountMigrationStatusPending as false
+ phoneAccountHandleMigrationUtils.setPhoneAccountMigrationStatusPending(false);
+ assertFalse(phoneAccountHandleMigrationUtils.isPhoneAccountMigrationPending());
+
+ // Test CallLogDatabaseHelper.isPhoneAccountMigrationPending as true
+ // and set for testing migration logic
+ phoneAccountHandleMigrationUtils.setPhoneAccountMigrationStatusPending(true);
+ assertTrue(phoneAccountHandleMigrationUtils.isPhoneAccountMigrationPending());
+ }
+
public void testInsert_RegularCallRecord() {
setTimeForTest(1000L);
ContentValues values = getDefaultCallValues();
@@ -201,7 +434,7 @@ public class CallLogProviderTest extends BaseContactsProvider2Test {
ContactsPermissions.ALLOW_SELF_CALL = true;
Uri uri = Calls.addCall(ci, getMockContext(), "1-800-263-7643",
Calls.PRESENTATION_ALLOWED, Calls.OUTGOING_TYPE, 0, subscription, 2000,
- 40, null, MISSED_REASON_NOT_MISSED);
+ 40, null, MISSED_REASON_NOT_MISSED, 0);
ContactsPermissions.ALLOW_SELF_CALL = false;
assertNotNull(uri);
assertEquals("0@" + CallLog.AUTHORITY, uri.getAuthority());
@@ -225,6 +458,7 @@ public class CallLogProviderTest extends BaseContactsProvider2Test {
// parameters and the compiler needs a hint as to which form is correct.
values.put(Calls.DATA_USAGE, (Long) null);
values.put(Calls.MISSED_REASON, 0);
+ values.put(Calls.IS_PHONE_ACCOUNT_MIGRATION_PENDING, 0);
assertStoredValues(uri, values);
}
diff --git a/tests/src/com/android/providers/contacts/ContactsDatabaseHelperUpgradeTest.java b/tests/src/com/android/providers/contacts/ContactsDatabaseHelperUpgradeTest.java
index 7ed996dd..0548f980 100644
--- a/tests/src/com/android/providers/contacts/ContactsDatabaseHelperUpgradeTest.java
+++ b/tests/src/com/android/providers/contacts/ContactsDatabaseHelperUpgradeTest.java
@@ -472,6 +472,7 @@ public class ContactsDatabaseHelperUpgradeTest extends BaseDatabaseHelperUpgrade
new TableColumn(Data.SYNC3, TEXT, false, null),
new TableColumn(Data.SYNC4, TEXT, false, null),
new TableColumn(Data.CARRIER_PRESENCE, INTEGER, true, "0"),
+ new TableColumn(Data.IS_PHONE_ACCOUNT_MIGRATION_PENDING, INTEGER, true, "0"),
new TableColumn(Data.PREFERRED_PHONE_ACCOUNT_COMPONENT_NAME, TEXT, false, null),
new TableColumn(Data.PREFERRED_PHONE_ACCOUNT_ID, TEXT, false, null),
};
diff --git a/tests/src/com/android/providers/contacts/ContactsDatabaseMigrationTest.java b/tests/src/com/android/providers/contacts/ContactsDatabaseMigrationTest.java
new file mode 100644
index 00000000..6ef14cf1
--- /dev/null
+++ b/tests/src/com/android/providers/contacts/ContactsDatabaseMigrationTest.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.providers.contacts;
+
+import android.content.Context;
+import android.database.DatabaseUtils;
+import android.database.sqlite.SQLiteDatabase;
+import android.provider.ContactsContract.Data;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+@LargeTest
+public class ContactsDatabaseMigrationTest extends FixedAndroidTestCase {
+
+ public static final int NUM_ENTRIES_CONTACTS_DB_OLD_VERSION = 11;
+
+ private void writeAssetFileToDisk(String assetName, File diskPath) throws IOException {
+ final Context context = getTestContext();
+ final byte[] BUF = new byte[1024 * 32];
+
+ try (final InputStream input = context.getAssets().open(assetName)) {
+ try (final OutputStream output = new FileOutputStream(diskPath)) {
+ for (;;) {
+ final int len = input.read(BUF);
+ if (len == -1) {
+ break;
+ }
+ output.write(BUF, 0, len);
+ }
+ }
+ }
+ }
+
+ /*
+ * Test onUpgrade() step, check the IS_PHONE_ACCOUNT_MIGRATION_PENDING column is upgraded.
+ */
+ public void testPhoneAccountHandleMigrationMarkingOnUpgrade() throws IOException {
+ final File sourceDbFile = getTestContext().getDatabasePath("contacts2.db");
+ writeAssetFileToDisk(
+ "phoneAccountHandleMigration/contacts2_oldversion.db", sourceDbFile);
+
+ try (final SQLiteDatabase sourceDb = SQLiteDatabase.openDatabase(
+ sourceDbFile.getAbsolutePath(), /* cursorFactory=*/ null,
+ SQLiteDatabase.OPEN_READWRITE)) {
+
+ final ContactsDatabaseHelper contactsDatabaseHelper = new ContactsDatabaseHelper(
+ getTestContext(), "contacts2.db", true, /* isTestInstance=*/ false);
+
+ final SQLiteDatabase sqLiteDatabase = contactsDatabaseHelper.getReadableDatabase();
+
+ // Check each entry in the Data has a new coloumn of
+ // Data.IS_PHONE_ACCOUNT_MIGRATION_PENDING that has a value of either 0 or 1
+ assertEquals(NUM_ENTRIES_CONTACTS_DB_OLD_VERSION /** preconfigured entries */,
+ DatabaseUtils.longForQuery(sqLiteDatabase,
+ "select count(*) from " + ContactsDatabaseHelper.Tables.DATA
+ + " where " + Data.IS_PHONE_ACCOUNT_MIGRATION_PENDING
+ + " >= 0", null));
+
+ assertEquals(3 /** preconfigured entries for telephony component*/,
+ DatabaseUtils.longForQuery(sqLiteDatabase,
+ "select count(*) from " + ContactsDatabaseHelper.Tables.DATA
+ + " where " + Data.IS_PHONE_ACCOUNT_MIGRATION_PENDING
+ + " == 1", null));
+
+ assertEquals(8 /** preconfigured entries for no telephony component*/,
+ DatabaseUtils.longForQuery(sqLiteDatabase,
+ "select count(*) from " + ContactsDatabaseHelper.Tables.DATA
+ + " where " + Data.IS_PHONE_ACCOUNT_MIGRATION_PENDING
+ + " == 0", null));
+ }
+ }
+
+ /*
+ * Test onCreate() step, check the IS_PHONE_ACCOUNT_MIGRATION_PENDING column is created
+ * in the schema.
+ */
+ public void testPhoneAccountHandleMigrationOnCreate() throws IOException {
+ final ContactsDatabaseHelper contactsDatabaseHelper = new ContactsDatabaseHelper(
+ getTestContext(), null, true, /* isTestInstance=*/ false);
+
+ final SQLiteDatabase sqLiteDatabase = contactsDatabaseHelper.getReadableDatabase();
+
+ // Check there is a a new coloumn of Data.IS_PHONE_ACCOUNT_MIGRATION_PENDING created
+ // in the schema.
+ assertEquals(0 /** 0 means no entries but the corresponding schema is created */,
+ DatabaseUtils.longForQuery(sqLiteDatabase,
+ "select count(*) from " + ContactsDatabaseHelper.Tables.DATA
+ + " where " + Data.IS_PHONE_ACCOUNT_MIGRATION_PENDING
+ + " >= 0", null));
+ }
+} \ No newline at end of file
diff --git a/tests/src/com/android/providers/contacts/ContactsProvider2Test.java b/tests/src/com/android/providers/contacts/ContactsProvider2Test.java
index 884f5010..09ea19fc 100644
--- a/tests/src/com/android/providers/contacts/ContactsProvider2Test.java
+++ b/tests/src/com/android/providers/contacts/ContactsProvider2Test.java
@@ -19,7 +19,12 @@ package com.android.providers.contacts;
import static com.android.providers.contacts.TestUtils.cv;
import static com.android.providers.contacts.TestUtils.dumpCursor;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.when;
+
import android.accounts.Account;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
import android.content.ContentProviderOperation;
import android.content.ContentProviderResult;
import android.content.ContentResolver;
@@ -27,8 +32,10 @@ import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Entity;
import android.content.EntityIterator;
+import android.content.Intent;
import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
+import android.database.DatabaseUtils;
import android.database.MatrixCursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
@@ -68,7 +75,10 @@ import android.provider.ContactsContract.StatusUpdates;
import android.provider.ContactsContract.StreamItemPhotos;
import android.provider.ContactsContract.StreamItems;
import android.provider.OpenableColumns;
+import android.telecom.PhoneAccountHandle;
+import android.telecom.TelecomManager;
import android.telephony.PhoneNumberUtils;
+import android.telephony.SubscriptionInfo;
import android.test.MoreAsserts;
import android.test.suitebuilder.annotation.LargeTest;
import android.text.TextUtils;
@@ -94,6 +104,7 @@ import com.android.providers.contacts.testutil.DeletedContactUtil;
import com.android.providers.contacts.testutil.RawContactUtil;
import com.android.providers.contacts.testutil.TestUtil;
import com.android.providers.contacts.util.NullContentProvider;
+import com.android.providers.contacts.util.PhoneAccountHandleMigrationUtils;
import com.android.providers.contacts.util.UserUtils;
import com.google.android.collect.Lists;
@@ -126,29 +137,242 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
private static final int MIN_MATCH = 7;
+ static final String TELEPHONY_PACKAGE = "com.android.phone";
+ static final String TELEPHONY_CLASS
+ = "com.android.services.telephony.TelephonyConnectionService";
+ static final String TEST_PHONE_ACCOUNT_HANDLE_SUB_ID = "666";
+ static final int TEST_PHONE_ACCOUNT_HANDLE_SUB_ID_INT = 666;
+ static final String TEST_PHONE_ACCOUNT_HANDLE_ICC_ID1 = "T6E6S6T6I6C6C6I6D";
+ static final String TEST_PHONE_ACCOUNT_HANDLE_ICC_ID2 = "T5E5S5T5I5C5C5I5D";
+ static final String TEST_COMPONENT_NAME = "foo/bar";
+
private int mOldMinMatch1;
private int mOldMinMatch2;
+ ContactsDatabaseHelper mMockContactsDatabaseHelper;
+ private ContactsProvider2 mContactsProvider2;
+ private ContactsDatabaseHelper mDbHelper;
+ private BroadcastReceiver mBroadcastReceiver;
+
@Override
protected void setUp() throws Exception {
super.setUp();
- final ContactsProvider2 cp = (ContactsProvider2) getProvider();
- final ContactsDatabaseHelper dbHelper = cp.getThreadActiveDatabaseHelperForTest();
+ mContactsProvider2 = (ContactsProvider2) getProvider();
+ mDbHelper = mContactsProvider2.getThreadActiveDatabaseHelperForTest();
+ mBroadcastReceiver = mContactsProvider2.getBroadcastReceiverForTest();
mOldMinMatch1 = PhoneNumberUtils.getMinMatchForTest();
- mOldMinMatch2 = dbHelper.getMinMatchForTest();
+ mOldMinMatch2 = mDbHelper.getMinMatchForTest();
PhoneNumberUtils.setMinMatchForTest(MIN_MATCH);
- dbHelper.setMinMatchForTest(MIN_MATCH);
+ mDbHelper.setMinMatchForTest(MIN_MATCH);
}
@Override
protected void tearDown() throws Exception {
final ContactsProvider2 cp = (ContactsProvider2) getProvider();
- final ContactsDatabaseHelper dbHelper = cp.getThreadActiveDatabaseHelperForTest();
+ //final ContactsDatabaseHelper dbHelper = cp.getThreadActiveDatabaseHelperForTest();
PhoneNumberUtils.setMinMatchForTest(mOldMinMatch1);
- dbHelper.setMinMatchForTest(mOldMinMatch2);
+ mDbHelper.setMinMatchForTest(mOldMinMatch2);
super.tearDown();
}
+ private ContactsDatabaseHelper getMockContactsDatabaseHelper(String databaseNameForTesting) {
+ ContactsDatabaseHelper contactsDatabaseHelper = new ContactsDatabaseHelper(
+ mTestContext, databaseNameForTesting, true, /* isTestInstance=*/ false);
+ SQLiteDatabase db = contactsDatabaseHelper.getWritableDatabase();
+ db.execSQL("DELETE FROM " + ContactsDatabaseHelper.Tables.DATA);
+ {
+ final ContentValues values = new ContentValues();
+ values.put(ContactsDatabaseHelper.DataColumns.MIMETYPE_ID, 6666);
+ values.put(Data.RAW_CONTACT_ID, 6666);
+ values.put(Data.PREFERRED_PHONE_ACCOUNT_COMPONENT_NAME,
+ PhoneAccountHandleMigrationUtils.TELEPHONY_COMPONENT_NAME);
+ values.put(Data.IS_PHONE_ACCOUNT_MIGRATION_PENDING, 1);
+ values.put(Data.PREFERRED_PHONE_ACCOUNT_ID, TEST_PHONE_ACCOUNT_HANDLE_ICC_ID1);
+ long count = db.insert(ContactsDatabaseHelper.Tables.DATA, null, values);
+ }
+ {
+ final ContentValues values = new ContentValues();
+ values.put(ContactsDatabaseHelper.DataColumns.MIMETYPE_ID, 6666);
+ values.put(Data.RAW_CONTACT_ID, 6666);
+ values.put(Data.PREFERRED_PHONE_ACCOUNT_COMPONENT_NAME,
+ PhoneAccountHandleMigrationUtils.TELEPHONY_COMPONENT_NAME);
+ values.put(Data.IS_PHONE_ACCOUNT_MIGRATION_PENDING, 1);
+ values.put(Data.PREFERRED_PHONE_ACCOUNT_ID, TEST_PHONE_ACCOUNT_HANDLE_ICC_ID1);
+ long count = db.insert(ContactsDatabaseHelper.Tables.DATA, null, values);
+ }
+ {
+ final ContentValues values = new ContentValues();
+ values.put(ContactsDatabaseHelper.DataColumns.MIMETYPE_ID, 6666);
+ values.put(Data.RAW_CONTACT_ID, 6666);
+ values.put(Data.PREFERRED_PHONE_ACCOUNT_COMPONENT_NAME,
+ PhoneAccountHandleMigrationUtils.TELEPHONY_COMPONENT_NAME);
+ values.put(Data.IS_PHONE_ACCOUNT_MIGRATION_PENDING, 1);
+ values.put(Data.PREFERRED_PHONE_ACCOUNT_ID, TEST_PHONE_ACCOUNT_HANDLE_ICC_ID1);
+ long count = db.insert(ContactsDatabaseHelper.Tables.DATA, null, values);
+ }
+ {
+ final ContentValues values = new ContentValues();
+ values.put(ContactsDatabaseHelper.DataColumns.MIMETYPE_ID, 6666);
+ values.put(Data.RAW_CONTACT_ID, 6666);
+ values.put(Data.PREFERRED_PHONE_ACCOUNT_COMPONENT_NAME,
+ PhoneAccountHandleMigrationUtils.TELEPHONY_COMPONENT_NAME);
+ values.put(Data.IS_PHONE_ACCOUNT_MIGRATION_PENDING, 1);
+ values.put(Data.PREFERRED_PHONE_ACCOUNT_ID, TEST_PHONE_ACCOUNT_HANDLE_ICC_ID1);
+ long count = db.insert(ContactsDatabaseHelper.Tables.DATA, null, values);
+ }
+ {
+ final ContentValues values = new ContentValues();
+ values.put(ContactsDatabaseHelper.DataColumns.MIMETYPE_ID, 6666);
+ values.put(Data.RAW_CONTACT_ID, 6666);
+ values.put(Data.PREFERRED_PHONE_ACCOUNT_COMPONENT_NAME,
+ PhoneAccountHandleMigrationUtils.TELEPHONY_COMPONENT_NAME);
+ values.put(Data.IS_PHONE_ACCOUNT_MIGRATION_PENDING, 1);
+ values.put(Data.PREFERRED_PHONE_ACCOUNT_ID, "FAKE_ICCID");
+ long count = db.insert(ContactsDatabaseHelper.Tables.DATA, null, values);
+ }
+ {
+ final ContentValues values = new ContentValues();
+ values.put(ContactsDatabaseHelper.DataColumns.MIMETYPE_ID, 6666);
+ values.put(Data.RAW_CONTACT_ID, 6666);
+ values.put(Data.PREFERRED_PHONE_ACCOUNT_COMPONENT_NAME, TEST_COMPONENT_NAME);
+ values.put(Data.IS_PHONE_ACCOUNT_MIGRATION_PENDING, 1);
+ values.put(Data.PREFERRED_PHONE_ACCOUNT_ID, "FAKE_ICCID");
+ long count = db.insert(ContactsDatabaseHelper.Tables.DATA, null, values);
+ }
+ return contactsDatabaseHelper;
+ }
+
+ public void testPhoneAccountHandleMigrationSimEvent() throws IOException {
+ ContactsDatabaseHelper originalContactsDatabaseHelper
+ = mContactsProvider2.getContactsDatabaseHelperForTest();
+
+ // Mock SubscriptionManager
+ SubscriptionInfo subscriptionInfo = new SubscriptionInfo(
+ TEST_PHONE_ACCOUNT_HANDLE_SUB_ID_INT, TEST_PHONE_ACCOUNT_HANDLE_ICC_ID1,
+ 1, "a", "b", 1, 1, "test", 1, null, null, null, null, false, null, null);
+ when(mSubscriptionManager.getActiveSubscriptionInfo(
+ eq(TEST_PHONE_ACCOUNT_HANDLE_SUB_ID_INT))).thenReturn(subscriptionInfo);
+
+ // Mock ContactsDatabaseHelper
+ ContactsDatabaseHelper contactsDatabaseHelper = getMockContactsDatabaseHelper(
+ "testContactsPhoneAccountHandleMigrationSimEvent.db");
+
+ // Test setPhoneAccountMigrationStatusPending as false
+ PhoneAccountHandleMigrationUtils phoneAccountHandleMigrationUtils
+ = contactsDatabaseHelper.getPhoneAccountHandleMigrationUtils();
+
+ // Test ContactsDatabaseHelper.isPhoneAccountMigrationPending as true
+ // and set for testing migration logic
+ phoneAccountHandleMigrationUtils.setPhoneAccountMigrationStatusPending(true);
+
+ mContactsProvider2.setContactsDatabaseHelperForTest(contactsDatabaseHelper);
+ final SQLiteDatabase sqLiteDatabase = contactsDatabaseHelper.getReadableDatabase();
+
+ // Check each entry in the Data has a new coloumn of
+ // Data.IS_PHONE_ACCOUNT_MIGRATION_PENDING that has a value of 1
+ assertEquals(6 /** pending migration entries in the preconfigured file */,
+ DatabaseUtils.longForQuery(sqLiteDatabase,
+ "select count(*) from " + ContactsDatabaseHelper.Tables.DATA
+ + " where " + Data.IS_PHONE_ACCOUNT_MIGRATION_PENDING
+ + " = 1", null));
+
+ // Prepare PhoneAccountHandle for the new sim event
+ PhoneAccountHandle phoneAccountHandle = new PhoneAccountHandle(
+ new ComponentName(TELEPHONY_PACKAGE, TELEPHONY_CLASS),
+ TEST_PHONE_ACCOUNT_HANDLE_SUB_ID);
+ Intent intent = new Intent(TelecomManager.ACTION_PHONE_ACCOUNT_REGISTERED);
+ intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle);
+ mBroadcastReceiver.onReceive(mTestContext, intent);
+
+ // Check four coloumns of Data.IS_PHONE_ACCOUNT_MIGRATION_PENDING have migrated
+ assertEquals(4 /** entries in the preconfigured database file */,
+ DatabaseUtils.longForQuery(sqLiteDatabase,
+ "select count(*) from " + ContactsDatabaseHelper.Tables.DATA
+ + " where " + Data.IS_PHONE_ACCOUNT_MIGRATION_PENDING
+ + " = 0", null));
+ // Check two coloumns
+ // of Data.IS_PHONE_ACCOUNT_MIGRATION_PENDING have not migrated
+ assertEquals(2 /** pending migration entries after migration in the preconfigured file */,
+ DatabaseUtils.longForQuery(sqLiteDatabase,
+ "select count(*) from " + ContactsDatabaseHelper.Tables.DATA
+ + " where " + Data.IS_PHONE_ACCOUNT_MIGRATION_PENDING
+ + " = 1", null));
+
+ mContactsProvider2.setContactsDatabaseHelperForTest(originalContactsDatabaseHelper);
+ }
+
+ public void testPhoneAccountHandleMigrationInitiation() throws IOException {
+ ContactsDatabaseHelper originalContactsDatabaseHelper
+ = mContactsProvider2.getContactsDatabaseHelperForTest();
+
+ // Mock SubscriptionManager
+ SubscriptionInfo subscriptionInfo = new SubscriptionInfo(
+ TEST_PHONE_ACCOUNT_HANDLE_SUB_ID_INT, TEST_PHONE_ACCOUNT_HANDLE_ICC_ID1,
+ 1, "a", "b", 1, 1, "test", 1, null, null, null, null, false, null, null);
+ List<SubscriptionInfo> subscriptionInfoList = new ArrayList<>();
+ subscriptionInfoList.add(subscriptionInfo);
+ when(mSubscriptionManager.getAllSubscriptionInfoList()).thenReturn(subscriptionInfoList);
+
+ // Mock ContactsDatabaseHelper
+ ContactsDatabaseHelper contactsDatabaseHelper = getMockContactsDatabaseHelper(
+ "testContactsPhoneAccountHandleMigrationInitiation.db");
+
+ // Test setPhoneAccountMigrationStatusPending as false
+ PhoneAccountHandleMigrationUtils phoneAccountHandleMigrationUtils
+ = contactsDatabaseHelper.getPhoneAccountHandleMigrationUtils();
+
+ // Test ContactsDatabaseHelper.isPhoneAccountMigrationPending as true
+ // and set for testing migration logic
+ phoneAccountHandleMigrationUtils.setPhoneAccountMigrationStatusPending(true);
+
+ mContactsProvider2.setContactsDatabaseHelperForTest(contactsDatabaseHelper);
+ final SQLiteDatabase sqLiteDatabase = contactsDatabaseHelper.getReadableDatabase();
+
+ // Check each entry in the Data has a new coloumn of
+ // Data.IS_PHONE_ACCOUNT_MIGRATION_PENDING that has a value of 1
+ assertEquals(6, DatabaseUtils.longForQuery(sqLiteDatabase,
+ "select count(*) from " + ContactsDatabaseHelper.Tables.DATA
+ + " where " + Data.IS_PHONE_ACCOUNT_MIGRATION_PENDING
+ + " = 1", null));
+
+ // Prepare Task for BACKGROUND_TASK_MIGRATE_PHONE_ACCOUNT_HANDLES
+ mContactsProvider2.performBackgroundTask(
+ mContactsProvider2.BACKGROUND_TASK_MIGRATE_PHONE_ACCOUNT_HANDLES, null);
+
+ // Check four coloumns of Data.IS_PHONE_ACCOUNT_MIGRATION_PENDING have migrated
+ assertEquals(4, DatabaseUtils.longForQuery(sqLiteDatabase,
+ "select count(*) from " + ContactsDatabaseHelper.Tables.DATA
+ + " where " + Data.IS_PHONE_ACCOUNT_MIGRATION_PENDING
+ + " = 0", null));
+ // Check two coloumns of Data.IS_PHONE_ACCOUNT_MIGRATION_PENDING have not migrated
+ assertEquals(2, DatabaseUtils.longForQuery(sqLiteDatabase,
+ "select count(*) from " + ContactsDatabaseHelper.Tables.DATA
+ + " where " + Data.IS_PHONE_ACCOUNT_MIGRATION_PENDING
+ + " = 1", null));
+
+ // Verify the pending status of phone account migration.
+ assertTrue(phoneAccountHandleMigrationUtils.isPhoneAccountMigrationPending());
+
+ mContactsProvider2.setContactsDatabaseHelperForTest(originalContactsDatabaseHelper);
+ }
+
+ public void testPhoneAccountHandleMigrationPendingStatus() {
+ // Mock ContactsDatabaseHelper
+ ContactsDatabaseHelper contactsDatabaseHelper = getMockContactsDatabaseHelper(
+ "testPhoneAccountHandleMigrationPendingStatus.db");
+
+ // Test setPhoneAccountMigrationStatusPending as false
+ PhoneAccountHandleMigrationUtils phoneAccountHandleMigrationUtils
+ = contactsDatabaseHelper.getPhoneAccountHandleMigrationUtils();
+ phoneAccountHandleMigrationUtils.setPhoneAccountMigrationStatusPending(false);
+ assertFalse(phoneAccountHandleMigrationUtils.isPhoneAccountMigrationPending());
+
+ // Test ContactsDatabaseHelper.isPhoneAccountMigrationPending as true
+ // and set for testing migration logic
+ phoneAccountHandleMigrationUtils.setPhoneAccountMigrationStatusPending(true);
+ assertTrue(phoneAccountHandleMigrationUtils.isPhoneAccountMigrationPending());
+ }
+
public void testConvertEnterpriseUriWithEnterpriseDirectoryToLocalUri() {
String phoneNumber = "886";
String directory = String.valueOf(Directory.ENTERPRISE_DEFAULT);
diff --git a/tests/src/com/android/providers/contacts/ContextWithServiceOverrides.java b/tests/src/com/android/providers/contacts/ContextWithServiceOverrides.java
new file mode 100644
index 00000000..c230cea4
--- /dev/null
+++ b/tests/src/com/android/providers/contacts/ContextWithServiceOverrides.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.providers.contacts;
+
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.util.Log;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class ContextWithServiceOverrides extends ContextWrapper {
+ private static final String TAG = "ContextWithOverrides";
+
+ private Map<String, Object> mInjectedSystemServices = new HashMap<>();
+
+ public ContextWithServiceOverrides(Context base) {
+ super(base);
+ }
+
+ public <S> void injectSystemService(Class<S> cls, S service) {
+ final String name = getSystemServiceName(cls);
+ mInjectedSystemServices.put(name, service);
+ }
+
+ @Override
+ public Context getApplicationContext() {
+ return this;
+ }
+
+ @Override
+ public Object getSystemService(String name) {
+ if (mInjectedSystemServices.containsKey(name)) {
+ return mInjectedSystemServices.get(name);
+ }
+ return super.getSystemService(name);
+ }
+} \ No newline at end of file