[automerger skipped] Merge "DO NOT MERGE Add check that prevents file operations outside of Call Composer Dir" into sc-v2-dev am: 888ef6f2b6 -s ours

am skip reason: subject contains skip directive

Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/providers/ContactsProvider/+/17949644

Change-Id: Ia3e9ad80f3e97c386793a8d9d9f7480d4c6f03dc
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/Android.bp b/Android.bp
index 4b345e6..08fd957 100644
--- a/Android.bp
+++ b/Android.bp
@@ -1,20 +1,6 @@
 package {
-    default_applicable_licenses: [
-        "packages_providers_ContactsProvider_license",
-    ],
-}
-
-// Added automatically by a large-scale-change
-// See: http://go/android-license-faq
-license {
-    name: "packages_providers_ContactsProvider_license",
-    visibility: [":__subpackages__"],
-    license_kinds: [
-        "SPDX-license-identifier-Apache-2.0",
-    ],
-    license_text: [
-        "NOTICE",
-    ],
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
 android_app {
@@ -49,3 +35,8 @@
         proguard_flags_files: ["proguard.flags"],
     },
 }
+
+platform_compat_config {
+    name: "contacts-provider-platform-compat-config",
+    src: ":ContactsProvider",
+}
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 2b101be..dbc835d 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -22,6 +22,9 @@
     <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
     <uses-permission android:name="android.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS" />
     <uses-permission android:name="android.permission.READ_PHONE_NUMBERS" />
+    <!-- Permissions required for reading and logging compat changes -->
+    <uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG" />
+    <uses-permission android:name="android.permission.LOG_COMPAT_CHANGE" />
 
     <permission
             android:name="android.permission.SEND_CALL_LOG_CHANGE"
@@ -108,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/NOTICE b/NOTICE
deleted file mode 100644
index c5b1efa..0000000
--- a/NOTICE
+++ /dev/null
@@ -1,190 +0,0 @@
-
-   Copyright (c) 2005-2008, 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.
-
-   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.
-
-
-                                 Apache License
-                           Version 2.0, January 2004
-                        http://www.apache.org/licenses/
-
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-   1. Definitions.
-
-      "License" shall mean the terms and conditions for use, reproduction,
-      and distribution as defined by Sections 1 through 9 of this document.
-
-      "Licensor" shall mean the copyright owner or entity authorized by
-      the copyright owner that is granting the License.
-
-      "Legal Entity" shall mean the union of the acting entity and all
-      other entities that control, are controlled by, or are under common
-      control with that entity. For the purposes of this definition,
-      "control" means (i) the power, direct or indirect, to cause the
-      direction or management of such entity, whether by contract or
-      otherwise, or (ii) ownership of fifty percent (50%) or more of the
-      outstanding shares, or (iii) beneficial ownership of such entity.
-
-      "You" (or "Your") shall mean an individual or Legal Entity
-      exercising permissions granted by this License.
-
-      "Source" form shall mean the preferred form for making modifications,
-      including but not limited to software source code, documentation
-      source, and configuration files.
-
-      "Object" form shall mean any form resulting from mechanical
-      transformation or translation of a Source form, including but
-      not limited to compiled object code, generated documentation,
-      and conversions to other media types.
-
-      "Work" shall mean the work of authorship, whether in Source or
-      Object form, made available under the License, as indicated by a
-      copyright notice that is included in or attached to the work
-      (an example is provided in the Appendix below).
-
-      "Derivative Works" shall mean any work, whether in Source or Object
-      form, that is based on (or derived from) the Work and for which the
-      editorial revisions, annotations, elaborations, or other modifications
-      represent, as a whole, an original work of authorship. For the purposes
-      of this License, Derivative Works shall not include works that remain
-      separable from, or merely link (or bind by name) to the interfaces of,
-      the Work and Derivative Works thereof.
-
-      "Contribution" shall mean any work of authorship, including
-      the original version of the Work and any modifications or additions
-      to that Work or Derivative Works thereof, that is intentionally
-      submitted to Licensor for inclusion in the Work by the copyright owner
-      or by an individual or Legal Entity authorized to submit on behalf of
-      the copyright owner. For the purposes of this definition, "submitted"
-      means any form of electronic, verbal, or written communication sent
-      to the Licensor or its representatives, including but not limited to
-      communication on electronic mailing lists, source code control systems,
-      and issue tracking systems that are managed by, or on behalf of, the
-      Licensor for the purpose of discussing and improving the Work, but
-      excluding communication that is conspicuously marked or otherwise
-      designated in writing by the copyright owner as "Not a Contribution."
-
-      "Contributor" shall mean Licensor and any individual or Legal Entity
-      on behalf of whom a Contribution has been received by Licensor and
-      subsequently incorporated within the Work.
-
-   2. Grant of Copyright License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      copyright license to reproduce, prepare Derivative Works of,
-      publicly display, publicly perform, sublicense, and distribute the
-      Work and such Derivative Works in Source or Object form.
-
-   3. Grant of Patent License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      (except as stated in this section) patent license to make, have made,
-      use, offer to sell, sell, import, and otherwise transfer the Work,
-      where such license applies only to those patent claims licensable
-      by such Contributor that are necessarily infringed by their
-      Contribution(s) alone or by combination of their Contribution(s)
-      with the Work to which such Contribution(s) was submitted. If You
-      institute patent litigation against any entity (including a
-      cross-claim or counterclaim in a lawsuit) alleging that the Work
-      or a Contribution incorporated within the Work constitutes direct
-      or contributory patent infringement, then any patent licenses
-      granted to You under this License for that Work shall terminate
-      as of the date such litigation is filed.
-
-   4. Redistribution. You may reproduce and distribute copies of the
-      Work or Derivative Works thereof in any medium, with or without
-      modifications, and in Source or Object form, provided that You
-      meet the following conditions:
-
-      (a) You must give any other recipients of the Work or
-          Derivative Works a copy of this License; and
-
-      (b) You must cause any modified files to carry prominent notices
-          stating that You changed the files; and
-
-      (c) You must retain, in the Source form of any Derivative Works
-          that You distribute, all copyright, patent, trademark, and
-          attribution notices from the Source form of the Work,
-          excluding those notices that do not pertain to any part of
-          the Derivative Works; and
-
-      (d) If the Work includes a "NOTICE" text file as part of its
-          distribution, then any Derivative Works that You distribute must
-          include a readable copy of the attribution notices contained
-          within such NOTICE file, excluding those notices that do not
-          pertain to any part of the Derivative Works, in at least one
-          of the following places: within a NOTICE text file distributed
-          as part of the Derivative Works; within the Source form or
-          documentation, if provided along with the Derivative Works; or,
-          within a display generated by the Derivative Works, if and
-          wherever such third-party notices normally appear. The contents
-          of the NOTICE file are for informational purposes only and
-          do not modify the License. You may add Your own attribution
-          notices within Derivative Works that You distribute, alongside
-          or as an addendum to the NOTICE text from the Work, provided
-          that such additional attribution notices cannot be construed
-          as modifying the License.
-
-      You may add Your own copyright statement to Your modifications and
-      may provide additional or different license terms and conditions
-      for use, reproduction, or distribution of Your modifications, or
-      for any such Derivative Works as a whole, provided Your use,
-      reproduction, and distribution of the Work otherwise complies with
-      the conditions stated in this License.
-
-   5. Submission of Contributions. Unless You explicitly state otherwise,
-      any Contribution intentionally submitted for inclusion in the Work
-      by You to the Licensor shall be under the terms and conditions of
-      this License, without any additional terms or conditions.
-      Notwithstanding the above, nothing herein shall supersede or modify
-      the terms of any separate license agreement you may have executed
-      with Licensor regarding such Contributions.
-
-   6. Trademarks. This License does not grant permission to use the trade
-      names, trademarks, service marks, or product names of the Licensor,
-      except as required for reasonable and customary use in describing the
-      origin of the Work and reproducing the content of the NOTICE file.
-
-   7. Disclaimer of Warranty. Unless required by applicable law or
-      agreed to in writing, Licensor provides the Work (and each
-      Contributor provides its Contributions) on an "AS IS" BASIS,
-      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-      implied, including, without limitation, any warranties or conditions
-      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
-      PARTICULAR PURPOSE. You are solely responsible for determining the
-      appropriateness of using or redistributing the Work and assume any
-      risks associated with Your exercise of permissions under this License.
-
-   8. Limitation of Liability. In no event and under no legal theory,
-      whether in tort (including negligence), contract, or otherwise,
-      unless required by applicable law (such as deliberate and grossly
-      negligent acts) or agreed to in writing, shall any Contributor be
-      liable to You for damages, including any direct, indirect, special,
-      incidental, or consequential damages of any character arising as a
-      result of this License or out of the use or inability to use the
-      Work (including but not limited to damages for loss of goodwill,
-      work stoppage, computer failure or malfunction, or any and all
-      other commercial damages or losses), even if such Contributor
-      has been advised of the possibility of such damages.
-
-   9. Accepting Warranty or Additional Liability. While redistributing
-      the Work or Derivative Works thereof, You may choose to offer,
-      and charge a fee for, acceptance of support, warranty, indemnity,
-      or other liability obligations and/or rights consistent with this
-      License. However, in accepting such obligations, You may act only
-      on Your own behalf and on Your sole responsibility, not on behalf
-      of any other Contributor, and only if You agree to indemnify,
-      defend, and hold each Contributor harmless for any liability
-      incurred by, or claims asserted against, such Contributor by reason
-      of your accepting any such warranty or additional liability.
-
-   END OF TERMS AND CONDITIONS
-
diff --git a/README-tests.md b/README-tests.md
index 8351c6c..cc828c9 100644
--- a/README-tests.md
+++ b/README-tests.md
@@ -3,5 +3,5 @@
 Use the following command to run the unit tests.
 
 ```
-atest ContactsProviderTests ContactsProviderTests2
-```
\ No newline at end of file
+atest ContactsProviderTests
+```
diff --git a/src/com/android/providers/contacts/AccountWithDataSet.java b/src/com/android/providers/contacts/AccountWithDataSet.java
index e1f633e..c20edce 100644
--- a/src/com/android/providers/contacts/AccountWithDataSet.java
+++ b/src/com/android/providers/contacts/AccountWithDataSet.java
@@ -17,11 +17,15 @@
 package com.android.providers.contacts;
 
 import android.accounts.Account;
-import android.provider.ContactsContract;
+import android.content.res.Resources;
+import android.database.DatabaseUtils;
 import android.provider.ContactsContract.SimAccount;
 import android.text.TextUtils;
 
+import com.android.internal.R;
+
 import com.google.common.base.Objects;
+import com.google.common.base.Strings;
 
 import java.util.List;
 
@@ -29,7 +33,16 @@
  * Account information that includes the data set, if any.
  */
 public class AccountWithDataSet {
-    public static final AccountWithDataSet LOCAL = new AccountWithDataSet(null, null, null);
+    public static final AccountWithDataSet LOCAL;
+
+    static {
+        Resources resources = Resources.getSystem();
+        String accountName = Strings.nullToEmpty(
+                resources.getString(R.string.config_rawContactsLocalAccountName));
+        String accountType = Strings.nullToEmpty(
+                resources.getString(R.string.config_rawContactsLocalAccountType));
+        LOCAL = new AccountWithDataSet(accountName, accountType, null);
+    }
 
     private final String mAccountName;
     private final String mAccountType;
@@ -66,7 +79,8 @@
     }
 
     public boolean isLocalAccount() {
-        return (mAccountName == null) && (mAccountType == null);
+        return LOCAL.equals(this) || (
+                mAccountName == null && mAccountType == null && mDataSet == null);
     }
 
     @Override
diff --git a/src/com/android/providers/contacts/CallLogDatabaseHelper.java b/src/com/android/providers/contacts/CallLogDatabaseHelper.java
index 22f1cad..c5052d7 100644
--- a/src/com/android/providers/contacts/CallLogDatabaseHelper.java
+++ b/src/com/android/providers/contacts/CallLogDatabaseHelper.java
@@ -18,28 +18,38 @@
 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 @@
 
     private final OpenHelper mOpenHelper;
 
+    @VisibleForTesting
+    final PhoneAccountHandleMigrationUtils mPhoneAccountHandleMigrationUtils;
+
     public interface Tables {
         String CALLS = "calls";
         String VOICEMAIL_STATUS = "voicemail_status";
@@ -74,7 +87,7 @@
      *
      * 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 @@
         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 @@
                     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 @@
             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 @@
     }
 
     /**
+     * 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 @@
         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 @@
         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 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 991413e..14f8666 100644
--- a/src/com/android/providers/contacts/CallLogProvider.java
+++ b/src/com/android/providers/contacts/CallLogProvider.java
@@ -19,10 +19,12 @@
 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.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.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;
@@ -73,14 +78,14 @@
 import java.io.StringWriter;
 import java.nio.file.DirectoryStream;
 import java.nio.file.Files;
-import java.nio.file.NoSuchFileException;
-import java.nio.file.OpenOption;
 import java.nio.file.Path;
 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;
@@ -93,8 +98,10 @@
 
     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 + "> ?";
@@ -242,8 +249,43 @@
         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
@@ -259,7 +301,8 @@
 
     private ContactsTaskScheduler mTaskScheduler;
 
-    private volatile CountDownLatch mReadAccessLatch;
+    @VisibleForTesting
+    protected volatile CountDownLatch mReadAccessLatch;
 
     private CallLogDatabaseHelper mDbHelper;
     private DatabaseUtils.InsertHelper mCallsInserter;
@@ -267,10 +310,12 @@
     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;
@@ -313,6 +358,13 @@
 
         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");
         }
@@ -334,6 +386,25 @@
         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);
     }
@@ -861,10 +932,6 @@
         }
     }
 
-    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.
@@ -951,15 +1018,13 @@
         }
 
         final UserManager userManager = UserUtils.getUserManager(getContext());
+        final int myUserId = userManager.getProcessUserId();
 
         // TODO: http://b/24944959
-        if (!Calls.shouldHaveSharedCallLogEntries(getContext(), userManager,
-                userManager.getUserHandle())) {
+        if (!Calls.shouldHaveSharedCallLogEntries(getContext(), userManager, myUserId)) {
             return;
         }
 
-        final int myUserId = userManager.getUserHandle();
-
         // See the comment in Calls.addCall() for the logic.
 
         if (userManager.isSystemUser()) {
@@ -1074,7 +1139,6 @@
                 // Keep going and get as many as we can.
             }
         }
-        
     }
     /**
      * Un-hides any hidden call log entries that are associated with the specified handle.
@@ -1114,7 +1178,6 @@
                 cursor.close();
             }
         }
-
     }
 
     /**
@@ -1206,16 +1269,39 @@
         }
     }
 
-    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 = null;
+            try {
+                iccId = mSubscriptionManager.getActiveSubscriptionInfo(
+                    Integer.parseInt(phoneAccountHandle.getId())).getIccId();
+            } catch (NumberFormatException nfe) {
+                // Ignore the exception, iccId will remain null and be handled below.
+            }
+            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 7f4188d..802b248 100644
--- a/src/com/android/providers/contacts/ContactsDatabaseHelper.java
+++ b/src/com/android/providers/contacts/ContactsDatabaseHelper.java
@@ -16,6 +16,7 @@
 
 package com.android.providers.contacts;
 
+import android.accounts.Account;
 import android.app.ActivityManager;
 import android.content.ContentResolver;
 import android.content.ContentValues;
@@ -42,6 +43,7 @@
 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;
@@ -91,6 +93,7 @@
 import android.util.Slog;
 
 import com.android.common.content.SyncStateContentProviderHelper;
+import com.android.internal.R;
 import com.android.internal.R.bool;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.providers.contacts.aggregation.util.CommonNicknameCache;
@@ -101,14 +104,19 @@
 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;
+
 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;
@@ -143,9 +151,10 @@
      *   1300-1399 P
      *   1400-1499 Q
      *   1500-1599 S
+     *   1600-1699 T
      * </pre>
      */
-    static final int DATABASE_VERSION = 1501;
+    static final int DATABASE_VERSION = 1604;
     private static final int MINIMUM_SUPPORTED_VERSION = 700;
 
     @VisibleForTesting
@@ -165,6 +174,16 @@
     private static final String RUSSIA_COUNTRY_CODE = "RU";
     private static final String KAZAKHSTAN_COUNTRY_CODE = "KZ";
 
+    /**
+     * Max size for "simple" fields, such as names, phone numbers and email addresses.
+     */
+    private static final int SIMPLE_FIELD_MAX_SIZE_DEFAULT = 10 * 1024;
+    private static final String SIMPLE_FIELD_MAX_SIZE_KEY = "simple_field_max_size";
+    private static volatile Integer sSimpleFieldMaxSizeCached = null;
+
+    private static final long DEVICE_CONFIG_CACHE_EXPIRATION_MS = 1 * 60 * 60 * 1000; // 1 hour
+    private static volatile long sDeviceConfigCacheExpirationElapsedTime;
+
     public interface Tables {
         public static final String CONTACTS = "contacts";
         public static final String DELETED_CONTACTS = "deleted_contacts";
@@ -224,37 +243,21 @@
                     + ")";
 
         // NOTE: This requires late binding of GroupMembership MIME-type
-        // TODO Consolidate settings and accounts
         public static final String RAW_CONTACTS_JOIN_SETTINGS_DATA_GROUPS = Tables.RAW_CONTACTS
                 + " JOIN " + Tables.ACCOUNTS + " ON ("
                 +   RawContactsColumns.CONCRETE_ACCOUNT_ID + "=" + AccountsColumns.CONCRETE_ID
                     + ")"
-                + "LEFT OUTER JOIN " + Tables.SETTINGS + " ON ("
-                    + AccountsColumns.CONCRETE_ACCOUNT_NAME + "="
-                        + SettingsColumns.CONCRETE_ACCOUNT_NAME + " AND "
-                    + AccountsColumns.CONCRETE_ACCOUNT_TYPE + "="
-                        + SettingsColumns.CONCRETE_ACCOUNT_TYPE + " AND "
-                    + "((" + AccountsColumns.CONCRETE_DATA_SET + " IS NULL AND "
-                            + SettingsColumns.CONCRETE_DATA_SET + " IS NULL) OR ("
-                        + AccountsColumns.CONCRETE_DATA_SET + "="
-                            + SettingsColumns.CONCRETE_DATA_SET + "))) "
                 + "LEFT OUTER JOIN data ON (data.mimetype_id=? AND "
                     + "data.raw_contact_id = raw_contacts._id) "
                 + "LEFT OUTER JOIN groups ON (groups._id = data." + GroupMembership.GROUP_ROW_ID
                 + ")";
 
         // NOTE: This requires late binding of GroupMembership MIME-type
-        // TODO Add missing DATA_SET join -- or just consolidate settings and accounts
-        public static final String SETTINGS_JOIN_RAW_CONTACTS_DATA_MIMETYPES_CONTACTS = "settings "
+        public static final String SETTINGS_JOIN_RAW_CONTACTS_DATA_MIMETYPES_CONTACTS = "accounts "
                 + "LEFT OUTER JOIN raw_contacts ON ("
-                    + RawContactsColumns.CONCRETE_ACCOUNT_ID + "=(SELECT "
+                    + RawContactsColumns.CONCRETE_ACCOUNT_ID + "="
                         + AccountsColumns.CONCRETE_ID
-                        + " FROM " + Tables.ACCOUNTS
-                        + " WHERE "
-                            + "(" + AccountsColumns.CONCRETE_ACCOUNT_NAME
-                                + "=" + SettingsColumns.CONCRETE_ACCOUNT_NAME + ") AND "
-                            + "(" + AccountsColumns.CONCRETE_ACCOUNT_TYPE
-                                + "=" + SettingsColumns.CONCRETE_ACCOUNT_TYPE + ")))"
+                    + ")"
                 + "LEFT OUTER JOIN data ON (data.mimetype_id=? AND "
                     + "data.raw_contact_id = raw_contacts._id) "
                 + "LEFT OUTER JOIN contacts ON (raw_contacts.contact_id = contacts._id)";
@@ -340,6 +343,7 @@
         public static final String ENTITIES = "view_entities";
         public static final String RAW_ENTITIES = "view_raw_entities";
         public static final String GROUPS = "view_groups";
+        public static final String SETTINGS = "view_settings";
 
         /** The data_usage_stat table with the low-res columns. */
         public static final String DATA_USAGE_LR = "view_data_usage";
@@ -368,21 +372,30 @@
     }
 
     public interface Clauses {
+
         final String HAVING_NO_GROUPS = "COUNT(" + DataColumns.CONCRETE_GROUP_ID + ") == 0";
 
-        final String GROUP_BY_ACCOUNT_CONTACT_ID = SettingsColumns.CONCRETE_ACCOUNT_NAME + ","
-                + SettingsColumns.CONCRETE_ACCOUNT_TYPE + "," + RawContacts.CONTACT_ID;
+        final String GROUP_BY_ACCOUNT_CONTACT_ID = AccountsColumns.CONCRETE_ID + ","
+                + RawContacts.CONTACT_ID;
 
         String LOCAL_ACCOUNT_ID =
-                "(SELECT " + AccountsColumns._ID +
-                " FROM " + Tables.ACCOUNTS +
-                " WHERE " +
-                    AccountsColumns.ACCOUNT_NAME + " IS NULL AND " +
-                    AccountsColumns.ACCOUNT_TYPE + " IS NULL AND " +
-                    AccountsColumns.DATA_SET + " IS NULL)";
-
-        final String RAW_CONTACT_IS_LOCAL = RawContactsColumns.CONCRETE_ACCOUNT_ID
-                + "=" + LOCAL_ACCOUNT_ID;
+                "(SELECT "
+                        + AccountsColumns._ID
+                        + " FROM "
+                        + Tables.ACCOUNTS
+                        + " WHERE "
+                        + AccountsColumns.ACCOUNT_NAME
+                        + " IS "
+                        + MoreDatabaseUtils.sqlEscapeNullableString(
+                                AccountWithDataSet.LOCAL.getAccountName())
+                        + " AND "
+                        + AccountsColumns.ACCOUNT_TYPE
+                        + " IS "
+                        + MoreDatabaseUtils.sqlEscapeNullableString(
+                                AccountWithDataSet.LOCAL.getAccountType())
+                        + " AND "
+                        + AccountsColumns.DATA_SET
+                        + " IS NULL)";
 
         final String ZERO_GROUP_MEMBERSHIPS = "COUNT(" + GroupsColumns.CONCRETE_ID + ")=0";
 
@@ -393,8 +406,6 @@
                 "SELECT " +
                     "MAX((SELECT (CASE WHEN " +
                         "(CASE" +
-                            " WHEN " + RAW_CONTACT_IS_LOCAL +
-                            " THEN 1 " +
                             " WHEN " + ZERO_GROUP_MEMBERSHIPS +
                             " THEN " + Settings.UNGROUPED_VISIBLE +
                             " ELSE MAX(" + Groups.GROUP_VISIBLE + ")" +
@@ -417,6 +428,17 @@
                 "EXISTS (SELECT _id FROM " + Tables.DEFAULT_DIRECTORY
                         + " WHERE " + Tables.CONTACTS +"." + Contacts._ID
                         + "=" + Tables.DEFAULT_DIRECTORY +"." + Contacts._ID + ")";
+
+        // Settings are in the accounts table and should only be deletable if there are no
+        // raw contacts or groups remaining in the account.
+        public static final String DELETABLE_SETTINGS =
+                "NOT EXISTS (SELECT 1 FROM " + Tables.RAW_CONTACTS
+                    + " WHERE " + RawContactsColumns.ACCOUNT_ID + "="
+                        + ViewSettingsColumns.CONCRETE_ACCOUNT_ID
+                    + " UNION SELECT 1 FROM " + Tables.GROUPS
+                    + " WHERE " + GroupsColumns.ACCOUNT_ID + "="
+                        + ViewSettingsColumns.CONCRETE_ACCOUNT_ID
+                + ")";
     }
 
     public interface ContactsColumns {
@@ -559,12 +581,8 @@
 
         public static final String ACCOUNT_ID = "account_id";
         public static final String CONCRETE_ACCOUNT_ID = Tables.GROUPS + "." + ACCOUNT_ID;
-    }
 
-    public interface ViewGroupsColumns {
-        String CONCRETE_ACCOUNT_NAME = Views.GROUPS + "." + Groups.ACCOUNT_NAME;
-        String CONCRETE_ACCOUNT_TYPE = Views.GROUPS + "." + Groups.ACCOUNT_TYPE;
-        String CONCRETE_DATA_SET = Views.GROUPS + "." + Groups.DATA_SET;
+        public static final String CONCRETE_SHOULD_SYNC = Tables.GROUPS + "." + Groups.SHOULD_SYNC;
     }
 
     public interface ActivitiesColumns {
@@ -611,13 +629,9 @@
         public static final String CLUSTER = "cluster";
     }
 
-    public interface SettingsColumns {
-        public static final String CONCRETE_ACCOUNT_NAME = Tables.SETTINGS + "."
-                + Settings.ACCOUNT_NAME;
-        public static final String CONCRETE_ACCOUNT_TYPE = Tables.SETTINGS + "."
-                + Settings.ACCOUNT_TYPE;
-        public static final String CONCRETE_DATA_SET = Tables.SETTINGS + "."
-                + Settings.DATA_SET;
+    public interface ViewSettingsColumns {
+        public static final String ACCOUNT_ID = "account_id";
+        public static final String CONCRETE_ACCOUNT_ID = Views.SETTINGS + "." + ACCOUNT_ID;
     }
 
     public interface PresenceColumns {
@@ -703,10 +717,14 @@
         String DATA_SET = RawContacts.DATA_SET;
         String SIM_SLOT_INDEX = "sim_slot_index";
         String SIM_EF_TYPE = "sim_ef_type";
+        String UNGROUPED_VISIBLE = Settings.UNGROUPED_VISIBLE;
+        String SHOULD_SYNC = Settings.SHOULD_SYNC;
+        String IS_DEFAULT = Settings.IS_DEFAULT;
 
         String CONCRETE_ACCOUNT_NAME = Tables.ACCOUNTS + "." + ACCOUNT_NAME;
         String CONCRETE_ACCOUNT_TYPE = Tables.ACCOUNTS + "." + ACCOUNT_TYPE;
         String CONCRETE_DATA_SET = Tables.ACCOUNTS + "." + DATA_SET;
+
     }
 
     public interface DirectoryColumns {
@@ -933,6 +951,7 @@
     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},
@@ -983,10 +1002,16 @@
         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()) {
@@ -999,7 +1024,6 @@
         mIsTestInstance = isTestInstance;
         mContext = context;
         mSyncState = new SyncStateContentProviderHelper();
-
         mCountryMonitor = new CountryMonitor(context, this::updateUseStrictPhoneNumberComparison);
 
         startListeningToDeviceConfigUpdates();
@@ -1125,7 +1149,8 @@
         }
     }
 
-    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," +
@@ -1224,8 +1249,10 @@
                 AccountsColumns.ACCOUNT_TYPE + " TEXT, " +
                 AccountsColumns.DATA_SET + " TEXT, " +
                 AccountsColumns.SIM_SLOT_INDEX + " INTEGER, " +
-                AccountsColumns.SIM_EF_TYPE + " INTEGER" +
-                ");");
+                AccountsColumns.SIM_EF_TYPE + " INTEGER, " +
+                AccountsColumns.UNGROUPED_VISIBLE + " INTEGER NOT NULL DEFAULT 0," +
+                AccountsColumns.SHOULD_SYNC + " INTEGER NOT NULL DEFAULT 1," +
+                AccountsColumns.IS_DEFAULT + " INTEGER NOT NULL DEFAULT 0" + ");");
 
         // Note, there are two sets of the usage stat columns: LR_* and RAW_*.
         // RAW_* contain the real values, which clients can't access.  The column names start
@@ -1423,6 +1450,7 @@
                 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 " +
         ");");
@@ -1551,14 +1579,6 @@
                 AggregationExceptions.RAW_CONTACT_ID1 +
         ");");
 
-        db.execSQL("CREATE TABLE IF NOT EXISTS " + Tables.SETTINGS + " (" +
-                Settings.ACCOUNT_NAME + " STRING NOT NULL," +
-                Settings.ACCOUNT_TYPE + " STRING NOT NULL," +
-                Settings.DATA_SET + " STRING," +
-                Settings.UNGROUPED_VISIBLE + " INTEGER NOT NULL DEFAULT 0," +
-                Settings.SHOULD_SYNC + " INTEGER NOT NULL DEFAULT 1" +
-        ");");
-
         db.execSQL("CREATE TABLE " + Tables.VISIBLE_CONTACTS + " (" +
                 Contacts._ID + " INTEGER PRIMARY KEY" +
         ");");
@@ -1607,6 +1627,7 @@
         // When adding new tables, be sure to also add size-estimates in updateSqliteStats
         createContactsViews(db);
         createGroupsView(db);
+        createSettingsView(db);
         createContactsTriggers(db);
         createContactsIndexes(db, false /* we build stats table later */);
         createPresenceTables(db);
@@ -1776,14 +1797,6 @@
                 + "     WHERE " + Groups._ID + "=OLD." + Groups._ID + ";"
                 + " END");
 
-        // Update DEFAULT_FILTER table per AUTO_ADD column update, see upgradeToVersion411.
-        final String insertContactsWithoutAccount = (
-                " INSERT OR IGNORE INTO " + Tables.DEFAULT_DIRECTORY +
-                "     SELECT " + RawContacts.CONTACT_ID +
-                "     FROM " + Tables.RAW_CONTACTS +
-                "     WHERE " + RawContactsColumns.CONCRETE_ACCOUNT_ID +
-                            "=" + Clauses.LOCAL_ACCOUNT_ID + ";");
-
         final String insertContactsWithAccountNoDefaultGroup = (
                 " INSERT OR IGNORE INTO " + Tables.DEFAULT_DIRECTORY +
                 "     SELECT " + RawContacts.CONTACT_ID +
@@ -1818,7 +1831,6 @@
                 + "   AFTER UPDATE OF " + Groups.AUTO_ADD + " ON " + Tables.GROUPS
                 + " BEGIN "
                 + "   DELETE FROM " + Tables.DEFAULT_DIRECTORY + ";"
-                    + insertContactsWithoutAccount
                     + insertContactsWithAccountNoDefaultGroup
                     + insertContactsWithAccountDefaultGroup
                 + " END");
@@ -2252,7 +2264,7 @@
                 + Groups.SYSTEM_ID + ","
                 + Groups.DELETED + ","
                 + Groups.GROUP_VISIBLE + ","
-                + Groups.SHOULD_SYNC + ","
+                + GroupsColumns.CONCRETE_SHOULD_SYNC + " AS " + Groups.SHOULD_SYNC + ","
                 + Groups.AUTO_ADD + ","
                 + Groups.FAVORITES + ","
                 + Groups.GROUP_IS_READ_ONLY + ","
@@ -2274,6 +2286,53 @@
         db.execSQL("CREATE VIEW " + Views.GROUPS + " AS " + groupsSelect);
     }
 
+    private void createSettingsView(SQLiteDatabase db) {
+        db.execSQL("DROP TRIGGER IF EXISTS " + Views.SETTINGS + "_update;");
+        db.execSQL("DROP TRIGGER IF EXISTS " + Tables.ACCOUNTS + "_insert_local_account ");
+        db.execSQL("DROP VIEW IF EXISTS " + Views.SETTINGS + ";");
+
+        String settingsColumns = AccountsColumns.CONCRETE_ID
+                + " AS " + ViewSettingsColumns.ACCOUNT_ID + ","
+                + AccountsColumns.CONCRETE_ACCOUNT_NAME + " AS " + Settings.ACCOUNT_NAME + ","
+                + AccountsColumns.CONCRETE_ACCOUNT_TYPE + " AS " + Settings.ACCOUNT_TYPE + ","
+                + AccountsColumns.CONCRETE_DATA_SET + " AS " + Settings.DATA_SET + ","
+                + Settings.UNGROUPED_VISIBLE + ","
+                + Settings.SHOULD_SYNC;
+
+        String settingsSelect = "SELECT " + settingsColumns + " FROM " + Tables.ACCOUNTS;
+
+        db.execSQL("CREATE VIEW " + Views.SETTINGS + " AS " + settingsSelect);
+
+        // A trigger is used to update settings to prevent changing the other columns in the
+        // accounts table that are not settings related.
+        db.execSQL("CREATE TRIGGER " + Views.SETTINGS + "_update "
+                + "INSTEAD OF UPDATE ON " + Views.SETTINGS + " "
+                + "BEGIN UPDATE " + Tables.ACCOUNTS + " SET "
+                    + AccountsColumns.UNGROUPED_VISIBLE + " = NEW."
+                        + Settings.UNGROUPED_VISIBLE + ", "
+                    + AccountsColumns.SHOULD_SYNC + " = NEW." + Settings.SHOULD_SYNC + " "
+                + "WHERE _id = OLD." + ViewSettingsColumns.ACCOUNT_ID + "; "
+                + "END;");
+
+        // Unlike other accounts ungrouped contacts in the local account are visible by default and
+        // it is not syncable.
+        String localAccountNameSqlLiteral = MoreDatabaseUtils.sqlEscapeNullableString(
+                AccountWithDataSet.LOCAL.getAccountName());
+        String localAccountTypeSqlLiteral = MoreDatabaseUtils.sqlEscapeNullableString(
+                AccountWithDataSet.LOCAL.getAccountType());
+        db.execSQL("CREATE TRIGGER " + Tables.ACCOUNTS + "_insert_local_account "
+                + "AFTER INSERT ON " + Tables.ACCOUNTS + " "
+                + "WHEN NEW." + AccountsColumns.ACCOUNT_NAME + " IS " + localAccountNameSqlLiteral
+                + " AND NEW." + AccountsColumns.ACCOUNT_TYPE + " IS " + localAccountTypeSqlLiteral
+                + " AND NEW." + AccountsColumns.DATA_SET + " IS NULL "
+                + "BEGIN UPDATE " + Tables.ACCOUNTS + " SET "
+                + Settings.UNGROUPED_VISIBLE + " = 1, "
+                + Settings.SHOULD_SYNC + " = 0 "
+                + "WHERE " + AccountsColumns._ID + " = NEW." + AccountsColumns._ID + "; "
+                + "END;"
+        );
+    }
+
     @Override
     public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
         Log.i(TAG, "ContactsProvider cannot proceed because downgrading your database is not " +
@@ -2594,12 +2653,44 @@
             oldVersion = 1501;
         }
 
+        if (isUpgradeRequired(oldVersion, newVersion, 1600)) {
+            upgradeToVersion1600(db);
+            upgradeViewsAndTriggers = true;
+            oldVersion = 1600;
+        }
+
+        if (isUpgradeRequired(oldVersion, newVersion, 1601)) {
+            upgradeToVersion1601(db);
+            upgradeViewsAndTriggers = true;
+            oldVersion = 1601;
+        }
+
+        if (isUpgradeRequired(oldVersion, newVersion, 1602)) {
+            // 1602 was used for an upgrade that was reverted and is now a no-op. It is safe to skip
+            // it but the database version should not be reused because droidfood devices may have
+            // run the upgrade.
+            oldVersion = 1602;
+        }
+
+        if (isUpgradeRequired(oldVersion, newVersion, 1603)) {
+            upgradeToVersion1603(db);
+            upgradeViewsAndTriggers = true;
+            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.
 
         if (upgradeViewsAndTriggers) {
             createContactsViews(db);
             createGroupsView(db);
+            createSettingsView(db);
             createContactsTriggers(db);
             createContactsIndexes(db, false /* we build stats table later */);
             upgradeLegacyApiSupport = true;
@@ -3123,7 +3214,7 @@
     private void upgradeToVersion910(SQLiteDatabase db) {
         final UserManager userManager = (UserManager) mContext.getSystemService(
                 Context.USER_SERVICE);
-        final UserInfo user = userManager.getUserInfo(userManager.getUserHandle());
+        final UserInfo user = userManager.getUserInfo(userManager.getProcessUserId());
         if (user.isManagedProfile()) {
             db.execSQL("DELETE FROM calls;");
         }
@@ -3375,6 +3466,89 @@
         }
     }
 
+    private void upgradeToVersion1600(SQLiteDatabase db) {
+        db.execSQL("ALTER TABLE accounts ADD ungrouped_visible INTEGER NOT NULL DEFAULT 0;");
+        db.execSQL("ALTER TABLE accounts ADD should_sync INTEGER NOT NULL DEFAULT 1;");
+
+        ContentValues values = new ContentValues();
+        // Copy over the existing settings rows.
+        try (Cursor cursor = db.query("settings", new String[]{
+                Settings.ACCOUNT_NAME, Settings.ACCOUNT_TYPE, Settings.DATA_SET,
+                Settings.UNGROUPED_VISIBLE, Settings.SHOULD_SYNC
+        }, null, null, null, null, null)) {
+            String[] selectionArgs = new String[3];
+            while (cursor.moveToNext()) {
+                DatabaseUtils.cursorRowToContentValues(cursor, values);
+                selectionArgs[0] = values.getAsString(Settings.ACCOUNT_NAME);
+                selectionArgs[1] = values.getAsString(Settings.ACCOUNT_TYPE);
+                selectionArgs[2] = values.getAsString(Settings.DATA_SET);
+                if (values.getAsString(Settings.DATA_SET) != null) {
+                    db.update("accounts", values,
+                            "account_name = ? AND account_type = ? AND data_set = ?",
+                            selectionArgs);
+                } else {
+                    db.update("accounts", values,
+                            "account_name = ? AND account_type = ? AND data_set IS ?",
+                            selectionArgs);
+                }
+            }
+        }
+
+        db.execSQL("DROP TABLE settings;");
+
+        // If the local account exists update it's settings so that ungrouped contacts are
+        // visible by default for the local account.
+        values.clear();
+        values.put("ungrouped_visible", true);
+        values.put("should_sync", false);
+        db.update("accounts", values,
+                "account_name IS NULL AND account_type IS NULL AND data_set IS NULL", null);
+    }
+
+    private void upgradeToVersion1601(SQLiteDatabase db) {
+        try {
+            db.execSQL("ALTER TABLE accounts ADD x_is_default INTEGER NOT NULL DEFAULT 0;");
+        } catch (SQLException ignore) {
+            Log.v(TAG, "Version 1601: Columns already exist, skipping upgrade steps.");
+        }
+    }
+
+    private void upgradeToVersion1603(SQLiteDatabase db) {
+        try {
+            // Drop the view that was created in 1602 which was reverted
+            db.execSQL("DROP VIEW IF EXISTS view_raw_contacts_lookup_compat");
+        } catch (SQLException ignore) {
+            Log.v(TAG, "Version 1603: failed to remove view_raw_contacts_lookup_compat.");
+        }
+    }
+
+    @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
@@ -3632,8 +3806,6 @@
             // Tiny tables
             updateIndexStats(db, Tables.AGGREGATION_EXCEPTIONS,
                     null, "10");
-            updateIndexStats(db, Tables.SETTINGS,
-                    null, "10");
             updateIndexStats(db, Tables.PACKAGES,
                     null, "0");
             updateIndexStats(db, Tables.DIRECTORIES,
@@ -3708,7 +3880,6 @@
         db.execSQL("DELETE FROM " + Tables.NAME_LOOKUP + ";");
         db.execSQL("DELETE FROM " + Tables.GROUPS + ";");
         db.execSQL("DELETE FROM " + Tables.AGGREGATION_EXCEPTIONS + ";");
-        db.execSQL("DELETE FROM " + Tables.SETTINGS + ";");
         db.execSQL("DELETE FROM " + Tables.DIRECTORIES + ";");
         db.execSQL("DELETE FROM " + Tables.SEARCH_INDEX + ";");
         db.execSQL("DELETE FROM " + Tables.DELETED_CONTACTS + ";");
@@ -3998,7 +4169,6 @@
         } finally {
             insert.close();
         }
-
         return id;
     }
 
@@ -4066,6 +4236,59 @@
     }
 
     /**
+     * Set is_default column for the given account name and account type.
+     *
+     * @param accountName The account name to be set to default.
+     * @param accountType The account type to be set to default.
+     * @throws IllegalArgumentException if the account name or type is null.
+     */
+    public void setDefaultAccount(String accountName, String accountType) {
+        if (TextUtils.isEmpty(accountName) ^ TextUtils.isEmpty(accountType)) {
+            throw new IllegalArgumentException("Account name or type is null.");
+        }
+        SQLiteDatabase db = getWritableDatabase();
+        db.execSQL(
+                "UPDATE " + Tables.ACCOUNTS +
+                        " SET " + AccountsColumns.IS_DEFAULT + "=0" +
+                        " WHERE " + AccountsColumns.IS_DEFAULT + "=1");
+
+        Long accountId = getAccountIdOrNull(new AccountWithDataSet(accountName, accountType, null));
+        ContentValues values = new ContentValues();
+        values.put(AccountsColumns.IS_DEFAULT, 1);
+        if (accountId == null) {
+            if (!TextUtils.isEmpty(accountName)) {
+                values.put(AccountsColumns.ACCOUNT_NAME, accountName);
+            }
+            if (!TextUtils.isEmpty(accountType)) {
+                values.put(AccountsColumns.ACCOUNT_TYPE, accountType);
+            }
+            db.insert(Tables.ACCOUNTS, null, values);
+        } else {
+            db.update(Tables.ACCOUNTS, values, AccountsColumns.CONCRETE_ID + "=" + accountId, null);
+        }
+    }
+
+    /**
+     * Return the default account from Accounts table.
+     */
+    public Account getDefaultAccount() {
+        Account defaultAccount = null;
+        try (Cursor c = getReadableDatabase().rawQuery(
+                "SELECT " + AccountsColumns.ACCOUNT_NAME + ","
+                + AccountsColumns.ACCOUNT_TYPE + " FROM " + Tables.ACCOUNTS + " WHERE "
+                + AccountsColumns.IS_DEFAULT + " = 1", null)) {
+            while (c.moveToNext()) {
+                String accountName = c.getString(0);
+                String accountType = c.getString(1);
+                if (!TextUtils.isEmpty(accountName) && !TextUtils.isEmpty(accountType)) {
+                    defaultAccount = new Account(accountName, accountType);
+                }
+            }
+        }
+        return defaultAccount;
+    }
+
+    /**
      * Update {@link Contacts#IN_VISIBLE_GROUP} for all contacts.
      */
     public void updateAllVisible() {
@@ -4117,12 +4340,6 @@
                                 + GroupsColumns.CONCRETE_ACCOUNT_ID +
                         "  AND " + Groups.AUTO_ADD + " != 0" +
                         ")" +
-                ") OR EXISTS (" +
-                    "SELECT " + RawContacts._ID +
-                    " FROM " + Tables.RAW_CONTACTS +
-                    " WHERE " + RawContacts.CONTACT_ID + "=?1" +
-                    "   AND " + RawContactsColumns.CONCRETE_ACCOUNT_ID + "=" +
-                        Clauses.LOCAL_ACCOUNT_ID +
                 ")",
                 new String[] {
                     contactIdAsString,
@@ -5120,6 +5337,38 @@
         }
     }
 
+    private static void invalidateDeviceConfigCacheIfTooOld() {
+        final long now = SystemClock.elapsedRealtime();
+        if (sDeviceConfigCacheExpirationElapsedTime > now) {
+            return;
+        }
+        if (AbstractContactsProvider.VERBOSE_LOGGING) {
+            Log.v(TAG, "Invalidating device config cache");
+        }
+        sSimpleFieldMaxSizeCached = null;
+        sDeviceConfigCacheExpirationElapsedTime = now + DEVICE_CONFIG_CACHE_EXPIRATION_MS;
+    }
+
+    /**
+     * @return the max size for "simple" fields from the device config setting.
+     */
+    public static int getSimpleFieldMaxSize() {
+        invalidateDeviceConfigCacheIfTooOld();
+        final Integer cached = sSimpleFieldMaxSizeCached;
+        if (cached != null) {
+            return cached;
+        }
+        final long token = Binder.clearCallingIdentity();
+        try {
+            final int value = DeviceConfig.getInt(DeviceConfig.NAMESPACE_CONTACTS_PROVIDER,
+                    SIMPLE_FIELD_MAX_SIZE_KEY, SIMPLE_FIELD_MAX_SIZE_DEFAULT);
+            sSimpleFieldMaxSizeCached = value;
+            return value;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
     public void dump(PrintWriter pw) {
         pw.print("CountryISO: ");
         pw.println(getCurrentCountryIso());
diff --git a/src/com/android/providers/contacts/ContactsProvider2.java b/src/com/android/providers/contacts/ContactsProvider2.java
index 4b2b37a..3f61a15 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_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.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.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.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;
@@ -126,7 +133,6 @@
 import com.android.common.content.ProjectionMap;
 import com.android.common.content.SyncStateContentProviderHelper;
 import com.android.common.io.MoreCloseables;
-import com.android.i18n.phonenumbers.Phonenumber;
 import com.android.internal.util.ArrayUtils;
 import com.android.providers.contacts.ContactLookupKey.LookupKeySegment;
 import com.android.providers.contacts.ContactsDatabaseHelper.AccountsColumns;
@@ -149,12 +155,11 @@
 import com.android.providers.contacts.ContactsDatabaseHelper.Projections;
 import com.android.providers.contacts.ContactsDatabaseHelper.RawContactsColumns;
 import com.android.providers.contacts.ContactsDatabaseHelper.SearchIndexColumns;
-import com.android.providers.contacts.ContactsDatabaseHelper.SettingsColumns;
 import com.android.providers.contacts.ContactsDatabaseHelper.StatusUpdatesColumns;
 import com.android.providers.contacts.ContactsDatabaseHelper.StreamItemPhotosColumns;
 import com.android.providers.contacts.ContactsDatabaseHelper.StreamItemsColumns;
 import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
-import com.android.providers.contacts.ContactsDatabaseHelper.ViewGroupsColumns;
+import com.android.providers.contacts.ContactsDatabaseHelper.ViewSettingsColumns;
 import com.android.providers.contacts.ContactsDatabaseHelper.Views;
 import com.android.providers.contacts.SearchIndexManager.FtsQueryBuilder;
 import com.android.providers.contacts.aggregation.AbstractContactAggregator;
@@ -174,6 +179,7 @@
 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;
@@ -204,6 +210,7 @@
 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;
@@ -221,6 +228,8 @@
     private static final String WRITE_PERMISSION = "android.permission.WRITE_CONTACTS";
     private static final String MANAGE_SIM_ACCOUNTS_PERMISSION =
             "android.contacts.permission.MANAGE_SIM_ACCOUNTS";
+    private static final String SET_DEFAULT_ACCOUNT_PERMISSION =
+            "android.permission.SET_DEFAULT_ACCOUNT_FOR_CONTACTS";
 
 
     /* package */ static final String PHONEBOOK_COLLATOR_NAME = "PHONEBOOK";
@@ -249,6 +258,9 @@
     private static final int BACKGROUND_TASK_CLEANUP_PHOTOS = 10;
     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;
@@ -267,6 +279,9 @@
     /** Rate limit (in milliseconds) for photo cleanup.  Do it at most once per day. */
     private static final int PHOTO_CLEANUP_RATE_LIMIT = 24 * 60 * 60 * 1000;
 
+    /** Rate limit (in milliseconds) for dangling contacts cleanup.  Do it at most once per day. */
+    private static final int DANGLING_CONTACTS_CLEANUP_RATE_LIMIT = 24 * 60 * 60 * 1000;
+
     /** Maximum length of a phone number that can be inserted into the database */
     private static final int PHONE_NUMBER_LENGTH_LIMIT = 1000;
 
@@ -1012,15 +1027,10 @@
                                 + " THEN 1"
                                 + " ELSE MIN(" + Groups.SHOULD_SYNC + ")"
                                 + " END)"
-                            + " FROM " + Views.GROUPS
-                            + " WHERE " + ViewGroupsColumns.CONCRETE_ACCOUNT_NAME + "="
-                                    + SettingsColumns.CONCRETE_ACCOUNT_NAME
-                                + " AND " + ViewGroupsColumns.CONCRETE_ACCOUNT_TYPE + "="
-                                    + SettingsColumns.CONCRETE_ACCOUNT_TYPE
-                                + " AND ((" + ViewGroupsColumns.CONCRETE_DATA_SET + " IS NULL AND "
-                                    + SettingsColumns.CONCRETE_DATA_SET + " IS NULL) OR ("
-                                    + ViewGroupsColumns.CONCRETE_DATA_SET + "="
-                                    + SettingsColumns.CONCRETE_DATA_SET + "))))=0"
+                            + " FROM " + Tables.GROUPS
+                            + " WHERE " + GroupsColumns.CONCRETE_ACCOUNT_ID + "="
+                                + ViewSettingsColumns.CONCRETE_ACCOUNT_ID
+                        + "))=0"
                     + " THEN 1"
                     + " ELSE 0"
                     + " END)")
@@ -1431,6 +1441,7 @@
     private PostalSplitter mPostalSplitter;
 
     private ContactDirectoryManager mContactDirectoryManager;
+    private SubscriptionManager mSubscriptionManager;
 
     private boolean mIsPhoneInitialized;
     private boolean mIsPhone;
@@ -1468,6 +1479,8 @@
 
     private long mLastPhotoCleanup = 0;
 
+    private long mLastDanglingContactsCleanup = 0;
+
     private FastScrollingIndexCache mFastScrollingIndexCache;
 
     // Stats about FastScrollingIndex.
@@ -1478,6 +1491,36 @@
     // 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) {
@@ -1524,7 +1567,7 @@
                 new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().build());
 
         mFastScrollingIndexCache = FastScrollingIndexCache.getInstance(getContext());
-
+        mSubscriptionManager = getContext().getSystemService(SubscriptionManager.class);
         mContactsHelper = getDatabaseHelper();
         mDbHelper.set(mContactsHelper);
 
@@ -1534,6 +1577,12 @@
         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);
@@ -1553,12 +1602,14 @@
         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);
@@ -1566,6 +1617,7 @@
         scheduleBackgroundTask(BACKGROUND_TASK_OPEN_WRITE_ACCESS);
         scheduleBackgroundTask(BACKGROUND_TASK_CLEANUP_PHOTOS);
         scheduleBackgroundTask(BACKGROUND_TASK_CLEAN_DELETE_LOG);
+        scheduleBackgroundTask(BACKGROUND_TASK_CLEANUP_DANGLING_CONTACTS);
 
         ContactsPackageMonitor.start(getContext());
 
@@ -1681,6 +1733,7 @@
         switchToContactMode();
         switch (task) {
             case BACKGROUND_TASK_INITIALIZE: {
+                mContactsHelper.updatePhoneAccountHandleMigrationPendingStatus();
                 initForDefaultLocale();
                 mReadAccessLatch.countDown();
                 mReadAccessLatch = null;
@@ -1716,6 +1769,32 @@
                 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;
@@ -1771,6 +1850,17 @@
                 DeletedContactsTableUtil.deleteOldLogs(db);
                 break;
             }
+
+            case BACKGROUND_TASK_CLEANUP_DANGLING_CONTACTS: {
+                // Check rate limit.
+                long now = System.currentTimeMillis();
+                if (now - mLastDanglingContactsCleanup > DANGLING_CONTACTS_CLEANUP_RATE_LIMIT) {
+                    mLastDanglingContactsCleanup = now;
+
+                    cleanupDanglingContacts();
+                }
+                break;
+            }
         }
     }
 
@@ -1998,6 +2088,31 @@
         }
     }
 
+    @VisibleForTesting
+    protected void cleanupDanglingContacts() {
+      // Dangling contacts are the contacts whose _id doesn't have a raw_contact_id linked with.
+      String danglingContactsSelection =
+          Contacts._ID
+              + " NOT IN (SELECT "
+              + RawContacts.CONTACT_ID
+              + " FROM "
+              + Tables.RAW_CONTACTS
+              + " WHERE "
+              + RawContacts.DELETED
+              + " = 0)";
+      int danglingContactsCount =
+          mDbHelper
+              .get()
+              .getWritableDatabase()
+              .delete(Tables.CONTACTS, danglingContactsSelection, /* selectionArgs= */ null);
+      LogFields.Builder logBuilder =
+          LogFields.Builder.aLogFields()
+              .setTaskType(LogUtils.TaskType.DANGLING_CONTACTS_CLEANUP_TASK)
+              .setResultCount(danglingContactsCount);
+      LogUtils.log(logBuilder.build());
+      Log.v(TAG, danglingContactsCount + " Dangling Contacts have been cleaned up.");
+    }
+
     @Override
     public ContactsDatabaseHelper newDatabaseHelper(final Context context) {
         return ContactsDatabaseHelper.getInstance(context);
@@ -2353,10 +2468,59 @@
             response.putParcelableList(SimContacts.KEY_SIM_ACCOUNTS, simAccounts);
 
             return response;
+        } else if (Settings.QUERY_DEFAULT_ACCOUNT_METHOD.equals(method)) {
+            ContactsPermissions.enforceCallingOrSelfPermission(getContext(), READ_PERMISSION);
+            final Bundle response = new Bundle();
+
+            final Account defaultAccount = mDbHelper.get().getDefaultAccount();
+            response.putParcelable(Settings.KEY_DEFAULT_ACCOUNT, defaultAccount);
+
+            return response;
+        } else if (Settings.SET_DEFAULT_ACCOUNT_METHOD.equals(method)) {
+            return setDefaultAccountSetting(extras);
         }
         return null;
     }
 
+    private Bundle setDefaultAccountSetting(Bundle extras) {
+        ContactsPermissions.enforceCallingOrSelfPermission(getContext(),
+                SET_DEFAULT_ACCOUNT_PERMISSION);
+        final String accountName = extras.getString(Settings.ACCOUNT_NAME);
+        final String accountType = extras.getString(Settings.ACCOUNT_TYPE);
+        final String dataSet = extras.getString(Settings.DATA_SET);
+
+        if (TextUtils.isEmpty(accountName) ^ TextUtils.isEmpty(accountType)) {
+            throw new IllegalArgumentException(
+                    "Must specify both or neither of ACCOUNT_NAME and ACCOUNT_TYPE");
+        }
+        if (!TextUtils.isEmpty(dataSet)) {
+            throw new IllegalArgumentException(
+                    "Cannot set default account with non-null data set.");
+        }
+
+        AccountWithDataSet accountWithDataSet = new AccountWithDataSet(
+                accountName, accountType, dataSet);
+        Account[] systemAccounts = AccountManager.get(getContext()).getAccounts();
+        List<SimAccount> simAccounts = mDbHelper.get().getAllSimAccounts();
+        if (!accountWithDataSet.isLocalAccount()
+                && !accountWithDataSet.inSystemAccounts(systemAccounts)
+                && !accountWithDataSet.inSimAccounts(simAccounts)) {
+            throw new IllegalArgumentException(
+                    "Cannot set default account for invalid accounts.");
+        }
+
+        final Bundle response = new Bundle();
+        final SQLiteDatabase db = mDbHelper.get().getWritableDatabase();
+        db.beginTransaction();
+        try {
+            mDbHelper.get().setDefaultAccount(accountName, accountType);
+            db.setTransactionSuccessful();
+        } finally {
+            db.endTransaction();
+        }
+        return response;
+    }
+
     /**
      * Pre-authorizes the given URI, adding an expiring permission token to it and placing that
      * in our map of pre-authorized URIs.
@@ -2729,9 +2893,9 @@
             }
 
             case SETTINGS: {
-                id = insertSettings(values);
                 mSyncToNetwork |= !callerIsSyncAdapter;
-                break;
+                // Settings rows are referenced by the account instead of their ID.
+                return insertSettings(uri, values);
             }
 
             case STATUS_UPDATES:
@@ -3289,55 +3453,35 @@
         return groupId;
     }
 
-    private long insertSettings(ContentValues values) {
-        // Before inserting, ensure that no settings record already exists for the
-        // values being inserted (this used to be enforced by a primary key, but that no
-        // longer works with the nullable data_set field added).
-        String accountName = values.getAsString(Settings.ACCOUNT_NAME);
-        String accountType = values.getAsString(Settings.ACCOUNT_TYPE);
-        String dataSet = values.getAsString(Settings.DATA_SET);
-        Uri.Builder settingsUri = Settings.CONTENT_URI.buildUpon();
-        if (accountName != null) {
-            settingsUri.appendQueryParameter(Settings.ACCOUNT_NAME, accountName);
-        }
-        if (accountType != null) {
-            settingsUri.appendQueryParameter(Settings.ACCOUNT_TYPE, accountType);
-        }
-        if (dataSet != null) {
-            settingsUri.appendQueryParameter(Settings.DATA_SET, dataSet);
-        }
-        Cursor c = queryLocal(settingsUri.build(), null, null, null, null, 0, null);
-        try {
-            if (c.getCount() > 0) {
-                // If a record was found, replace it with the new values.
-                String selection = null;
-                String[] selectionArgs = null;
-                if (accountName != null && accountType != null) {
-                    selection = Settings.ACCOUNT_NAME + "=? AND " + Settings.ACCOUNT_TYPE + "=?";
-                    if (dataSet == null) {
-                        selection += " AND " + Settings.DATA_SET + " IS NULL";
-                        selectionArgs = new String[] {accountName, accountType};
-                    } else {
-                        selection += " AND " + Settings.DATA_SET + "=?";
-                        selectionArgs = new String[] {accountName, accountType, dataSet};
-                    }
-                }
-                return updateSettings(values, selection, selectionArgs);
-            }
-        } finally {
-            c.close();
-        }
+    private Uri insertSettings(Uri uri, ContentValues values) {
+        final AccountWithDataSet account = resolveAccountWithDataSet(uri, values);
 
+        // Note that the following check means the local account settings cannot be created with
+        // an insert because resolveAccountWithDataSet returns null for it. However, the settings
+        // for it can be updated once it is created automatically by a raw contact or group insert.
+        if (account == null) {
+            return null;
+        }
+        final ContactsDatabaseHelper dbHelper = mDbHelper.get();
         final SQLiteDatabase db = mDbHelper.get().getWritableDatabase();
 
-        // If we didn't find a duplicate, we're fine to insert.
-        final long id = db.insert(Tables.SETTINGS, null, values);
+        long accountId = dbHelper.getOrCreateAccountIdInTransaction(account);
+        mSelectionArgs1[0] = String.valueOf(accountId);
+
+        int count = db.update(Views.SETTINGS, values,
+                ViewSettingsColumns.ACCOUNT_ID + "= ?", mSelectionArgs1);
 
         if (values.containsKey(Settings.UNGROUPED_VISIBLE)) {
             mVisibleTouched = true;
         }
 
-        return id;
+        Uri.Builder builder = Settings.CONTENT_URI.buildUpon()
+                .appendQueryParameter(Settings.ACCOUNT_NAME, account.getAccountName())
+                .appendQueryParameter(Settings.ACCOUNT_TYPE, account.getAccountType());
+        if (account.getDataSet() != null) {
+            builder.appendQueryParameter(Settings.DATA_SET, account.getDataSet());
+        }
+        return builder.build();
     }
 
     /**
@@ -3764,7 +3908,7 @@
 
             case SETTINGS: {
                 mSyncToNetwork |= !callerIsSyncAdapter;
-                return deleteSettings(appendAccountToSelection(uri, selection), selectionArgs);
+                return deleteSettings(appendAccountIdToSelection(uri, selection), selectionArgs);
             }
 
             case STATUS_UPDATES:
@@ -3841,9 +3985,21 @@
         }
     }
 
-    private int deleteSettings(String selection, String[] selectionArgs) {
+    private int deleteSettings(String initialSelection, String[] selectionArgs) {
         final SQLiteDatabase db = mDbHelper.get().getWritableDatabase();
-        final int count = db.delete(Tables.SETTINGS, selection, selectionArgs);
+
+        int count = 0;
+        final String selection = DbQueryUtils.concatenateClauses(
+                initialSelection, Clauses.DELETABLE_SETTINGS);
+        try (Cursor cursor = db.query(Views.SETTINGS,
+                new String[] { ViewSettingsColumns.ACCOUNT_ID },
+                selection, selectionArgs, null, null, null)) {
+            while (cursor.moveToNext()) {
+                mSelectionArgs1[0] = cursor.getString(0);
+                db.delete(Tables.ACCOUNTS, AccountsColumns._ID + "=?", mSelectionArgs1);
+                count++;
+            }
+        }
         mVisibleTouched = true;
         return count;
     }
@@ -4512,7 +4668,20 @@
 
     private int updateSettings(ContentValues values, String selection, String[] selectionArgs) {
         final SQLiteDatabase db = mDbHelper.get().getWritableDatabase();
-        final int count = db.update(Tables.SETTINGS, values, selection, selectionArgs);
+
+        int count = 0;
+        // We have to query for the count because the update is using a trigger and triggers
+        // don't return a count of modified rows.
+        try (Cursor cursor = db.query(Views.SETTINGS,
+                new String[] { "COUNT(*)" },
+                selection, selectionArgs, null, null, null)) {
+            if (cursor.moveToFirst()) {
+                count = cursor.getInt(0);
+            }
+        }
+        if (count > 0) {
+            db.update(Views.SETTINGS, values, selection, selectionArgs);
+        }
         if (values.containsKey(Settings.UNGROUPED_VISIBLE)) {
             mVisibleTouched = true;
         }
@@ -5293,9 +5462,7 @@
                 }
             }
 
-            // Second, remove stale rows from Tables.SETTINGS and Tables.DIRECTORIES
-            removeStaleAccountRows(
-                    Tables.SETTINGS, Settings.ACCOUNT_NAME, Settings.ACCOUNT_TYPE, systemAccounts);
+            // Second, remove stale rows from Tables.DIRECTORIES
             removeStaleAccountRows(Tables.DIRECTORIES, Directory.ACCOUNT_NAME,
                     Directory.ACCOUNT_TYPE, systemAccounts);
 
@@ -5610,8 +5777,8 @@
                 Log.v(TAG, "Making authority " + directoryAuthority
                         + " visible to UID " + callingUid);
             }
-            getContext().getPackageManager().grantImplicitAccess(
-                    callingUid, directoryAuthority);
+            getContext().getPackageManager()
+                    .makeProviderVisible(callingUid, directoryAuthority);
         }
 
         // Load the cursor contents into a memory cursor (backed by a cursor window) and close the
@@ -6877,9 +7044,9 @@
             }
 
             case SETTINGS: {
-                qb.setTables(Tables.SETTINGS);
+                qb.setTables(Views.SETTINGS);
                 qb.setProjectionMap(sSettingsProjectionMap);
-                appendAccountFromParameter(qb, uri);
+                appendAccountIdFromParameter(qb, uri);
 
                 // When requesting specific columns, this query requires
                 // late-binding of the GroupMembership MIME-type.
@@ -9835,6 +10002,15 @@
         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) {
@@ -9921,6 +10097,12 @@
         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/DataRowHandler.java b/src/com/android/providers/contacts/DataRowHandler.java
index a82ce34..b1295c1 100644
--- a/src/com/android/providers/contacts/DataRowHandler.java
+++ b/src/com/android/providers/contacts/DataRowHandler.java
@@ -27,6 +27,9 @@
 import android.provider.ContactsContract.CommonDataKinds.StructuredName;
 import android.provider.ContactsContract.Data;
 import android.text.TextUtils;
+import android.util.Log;
+import android.util.LogWriter;
+
 import com.android.providers.contacts.ContactsDatabaseHelper.DataColumns;
 import com.android.providers.contacts.ContactsDatabaseHelper.MimetypesColumns;
 import com.android.providers.contacts.ContactsDatabaseHelper.PresenceColumns;
@@ -37,6 +40,7 @@
  * Handles inserts and update for a specific Data type.
  */
 public abstract class DataRowHandler {
+    private static final String TAG = AbstractContactsProvider.TAG;
 
     private static final String[] HASH_INPUT_COLUMNS = new String[] {
             Data.DATA1, Data.DATA2};
@@ -439,4 +443,14 @@
         }
         return false;
     }
+
+    protected static void applySimpleFieldMaxSize(ContentValues cv, String column) {
+        final int maxSize = ContactsDatabaseHelper.getSimpleFieldMaxSize();
+        String v = cv.getAsString(column);
+        if (v == null || v.length() <= maxSize) {
+            return;
+        }
+        Log.w(TAG, "Truncating field " + column + ": length=" + v.length() + " max=" + maxSize);
+        cv.put(column, v.substring(0, maxSize));
+    }
 }
diff --git a/src/com/android/providers/contacts/DataRowHandlerForEmail.java b/src/com/android/providers/contacts/DataRowHandlerForEmail.java
index 539c959..76196df 100644
--- a/src/com/android/providers/contacts/DataRowHandlerForEmail.java
+++ b/src/com/android/providers/contacts/DataRowHandlerForEmail.java
@@ -33,9 +33,15 @@
         super(context, dbHelper, aggregator, Email.CONTENT_ITEM_TYPE, Email.TYPE, Email.LABEL);
     }
 
+    private void applySimpleFieldMaxSize(ContentValues cv) {
+        applySimpleFieldMaxSize(cv, Email.DATA);
+    }
+
     @Override
     public long insert(SQLiteDatabase db, TransactionContext txContext, long rawContactId,
             ContentValues values) {
+        applySimpleFieldMaxSize(values);
+
         String email = values.getAsString(Email.DATA);
 
         long dataId = super.insert(db, txContext, rawContactId, values);
@@ -51,6 +57,7 @@
     @Override
     public boolean update(SQLiteDatabase db, TransactionContext txContext, ContentValues values,
             Cursor c, boolean callerIsSyncAdapter) {
+        applySimpleFieldMaxSize(values);
         if (!super.update(db, txContext, values, c, callerIsSyncAdapter)) {
             return false;
         }
diff --git a/src/com/android/providers/contacts/DataRowHandlerForPhoneNumber.java b/src/com/android/providers/contacts/DataRowHandlerForPhoneNumber.java
index 052252e..85e7658 100644
--- a/src/com/android/providers/contacts/DataRowHandlerForPhoneNumber.java
+++ b/src/com/android/providers/contacts/DataRowHandlerForPhoneNumber.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.database.Cursor;
 import android.database.sqlite.SQLiteDatabase;
+import android.provider.ContactsContract.CommonDataKinds.Email;
 import android.provider.ContactsContract.CommonDataKinds.Phone;
 import android.telephony.PhoneNumberUtils;
 import android.text.TextUtils;
@@ -38,9 +39,15 @@
         super(context, dbHelper, aggregator, Phone.CONTENT_ITEM_TYPE, Phone.TYPE, Phone.LABEL);
     }
 
+    private void applySimpleFieldMaxSize(ContentValues cv) {
+        applySimpleFieldMaxSize(cv, Phone.NUMBER);
+        applySimpleFieldMaxSize(cv, Phone.NORMALIZED_NUMBER);
+    }
+
     @Override
     public long insert(SQLiteDatabase db, TransactionContext txContext, long rawContactId,
             ContentValues values) {
+        applySimpleFieldMaxSize(values);
         fillNormalizedNumber(values);
 
         final long dataId = super.insert(db, txContext, rawContactId, values);
@@ -59,6 +66,7 @@
     @Override
     public boolean update(SQLiteDatabase db, TransactionContext txContext, ContentValues values,
             Cursor c, boolean callerIsSyncAdapter) {
+        applySimpleFieldMaxSize(values);
         fillNormalizedNumber(values);
 
         if (!super.update(db, txContext, values, c, callerIsSyncAdapter)) {
diff --git a/src/com/android/providers/contacts/DataRowHandlerForStructuredName.java b/src/com/android/providers/contacts/DataRowHandlerForStructuredName.java
index 044e972..11c24a7 100644
--- a/src/com/android/providers/contacts/DataRowHandlerForStructuredName.java
+++ b/src/com/android/providers/contacts/DataRowHandlerForStructuredName.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.database.Cursor;
 import android.database.sqlite.SQLiteDatabase;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
 import android.provider.ContactsContract.CommonDataKinds.StructuredName;
 import android.provider.ContactsContract.FullNameStyle;
 import android.provider.ContactsContract.PhoneticNameStyle;
@@ -43,9 +44,23 @@
         mNameLookupBuilder = nameLookupBuilder;
     }
 
+    private void applySimpleFieldMaxSize(ContentValues cv) {
+        applySimpleFieldMaxSize(cv, StructuredName.DISPLAY_NAME);
+        applySimpleFieldMaxSize(cv, StructuredName.GIVEN_NAME);
+        applySimpleFieldMaxSize(cv, StructuredName.FAMILY_NAME);
+        applySimpleFieldMaxSize(cv, StructuredName.PREFIX);
+        applySimpleFieldMaxSize(cv, StructuredName.MIDDLE_NAME);
+        applySimpleFieldMaxSize(cv, StructuredName.SUFFIX);
+
+        applySimpleFieldMaxSize(cv, StructuredName.PHONETIC_GIVEN_NAME);
+        applySimpleFieldMaxSize(cv, StructuredName.PHONETIC_MIDDLE_NAME);
+        applySimpleFieldMaxSize(cv, StructuredName.PHONETIC_FAMILY_NAME);
+    }
+
     @Override
     public long insert(SQLiteDatabase db, TransactionContext txContext, long rawContactId,
             ContentValues values) {
+        applySimpleFieldMaxSize(values);
         fixStructuredNameComponents(values, values);
 
         long dataId = super.insert(db, txContext, rawContactId, values);
@@ -64,6 +79,7 @@
     @Override
     public boolean update(SQLiteDatabase db, TransactionContext txContext, ContentValues values,
             Cursor c, boolean callerIsSyncAdapter) {
+        applySimpleFieldMaxSize(values);
         final long dataId = c.getLong(DataUpdateQuery._ID);
         final long rawContactId = c.getLong(DataUpdateQuery.RAW_CONTACT_ID);
 
diff --git a/src/com/android/providers/contacts/LegacyApiSupport.java b/src/com/android/providers/contacts/LegacyApiSupport.java
index c111cf3..3120fae 100644
--- a/src/com/android/providers/contacts/LegacyApiSupport.java
+++ b/src/com/android/providers/contacts/LegacyApiSupport.java
@@ -65,6 +65,8 @@
 import com.android.providers.contacts.ContactsDatabaseHelper.RawContactsColumns;
 import com.android.providers.contacts.ContactsDatabaseHelper.StatusUpdatesColumns;
 import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
+import com.android.providers.contacts.ContactsDatabaseHelper.Views;
+import com.android.providers.contacts.database.MoreDatabaseUtils;
 
 import java.util.Locale;
 
@@ -1246,7 +1248,7 @@
                     + ContactsContract.Settings.ACCOUNT_NAME + ","
                     + ContactsContract.Settings.ACCOUNT_TYPE + ","
                     + ContactsContract.Settings.SHOULD_SYNC +
-            " FROM " + Tables.SETTINGS + " LEFT OUTER JOIN " + LegacyTables.SETTINGS +
+            " FROM " + Views.SETTINGS + " LEFT OUTER JOIN " + LegacyTables.SETTINGS +
             " ON (" + ContactsContract.Settings.ACCOUNT_NAME + "="
                               + android.provider.Contacts.Settings._SYNC_ACCOUNT +
                       " AND " + ContactsContract.Settings.ACCOUNT_TYPE + "="
@@ -1859,8 +1861,12 @@
             sb.append(" AND " + RawContacts.ACCOUNT_TYPE + "=");
             DatabaseUtils.appendEscapedSQLString(sb, mAccount.type);
         } else {
-            sb.append(RawContacts.ACCOUNT_NAME + " IS NULL" +
-                    " AND " + RawContacts.ACCOUNT_TYPE + " IS NULL");
+            sb.append(RawContacts.ACCOUNT_NAME + " IS ");
+            MoreDatabaseUtils.appendEscapedSQLStringOrLiteralNull(
+                    sb, AccountWithDataSet.LOCAL.getAccountName());
+            sb.append(" AND ").append(RawContacts.ACCOUNT_TYPE + " IS ");
+            MoreDatabaseUtils.appendEscapedSQLStringOrLiteralNull(
+                    sb, AccountWithDataSet.LOCAL.getAccountType());
         }
     }
 
@@ -1877,8 +1883,12 @@
             sb.append(" AND " + Groups.ACCOUNT_TYPE + "=");
             DatabaseUtils.appendEscapedSQLString(sb, mAccount.type);
         } else {
-            sb.append(Groups.ACCOUNT_NAME + " IS NULL" +
-                    " AND " + Groups.ACCOUNT_TYPE + " IS NULL");
+            sb.append(Groups.ACCOUNT_NAME + " IS ");
+            MoreDatabaseUtils.appendEscapedSQLStringOrLiteralNull(
+                    sb, AccountWithDataSet.LOCAL.getAccountName());
+            sb.append(" AND " + Groups.ACCOUNT_TYPE + " IS ");
+            MoreDatabaseUtils.appendEscapedSQLStringOrLiteralNull(
+                    sb, AccountWithDataSet.LOCAL.getAccountType());
         }
     }
 
diff --git a/src/com/android/providers/contacts/PhoneAccountRegistrationReceiver.java b/src/com/android/providers/contacts/PhoneAccountRegistrationReceiver.java
deleted file mode 100644
index 8a68889..0000000
--- 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/database/MoreDatabaseUtils.java b/src/com/android/providers/contacts/database/MoreDatabaseUtils.java
index 3dadb3f..61ffb9f 100644
--- a/src/com/android/providers/contacts/database/MoreDatabaseUtils.java
+++ b/src/com/android/providers/contacts/database/MoreDatabaseUtils.java
@@ -16,11 +16,13 @@
 
 package com.android.providers.contacts.database;
 
-import com.android.providers.contacts.util.NeededForTesting;
-
+import android.annotation.Nullable;
 import android.database.Cursor;
+import android.database.DatabaseUtils;
 import android.util.Log;
 
+import com.android.providers.contacts.util.NeededForTesting;
+
 /**
  * Static methods for database operations.
  */
@@ -108,4 +110,30 @@
             Log.d(logTag, sb.toString());
         }
     }
+
+    /**
+     * Same as {@link DatabaseUtils#sqlEscapeString(String)} but handles a null argument by
+     * returning the string "NULL".
+     *
+     * @return the SQL-escaped string or "NULL" if the argument is null.
+     */
+    @Nullable
+    public static String sqlEscapeNullableString(@Nullable String s) {
+        return s == null
+                ? "NULL"
+                : DatabaseUtils.sqlEscapeString(s);
+    }
+
+    /**
+     * Same as {@link DatabaseUtils#appendEscapedSQLString(StringBuilder, String)} but handles a
+     * null argument by appending the literal string "NULL".
+     */
+    @Nullable
+    public static void appendEscapedSQLStringOrLiteralNull(StringBuilder sb, @Nullable String s) {
+        if (s == null) {
+            sb.append("NULL");
+        } else {
+            DatabaseUtils.appendEscapedSQLString(sb, s);
+        }
+    }
 }
diff --git a/src/com/android/providers/contacts/util/LogFields.java b/src/com/android/providers/contacts/util/LogFields.java
index f4a60fe..fc05c84 100644
--- a/src/com/android/providers/contacts/util/LogFields.java
+++ b/src/com/android/providers/contacts/util/LogFields.java
@@ -24,6 +24,9 @@
 
     private final int uriType;
 
+    // The type is from LogUtils.TaskType
+    private final int taskType;
+
     private final boolean callerIsSyncAdapter;
 
     private final long startNanos;
@@ -34,9 +37,11 @@
 
     private int resultCount;
 
-    public LogFields(int apiType, int uriType, boolean callerIsSyncAdapter, long startNanos) {
+    public LogFields(
+            int apiType, int uriType, int taskType, boolean callerIsSyncAdapter, long startNanos) {
         this.apiType = apiType;
         this.uriType = uriType;
+        this.taskType = taskType;
         this.callerIsSyncAdapter = callerIsSyncAdapter;
         this.startNanos = startNanos;
     }
@@ -49,6 +54,10 @@
         return uriType;
     }
 
+    public int getTaskType() {
+        return taskType;
+    }
+
     public boolean isCallerIsSyncAdapter() {
         return callerIsSyncAdapter;
     }
@@ -72,6 +81,7 @@
     public static final class Builder {
         private int apiType;
         private int uriType;
+        private int taskType;
         private boolean callerIsSyncAdapter;
         private long startNanos;
         private Exception exception;
@@ -95,6 +105,11 @@
             return this;
         }
 
+        public Builder setTaskType(int taskType) {
+            this.taskType = taskType;
+            return this;
+        }
+
         public Builder setCallerIsSyncAdapter(boolean callerIsSyncAdapter) {
             this.callerIsSyncAdapter = callerIsSyncAdapter;
             return this;
@@ -121,7 +136,8 @@
         }
 
         public LogFields build() {
-            LogFields logFields = new LogFields(apiType, uriType, callerIsSyncAdapter, startNanos);
+            LogFields logFields =
+                    new LogFields(apiType, uriType, taskType, callerIsSyncAdapter, startNanos);
             logFields.resultCount = this.resultCount;
             logFields.exception = this.exception;
             logFields.resultUri = this.resultUri;
diff --git a/src/com/android/providers/contacts/util/LogUtils.java b/src/com/android/providers/contacts/util/LogUtils.java
index 9e12325..368411c 100644
--- a/src/com/android/providers/contacts/util/LogUtils.java
+++ b/src/com/android/providers/contacts/util/LogUtils.java
@@ -39,6 +39,12 @@
         int DELETE = 4;
     }
 
+    // Keep in sync with ContactsProviderStatus#TaskType in
+    // frameworks/proto_logging/stats/atoms.proto file.
+    public interface TaskType {
+        int DANGLING_CONTACTS_CLEANUP_TASK = 1;
+    }
+
     // Keep in sync with ContactsProviderStatus#CallerType in
     // frameworks/proto_logging/stats/atoms.proto file.
     public interface CallerType {
@@ -55,6 +61,7 @@
                 .writeInt(logFields.getUriType())
                 .writeInt(getCallerType(logFields.isCallerIsSyncAdapter()))
                 .writeInt(getResultType(logFields.getException()))
+                .writeInt(logFields.getTaskType())
                 .writeInt(logFields.getResultCount())
                 .writeLong(getLatencyMicros(logFields.getStartNanos()))
                 .usePooledBuffer()
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 0000000..b578314
--- /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/src/com/android/providers/contacts/util/UserUtils.java b/src/com/android/providers/contacts/util/UserUtils.java
index 3edbb45..31ea41a 100644
--- a/src/com/android/providers/contacts/util/UserUtils.java
+++ b/src/com/android/providers/contacts/util/UserUtils.java
@@ -20,7 +20,6 @@
 import android.app.admin.DevicePolicyManager;
 import android.content.Context;
 import android.content.pm.UserInfo;
-import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.Log;
 
@@ -41,7 +40,7 @@
     }
 
     public static int getCurrentUserHandle(Context context) {
-        return getUserManager(context).getUserHandle();
+        return getUserManager(context).getProcessUserId();
     }
 
     /**
@@ -52,7 +51,7 @@
      */
     private static UserInfo getCorpUserInfo(Context context) {
         final UserManager um = getUserManager(context);
-        final int myUser = um.getUserHandle();
+        final int myUser = um.getProcessUserId();
 
         // Check each user.
         for (UserInfo ui : um.getUsers()) {
diff --git a/test_common/Android.bp b/test_common/Android.bp
index 207b1db..1ec6b9a 100644
--- a/test_common/Android.bp
+++ b/test_common/Android.bp
@@ -14,13 +14,7 @@
 
 package {
     // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "packages_providers_ContactsProvider_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    default_applicable_licenses: [
-        "packages_providers_ContactsProvider_license",
-    ],
+    default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
 java_library {
diff --git a/test_common/src/com/android/providers/contacts/testutil/TestUtil.java b/test_common/src/com/android/providers/contacts/testutil/TestUtil.java
index 6c8c689..b9203d7 100644
--- a/test_common/src/com/android/providers/contacts/testutil/TestUtil.java
+++ b/test_common/src/com/android/providers/contacts/testutil/TestUtil.java
@@ -59,7 +59,7 @@
         return uri.buildUpon()
                 .appendQueryParameter(RawContacts.ACCOUNT_NAME, accountName)
                 .appendQueryParameter(RawContacts.ACCOUNT_TYPE, accountType)
-                .appendQueryParameter(RawContacts.DATA_SET, dataSet)
+                .appendQueryParameter(RawContacts.DATA_SET, dataSet != null ? dataSet : "")
                 .build();
     }
 }
diff --git a/tests/Android.bp b/tests/Android.bp
index 6fc1d17..beb2d31 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -1,12 +1,6 @@
 package {
     // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "packages_providers_ContactsProvider_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    default_applicable_licenses: [
-        "packages_providers_ContactsProvider_license",
-    ],
+    default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
 android_test {
diff --git a/tests/assets/phoneAccountHandleMigration/calllog_oldversion.db b/tests/assets/phoneAccountHandleMigration/calllog_oldversion.db
new file mode 100644
index 0000000..e00b985
--- /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 0000000..128da64
--- /dev/null
+++ b/tests/assets/phoneAccountHandleMigration/contacts2_oldversion.db
Binary files differ
diff --git a/tests/assets/upgradeTest/pre_upgrade1600.sql b/tests/assets/upgradeTest/pre_upgrade1600.sql
new file mode 100644
index 0000000..f3aacd0
--- /dev/null
+++ b/tests/assets/upgradeTest/pre_upgrade1600.sql
@@ -0,0 +1,20 @@
+DELETE FROM accounts;
+DELETE FROM settings;
+DELETE FROM contacts;
+DELETE FROM raw_contacts;
+DELETE FROM data;
+DELETE FROM data_usage_stat;
+
+--CREATE TABLE accounts (_id INTEGER PRIMARY KEY AUTOINCREMENT,account_name TEXT, account_type TEXT, data_set TEXT);
+
+INSERT INTO "accounts" VALUES(1,NULL,NULL,NULL);
+INSERT INTO "accounts" VALUES(2,"visible","type1",NULL);
+INSERT INTO "accounts" VALUES(3,"visible","type1","ds_not_visible");
+INSERT INTO "accounts" VALUES(4,"not_syncable","type1",NULL);
+INSERT INTO "accounts" VALUES(5,"no_settings","type2",NULL);
+
+--CREATE TABLE settings (account_name STRING NOT NULL,account_type STRING NOT NULL,data_set STRING,ungrouped_visible INTEGER NOT NULL DEFAULT 0,should_sync INTEGER NOT NULL DEFAULT 1);
+
+INSERT INTO "settings" VALUES ("visible","type1",NULL,1,1)
+INSERT INTO "settings" VALUES ("visible","type1","ds_not_visible",0,1)
+INSERT INTO "settings" VALUES ("not_syncable","type1",NULL,0,0)
diff --git a/tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java b/tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java
index 816d10d..54984d2 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.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.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 @@
     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 @@
     @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();
@@ -219,12 +230,12 @@
         return ContentUris.parseId(mResolver.insert(uri, values));
     }
 
-    protected void createSettings(Account account, String shouldSync, String ungroupedVisible) {
-        createSettings(new AccountWithDataSet(account.name, account.type, null),
+    protected Uri createSettings(Account account, String shouldSync, String ungroupedVisible) {
+        return createSettings(new AccountWithDataSet(account.name, account.type, null),
                 shouldSync, ungroupedVisible);
     }
 
-    protected void createSettings(AccountWithDataSet account, String shouldSync,
+    protected Uri createSettings(AccountWithDataSet account, String shouldSync,
             String ungroupedVisible) {
         ContentValues values = new ContentValues();
         values.put(Settings.ACCOUNT_NAME, account.getAccountName());
@@ -234,7 +245,7 @@
         }
         values.put(Settings.SHOULD_SYNC, shouldSync);
         values.put(Settings.UNGROUPED_VISIBLE, ungroupedVisible);
-        mResolver.insert(Settings.CONTENT_URI, values);
+        return mResolver.insert(Settings.CONTENT_URI, values);
     }
 
     protected Uri insertOrganization(long rawContactId, ContentValues values) {
diff --git a/tests/src/com/android/providers/contacts/CallLogMigrationTest.java b/tests/src/com/android/providers/contacts/CallLogMigrationTest.java
index 767b62f..d1e8003 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 @@
 
 @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 @@
         }
     }
 
+    /** 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 @@
             // 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 @@
             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 @@
                     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 b45ccaf..c755582 100644
--- a/tests/src/com/android/providers/contacts/CallLogProviderTest.java
+++ b/tests/src/com/android/providers/contacts/CallLogProviderTest.java
@@ -26,13 +26,18 @@
 import com.android.providers.contacts.testutil.CommonDatabaseUtils;
 import com.android.providers.contacts.util.ContactsPermissions;
 import com.android.providers.contacts.util.FileUtilities;
+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;
@@ -40,6 +45,8 @@
 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 org.junit.Assert;
@@ -49,6 +56,8 @@
 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}.
@@ -72,7 +81,7 @@
             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;
 
@@ -98,6 +107,7 @@
     private int mOldMinMatch;
 
     private CallLogProviderTestable mCallLogProvider;
+    private BroadcastReceiver mBroadcastReceiver;
 
     @Override
     protected Class<? extends ContentProvider> getProviderClass() {
@@ -113,6 +123,7 @@
     protected void setUp() throws Exception {
         super.setUp();
         mCallLogProvider = addProvider(CallLogProviderTestable.class, CallLog.AUTHORITY);
+        mBroadcastReceiver = mCallLogProvider.getBroadcastReceiverForTest();
         mOldMinMatch = mCallLogProvider.getMinMatchForTest();
         mCallLogProvider.setMinMatchForTest(MIN_MATCH);
     }
@@ -126,6 +137,211 @@
         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();
@@ -230,7 +446,7 @@
         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());
@@ -254,6 +470,7 @@
         // 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/ContactLookupKeyTest.java b/tests/src/com/android/providers/contacts/ContactLookupKeyTest.java
index 047e8ea..eae49b7 100644
--- a/tests/src/com/android/providers/contacts/ContactLookupKeyTest.java
+++ b/tests/src/com/android/providers/contacts/ContactLookupKeyTest.java
@@ -40,7 +40,14 @@
 @MediumTest
 public class ContactLookupKeyTest extends BaseContactsProvider2Test {
 
+    private static final int LOCAL_ACCOUNT_HASH_CODE =
+            ContactLookupKey.getAccountHashCode(
+                    AccountWithDataSet.LOCAL.getAccountType(),
+                    AccountWithDataSet.LOCAL.getAccountName());
+
     public void testLookupKeyUsingDisplayNameAndNoAccount() {
+        int accountHashCode = LOCAL_ACCOUNT_HASH_CODE;
+
         long rawContactId1 = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe");
         long rawContactId2 = RawContactUtil.createRawContactWithName(mResolver, "johndoe", null);
         setAggregationException(
@@ -48,8 +55,8 @@
 
         // Normalized display name
         String normalizedName = NameNormalizer.normalize("johndoe");
-        String expectedLookupKey = "0r" + rawContactId1 + "-" + normalizedName + ".0r"
-                + rawContactId2 + "-" + normalizedName;
+        String expectedLookupKey = accountHashCode + "r" + rawContactId1 + "-" + normalizedName
+                + "." + accountHashCode + "r" + rawContactId2 + "-" + normalizedName;
 
         long contactId = queryContactId(rawContactId1);
         assertStoredValue(ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId),
@@ -83,8 +90,12 @@
         setAggregationException(
                 AggregationExceptions.TYPE_KEEP_TOGETHER, rawContactId1, rawContactId3);
 
+        int accountHashCode = LOCAL_ACCOUNT_HASH_CODE;
         // Two source ids, of them escaped
-        String expectedLookupKey = "0i123.0e4..5..6.0ihttp%3A%2F%2Ffoo%3Fbar";
+        String expectedLookupKey = accountHashCode + "i123."
+                + accountHashCode + "e4..5..6."
+                + accountHashCode + "ihttp%3A%2F%2Ffoo%3Fbar";
+
 
         long contactId = queryContactId(rawContactId1);
         assertStoredValue(ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId),
@@ -139,7 +150,9 @@
         setAggregationException(
                 AggregationExceptions.TYPE_KEEP_TOGETHER, rawContactId1, rawContactId3);
 
-        String lookupKey = "0i1.0i2.0i3";
+        int accountHashCode = LOCAL_ACCOUNT_HASH_CODE;
+        String lookupKey = accountHashCode + "i1." + accountHashCode + "i2."
+                + accountHashCode + "i3";
 
         long contactId = queryContactId(rawContactId1);
         assertStoredValue(ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId),
@@ -156,10 +169,10 @@
         long largerContactId = queryContactId(rawContactId1);
         assertStoredValue(
                 ContentUris.withAppendedId(Contacts.CONTENT_URI, largerContactId),
-                Contacts.LOOKUP_KEY, "0i1.0i2");
+                Contacts.LOOKUP_KEY, accountHashCode + "i1." + accountHashCode + "i2");
         assertStoredValue(
                 ContentUris.withAppendedId(Contacts.CONTENT_URI, queryContactId(rawContactId3)),
-                Contacts.LOOKUP_KEY, "0i3");
+                Contacts.LOOKUP_KEY, accountHashCode + "i3");
 
         Uri lookupUri = Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey);
         assertStoredValue(lookupUri, Contacts._ID, largerContactId);
@@ -169,15 +182,17 @@
         long rawContactId1 = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe");
         storeValue(RawContacts.CONTENT_URI, rawContactId1, RawContacts.SOURCE_ID, "1");
 
+        int accountHashCode = LOCAL_ACCOUNT_HASH_CODE;
         long contactId = queryContactId(rawContactId1);
-        String lookupUri = "content://com.android.contacts/contacts/lookup/0i1/" + contactId;
+        String lookupUri = "content://com.android.contacts/contacts/lookup/"
+                + accountHashCode + "i1/" + contactId;
 
         Uri contentUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
         assertEquals(lookupUri,
                 Contacts.getLookupUri(mResolver, contentUri).toString());
 
         Uri staleLookupUri = ContentUris.withAppendedId(
-                Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, "0i1"),
+                Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, accountHashCode + "i1"),
                 contactId+2);
         assertEquals(lookupUri,
                 Contacts.getLookupUri(mResolver, staleLookupUri).toString());
diff --git a/tests/src/com/android/providers/contacts/ContactsActor.java b/tests/src/com/android/providers/contacts/ContactsActor.java
index 56a4fc4..e3c606e 100644
--- a/tests/src/com/android/providers/contacts/ContactsActor.java
+++ b/tests/src/com/android/providers/contacts/ContactsActor.java
@@ -191,7 +191,7 @@
         }
 
         @Override
-        public int getUserHandle() {
+        public int getProcessUserId() {
             return myUser;
         }
 
@@ -404,7 +404,7 @@
             @Override
             public int getUserId() {
                 if (mockUserManager != null) {
-                    return mockUserManager.getUserHandle();
+                    return mockUserManager.getProcessUserId();
                 } else {
                     return DEFAULT_USER_ID;
                 }
diff --git a/tests/src/com/android/providers/contacts/ContactsDatabaseHelperTest.java b/tests/src/com/android/providers/contacts/ContactsDatabaseHelperTest.java
index a9867c5..2e2e924 100644
--- a/tests/src/com/android/providers/contacts/ContactsDatabaseHelperTest.java
+++ b/tests/src/com/android/providers/contacts/ContactsDatabaseHelperTest.java
@@ -18,6 +18,7 @@
 
 import android.content.ContentValues;
 import android.database.ContentObserver;
+import android.accounts.Account;
 import android.database.Cursor;
 import android.database.sqlite.SQLiteDatabase;
 import android.net.Uri;
@@ -508,4 +509,46 @@
 
         assertEquals(creationTime, dbHelper2.getDatabaseCreationTime());
     }
+
+    public void testGetAndSetDefaultAccount() {
+        Account account = mDbHelper.getDefaultAccount();
+        assertNull(account);
+
+        mDbHelper.setDefaultAccount("a", "b");
+        account = mDbHelper.getDefaultAccount();
+        assertEquals("a", account.name);
+        assertEquals("b", account.type);
+
+        mDbHelper.setDefaultAccount("c", "d");
+        account = mDbHelper.getDefaultAccount();
+        assertEquals("c", account.name);
+        assertEquals("d", account.type);
+
+        mDbHelper.setDefaultAccount(null, null);
+        account = mDbHelper.getDefaultAccount();
+        assertNull(account);
+
+        // Invalid account (not-null account name and null account type) throws exception.
+        try {
+            mDbHelper.setDefaultAccount("name", null);
+            fail("Setting default account to an invalid account should fail.");
+        } catch (IllegalArgumentException e) {
+            // expected.
+        }
+        account = mDbHelper.getDefaultAccount();
+        assertNull(account);
+
+        // Update default account to an existing account
+        mDbHelper.setDefaultAccount("a", "b");
+        account = mDbHelper.getDefaultAccount();
+        assertEquals("a", account.name);
+        assertEquals("b", account.type);
+
+        try (Cursor cursor = mDbHelper.getReadableDatabase().query(Tables.ACCOUNTS, new String[]{
+                ContactsDatabaseHelper.AccountsColumns.ACCOUNT_NAME,
+                ContactsDatabaseHelper.AccountsColumns.ACCOUNT_TYPE
+        }, null, null, null, null, null)) {
+            assertEquals(3, cursor.getCount());
+        }
+    }
 }
diff --git a/tests/src/com/android/providers/contacts/ContactsDatabaseHelperUpgradeTest.java b/tests/src/com/android/providers/contacts/ContactsDatabaseHelperUpgradeTest.java
index d50a292..0548f98 100644
--- a/tests/src/com/android/providers/contacts/ContactsDatabaseHelperUpgradeTest.java
+++ b/tests/src/com/android/providers/contacts/ContactsDatabaseHelperUpgradeTest.java
@@ -33,7 +33,6 @@
 import android.provider.ContactsContract.PhotoFiles;
 import android.provider.ContactsContract.PinnedPositions;
 import android.provider.ContactsContract.RawContacts;
-import android.provider.ContactsContract.Settings;
 import android.provider.ContactsContract.StatusUpdates;
 import android.provider.ContactsContract.StreamItemPhotos;
 import android.provider.ContactsContract.StreamItems;
@@ -122,6 +121,8 @@
 
         int oldVersion = 1108;
         oldVersion = upgradeTo1109(oldVersion);
+        oldVersion = upgradeTo1600(oldVersion);
+        oldVersion = upgradeTo1601(oldVersion);
         oldVersion = upgrade(oldVersion, ContactsDatabaseHelper.DATABASE_VERSION);
         assertEquals(ContactsDatabaseHelper.DATABASE_VERSION, oldVersion);
         assertDatabaseStructureSameAsList(TABLE_LIST, /* isNewDatabase =*/ false);
@@ -192,6 +193,110 @@
         return MY_VERSION;
     }
 
+    private int upgradeTo1600(int upgradeFrom) {
+        final int MY_VERSION = 1600;
+
+        executeSqlFromAssetFile(getTestContext(), mDb, "upgradeTest/pre_upgrade1600.sql");
+
+        mHelper.onUpgrade(mDb, upgradeFrom, MY_VERSION);
+
+        try (Cursor c = mDb.rawQuery("select "
+                + "_id, account_name, account_type, data_set, ungrouped_visible, should_sync "
+                + "from accounts order by _id", null)) {
+            BaseContactsProvider2Test.assertCursorValuesOrderly(c,
+                    cv(Contacts._ID, 1,
+                            "account_name", null,
+                            "account_type", null,
+                            "data_set", null,
+                            "ungrouped_visible", 1,
+                            "should_sync", 0
+                    ),
+                    cv(Contacts._ID, 2,
+                            "account_name", "visible",
+                            "account_type", "type1",
+                            "data_set", null,
+                            "ungrouped_visible", 1,
+                            "should_sync", 1
+                    ),
+                    cv(Contacts._ID, 3,
+                            "account_name", "visible",
+                            "account_type", "type1",
+                            "data_set", "ds_not_visible",
+                            "ungrouped_visible", 0,
+                            "should_sync", 1
+                    ),
+                    cv(Contacts._ID, 4,
+                            "account_name", "not_syncable",
+                            "account_type", "type1",
+                            "data_set", null,
+                            "ungrouped_visible", 0,
+                            "should_sync", 0
+                    ),
+                    cv(Contacts._ID, 5,
+                            "account_name", "no_settings",
+                            "account_type", "type2",
+                            "data_set", null,
+                            "ungrouped_visible", 0,
+                            "should_sync", 1
+                    ));
+        }
+
+        return MY_VERSION;
+    }
+
+    private int upgradeTo1601(int upgradeFrom) {
+        final int MY_VERSION = 1601;
+
+        mHelper.onUpgrade(mDb, upgradeFrom, MY_VERSION);
+
+        try (Cursor c = mDb.rawQuery("select "
+            + "_id, account_name, account_type, data_set, ungrouped_visible, should_sync, "
+            + "x_is_default from accounts order by _id", null)) {
+            BaseContactsProvider2Test.assertCursorValuesOrderly(c,
+                cv(Contacts._ID, 1,
+                    "account_name", null,
+                    "account_type", null,
+                    "data_set", null,
+                    "ungrouped_visible", 1,
+                    "should_sync", 0,
+                    "x_is_default", 0
+                ),
+                cv(Contacts._ID, 2,
+                    "account_name", "visible",
+                    "account_type", "type1",
+                    "data_set", null,
+                    "ungrouped_visible", 1,
+                    "should_sync", 1,
+                    "x_is_default", 0
+                ),
+                cv(Contacts._ID, 3,
+                    "account_name", "visible",
+                    "account_type", "type1",
+                    "data_set", "ds_not_visible",
+                    "ungrouped_visible", 0,
+                    "should_sync", 1,
+                    "x_is_default", 0
+                ),
+                cv(Contacts._ID, 4,
+                    "account_name", "not_syncable",
+                    "account_type", "type1",
+                    "data_set", null,
+                    "ungrouped_visible", 0,
+                    "should_sync", 0,
+                    "x_is_default", 0
+                ),
+                cv(Contacts._ID, 5,
+                    "account_name", "no_settings",
+                    "account_type", "type2",
+                    "data_set", null,
+                    "ungrouped_visible", 0,
+                    "should_sync", 1,
+                    "x_is_default", 0
+                ));
+        }
+        return MY_VERSION;
+    }
+
     private int upgrade(int upgradeFrom, int upgradeTo) {
         if (upgradeFrom < upgradeTo) {
             mHelper.onUpgrade(mDb, upgradeFrom, upgradeTo);
@@ -222,6 +327,9 @@
             new TableColumn(AccountsColumns.DATA_SET, TEXT, false, null),
             new TableColumn(AccountsColumns.SIM_SLOT_INDEX, INTEGER, false, null),
             new TableColumn(AccountsColumns.SIM_EF_TYPE, INTEGER, false, null),
+            new TableColumn(AccountsColumns.UNGROUPED_VISIBLE, INTEGER, true, "0"),
+            new TableColumn(AccountsColumns.SHOULD_SYNC, INTEGER, true, "1"),
+            new TableColumn(AccountsColumns.IS_DEFAULT, INTEGER, true, "0")
     };
 
     private static final TableColumn[] CONTACTS_COLUMNS = new TableColumn[] {
@@ -364,6 +472,7 @@
             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),
     };
@@ -417,14 +526,6 @@
             new TableColumn(AggregationExceptions.RAW_CONTACT_ID2, INTEGER, false, null),
     };
 
-    private static final TableColumn[] SETTINGS_COLUMNS = new TableColumn[] {
-            new TableColumn(Settings.ACCOUNT_NAME, STRING, true, null),
-            new TableColumn(Settings.ACCOUNT_TYPE, STRING, true, null),
-            new TableColumn(Settings.DATA_SET, STRING, false, null),
-            new TableColumn(Settings.UNGROUPED_VISIBLE, INTEGER, true, "0"),
-            new TableColumn(Settings.SHOULD_SYNC, INTEGER, true, "1"),
-    };
-
     private static final TableColumn[] VISIBLE_CONTACTS_COLUMNS = new TableColumn[] {
             new TableColumn(Contacts._ID, INTEGER, false, null),
     };
@@ -567,7 +668,6 @@
             new TableListEntry(Tables.NICKNAME_LOOKUP, NICKNAME_LOOKUP_COLUMNS),
             new TableListEntry(Tables.GROUPS, GROUPS_COLUMNS),
             new TableListEntry(Tables.AGGREGATION_EXCEPTIONS, AGGREGATION_EXCEPTIONS_COLUMNS),
-            new TableListEntry(Tables.SETTINGS, SETTINGS_COLUMNS),
             new TableListEntry(Tables.VISIBLE_CONTACTS, VISIBLE_CONTACTS_COLUMNS),
             new TableListEntry(Tables.DEFAULT_DIRECTORY, DEFAULT_DIRECTORY_COLUMNS),
             new TableListEntry("calls", CALLS_COLUMNS, false),
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 0000000..6ef14cf
--- /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 7efc2f4..09ea19f 100644
--- a/tests/src/com/android/providers/contacts/ContactsProvider2Test.java
+++ b/tests/src/com/android/providers/contacts/ContactsProvider2Test.java
@@ -19,7 +19,12 @@
 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.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.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;
@@ -77,6 +87,7 @@
 import com.android.internal.util.ArrayUtils;
 import com.android.providers.contacts.ContactsActor.AlteringUserContext;
 import com.android.providers.contacts.ContactsActor.MockUserManager;
+import com.android.providers.contacts.ContactsDatabaseHelper.AccountsColumns;
 import com.android.providers.contacts.ContactsDatabaseHelper.AggregationExceptionColumns;
 import com.android.providers.contacts.ContactsDatabaseHelper.ContactsColumns;
 import com.android.providers.contacts.ContactsDatabaseHelper.DataUsageStatColumns;
@@ -84,6 +95,7 @@
 import com.android.providers.contacts.ContactsDatabaseHelper.PresenceColumns;
 import com.android.providers.contacts.ContactsDatabaseHelper.RawContactsColumns;
 import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
+import com.android.providers.contacts.tests.R;
 import com.android.providers.contacts.testutil.CommonDatabaseUtils;
 import com.android.providers.contacts.testutil.ContactUtil;
 import com.android.providers.contacts.testutil.DataUtil;
@@ -91,8 +103,8 @@
 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.tests.R;
 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;
@@ -125,29 +137,242 @@
 
     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);
@@ -4242,6 +4467,136 @@
                 new String[] {"c", "d", "plus"}, Settings.SHOULD_SYNC, "0");
     }
 
+    public void testSettingsDeletion() {
+        Account account = new Account("a", "b");
+        Uri settingUri = createSettings(account, "0", "1");
+        long rawContactId = RawContactUtil.createRawContact(mResolver, account);
+
+        int count = mResolver.delete(settingUri, null, null);
+
+        // Settings cannot be deleted when there are still raw contacts for the account.
+        assertEquals(0, count);
+
+        assertStoredValue(Settings.CONTENT_URI,
+                Settings.ACCOUNT_NAME + "= ? AND " + Settings.ACCOUNT_TYPE + "= ?",
+                new String[] {"a", "b"}, Settings.UNGROUPED_VISIBLE, "1");
+
+        RawContactUtil.delete(mResolver, rawContactId, true);
+
+        count = mResolver.delete(settingUri, null, null);
+
+        assertEquals(1, count);
+        assertRowCount(0, Settings.CONTENT_URI, null, null);
+    }
+
+    public void testSettingsUpdate() {
+        Account account1 = new Account("a", "b");
+        Account account2 = new Account("c", "d");
+        Account account3 = new Account("e", "f");
+        createSettings(account1, "0", "0");
+        createSettings(account2, "0", "0");
+        createSettings(account3, "0", "0");
+
+        ContentValues values = new ContentValues();
+        values.put(Settings.UNGROUPED_VISIBLE, 1);
+        int count = mResolver.update(Settings.CONTENT_URI, values, null, null);
+
+        assertEquals(3, count);
+        assertStoredValues(Settings.CONTENT_URI,
+                cv(Settings.UNGROUPED_VISIBLE, 1),
+                cv(Settings.UNGROUPED_VISIBLE, 1),
+                cv(Settings.UNGROUPED_VISIBLE, 1));
+
+        values.put(Settings.SHOULD_SYNC, 1);
+        count = mResolver.update(Settings.CONTENT_URI, values,
+                Settings.ACCOUNT_NAME  + "=?", new String[] {"a"});
+
+        assertEquals(1, count);
+        assertStoredValues(Settings.CONTENT_URI,
+                cv(Settings.ACCOUNT_NAME, "a",
+                        Settings.SHOULD_SYNC, 1),
+                cv(Settings.ACCOUNT_NAME, "c",
+                        Settings.SHOULD_SYNC, 0),
+                cv(Settings.ACCOUNT_NAME, "e",
+                        Settings.SHOULD_SYNC, 0));
+
+        values.clear();
+        // Settings are stored in the accounts table but updates shouldn't be allowed to modify
+        // the other non-Settings columns.
+        values.put(Settings.ACCOUNT_NAME, "x");
+        values.put(Settings.ACCOUNT_TYPE, "y");
+        values.put(Settings.DATA_SET, "z");
+        mResolver.update(Settings.CONTENT_URI, values, null, null);
+
+        values.put(AccountsColumns.SIM_EF_TYPE, 1);
+        values.put(AccountsColumns.SIM_SLOT_INDEX, 1);
+        try {
+            mResolver.update(Settings.CONTENT_URI, values, null, null);
+        } catch (Exception e) {
+            // ignored. We just care that the update didn't change the data
+        }
+
+        assertStoredValuesDb("SELECT * FROM " + Tables.ACCOUNTS, null,
+                cv(
+                        Settings.ACCOUNT_NAME, "a",
+                        Settings.ACCOUNT_TYPE, "b",
+                        Settings.DATA_SET, null,
+                        AccountsColumns.SIM_SLOT_INDEX, null,
+                        AccountsColumns.SIM_EF_TYPE, null
+                ),
+                cv(
+                        Settings.ACCOUNT_NAME, "c",
+                        Settings.ACCOUNT_TYPE, "d",
+                        Settings.DATA_SET, null,
+                        AccountsColumns.SIM_SLOT_INDEX, null,
+                        AccountsColumns.SIM_EF_TYPE, null
+                ),
+                cv(
+                        Settings.ACCOUNT_NAME, "e",
+                        Settings.ACCOUNT_TYPE, "f",
+                        Settings.DATA_SET, null,
+                        AccountsColumns.SIM_SLOT_INDEX, null,
+                        AccountsColumns.SIM_EF_TYPE, null
+                ));
+    }
+
+    public void testSettingsLocalAccount() {
+        AccountWithDataSet localAccount = AccountWithDataSet.LOCAL;
+
+        // It's not possible to insert the local account directly into settings but it will be
+        // created automatically when a raw contact is created for it.
+        RawContactUtil.createRawContactWithAccountDataSet(
+                mResolver, localAccount.getAccountName(),
+                localAccount.getAccountType(), localAccount.getDataSet());
+
+        ContentValues values = new ContentValues();
+        values.put(Settings.ACCOUNT_NAME, localAccount.getAccountName());
+        values.put(Settings.ACCOUNT_TYPE, localAccount.getAccountType());
+        values.put(Settings.DATA_SET, localAccount.getDataSet());
+        ContentValues expectedValues = new ContentValues(values);
+        // The defaults for the local account are opposite of other accounts.
+        expectedValues.put(Settings.UNGROUPED_VISIBLE, "1");
+        expectedValues.put(Settings.SHOULD_SYNC, "0");
+
+        assertStoredValues(Settings.CONTENT_URI, expectedValues);
+
+        values.put(Settings.SHOULD_SYNC, 1);
+        values.put(Settings.UNGROUPED_VISIBLE, 0);
+        mResolver.update(Settings.CONTENT_URI, values, null, null);
+
+        expectedValues.put(Settings.UNGROUPED_VISIBLE, "0");
+        expectedValues.put(Settings.SHOULD_SYNC, "1");
+        assertStoredValues(Settings.CONTENT_URI, expectedValues);
+
+        // Empty strings should also be the local account.
+        values.put(Settings.ACCOUNT_NAME, "");
+        values.put(Settings.ACCOUNT_TYPE, "");
+        values.put(Settings.DATA_SET, "");
+        mResolver.insert(Settings.CONTENT_URI, values);
+
+        assertRowCount(1, Settings.CONTENT_URI, null, null);
+    }
+
     public void testDisplayNameParsingWhenPartsUnspecified() {
         long rawContactId = RawContactUtil.createRawContact(mResolver);
         ContentValues values = new ContentValues();
@@ -6317,8 +6672,11 @@
         mActor.setAccounts(new Account[]{mAccount, mAccountTwo});
         cp.onAccountsUpdated(new Account[]{mAccount, mAccountTwo});
         assertEquals(1, getCount(RawContacts.CONTENT_URI, null, null));
-        assertStoredValue(rawContact3, RawContacts.ACCOUNT_NAME, null);
-        assertStoredValue(rawContact3, RawContacts.ACCOUNT_TYPE, null);
+        assertStoredValue(
+                rawContact3, RawContacts.ACCOUNT_NAME,
+                AccountWithDataSet.LOCAL.getAccountName());
+        assertStoredValue(rawContact3, RawContacts.ACCOUNT_TYPE,
+                AccountWithDataSet.LOCAL.getAccountType());
 
         long rawContactId1 = RawContactUtil.createRawContact(mResolver, mAccount);
         insertEmail(rawContactId1, "account1@email.com");
@@ -7329,6 +7687,55 @@
 
     }
 
+    public void testCleanupDanglingContacts_noDanglingContacts() throws Exception {
+        SynchronousContactsProvider2 provider = (SynchronousContactsProvider2) mActor.provider;
+        RawContactUtil.createRawContactWithName(mResolver, "A", "B");
+        RawContactUtil.createRawContactWithName(mResolver, "C", "D");
+
+        provider.cleanupDanglingContacts();
+
+        Cursor contactCursor = mResolver.query(Contacts.CONTENT_URI, null, null, null, null);
+        Cursor rawContactCursor = mResolver.query(RawContacts.CONTENT_URI, null, null, null, null);
+
+        // No contacts should be deleted
+        assertEquals(2, contactCursor.getCount());
+        assertEquals(2, rawContactCursor.getCount());
+    }
+
+    public void testCleanupDanglingContacts_singleDanglingContacts() throws Exception {
+        SynchronousContactsProvider2 provider = (SynchronousContactsProvider2) mActor.provider;
+        long rawContactId = RawContactUtil.createRawContactWithName(mResolver, "A", "B");
+
+        // Change the contact_id to create dangling contact.
+        SQLiteDatabase db = provider.getDatabaseHelper().getWritableDatabase();
+        db.execSQL("UPDATE raw_contacts SET contact_id = 99999 WHERE _id = " + rawContactId + ";");
+
+        provider.cleanupDanglingContacts();
+
+        // Dangling contact should be deleted from contacts table.
+        assertEquals(0, mResolver.query(Contacts.CONTENT_URI, null, null, null, null).getCount());
+    }
+
+    public void testCleanupDanglingContacts_multipleDanglingContacts() throws Exception {
+        SynchronousContactsProvider2 provider = (SynchronousContactsProvider2) mActor.provider;
+        long rawContactId1 = RawContactUtil.createRawContactWithName(mResolver, "A", "B");
+        long rawContactId2 = RawContactUtil.createRawContactWithName(mResolver, "C", "D");
+        RawContactUtil.createRawContactWithName(mResolver, "E", "F");
+
+        final ContactsDatabaseHelper helper = provider.getDatabaseHelper();
+        SQLiteDatabase db = helper.getWritableDatabase();
+
+        // Change contact_id of RawContact1 and RawContact2 to create dangling contacts.
+        db.execSQL("UPDATE raw_contacts SET contact_id = 99998 WHERE _id = " + rawContactId1 + ";");
+        db.execSQL("UPDATE raw_contacts SET contact_id = 99999 WHERE _id = " + rawContactId2 + ";");
+
+        provider.cleanupDanglingContacts();
+
+        // Should only be one contact left in the contacts table.
+        // RawContact1 and RawContact2 should be deleted from the contacts table.
+        assertEquals(1, mResolver.query(Contacts.CONTENT_URI, null, null, null, null).getCount());
+    }
+
     public void testOverwritePhotoWithThumbnail() throws IOException {
         long rawContactId = RawContactUtil.createRawContactWithName(mResolver);
         long contactId = queryContactId(rawContactId);
@@ -8944,6 +9351,79 @@
         );
     }
 
+    public void testDefaultAccountSet_throwException() {
+        mActor.setAccounts(new Account[]{mAccount});
+        try {
+            mResolver.call(ContactsContract.AUTHORITY_URI, Settings.SET_DEFAULT_ACCOUNT_METHOD,
+                    null, null);
+            fail();
+        } catch (SecurityException expected) {
+        }
+
+        mActor.addPermissions("android.permission.SET_DEFAULT_ACCOUNT_FOR_CONTACTS");
+        try {
+            Bundle bundle = new Bundle();
+            bundle.putString(Settings.ACCOUNT_NAME, "account1"); // no account type specified
+            mResolver.call(ContactsContract.AUTHORITY_URI, Settings.SET_DEFAULT_ACCOUNT_METHOD,
+                    null, bundle);
+            fail();
+        } catch (IllegalArgumentException expected) {
+        }
+
+        try {
+            Bundle bundle = new Bundle();
+            bundle.putString(Settings.ACCOUNT_NAME, "account1");
+            bundle.putString(Settings.ACCOUNT_TYPE, "account type1");
+            bundle.putString(Settings.DATA_SET, "c"); // data set should not be set.
+            mResolver.call(ContactsContract.AUTHORITY_URI, Settings.SET_DEFAULT_ACCOUNT_METHOD,
+                    null, bundle);
+            fail();
+        } catch (IllegalArgumentException expected) {
+        }
+
+        try {
+            Bundle bundle = new Bundle();
+            bundle.putString(Settings.ACCOUNT_NAME, "account2"); // invalid account
+            bundle.putString(Settings.ACCOUNT_TYPE, "account type2");
+            mResolver.call(ContactsContract.AUTHORITY_URI, Settings.SET_DEFAULT_ACCOUNT_METHOD,
+                null, bundle);
+            fail();
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    public void testDefaultAccountSetAndQuery() {
+        Bundle response = mResolver.call(ContactsContract.AUTHORITY_URI,
+                Settings.QUERY_DEFAULT_ACCOUNT_METHOD, null, null);
+        Account account = response.getParcelable(Settings.KEY_DEFAULT_ACCOUNT);
+        assertNull(account);
+
+        mActor.addPermissions("android.permission.SET_DEFAULT_ACCOUNT_FOR_CONTACTS");
+        mActor.setAccounts(new Account[]{mAccount});
+        // Set ("account1", "account type1") account as the default account.
+        Bundle bundle = new Bundle();
+        bundle.putString(Settings.ACCOUNT_NAME, "account1");
+        bundle.putString(Settings.ACCOUNT_TYPE, "account type1");
+        mResolver.call(ContactsContract.AUTHORITY_URI, Settings.SET_DEFAULT_ACCOUNT_METHOD,
+                null, bundle);
+
+        response = mResolver.call(ContactsContract.AUTHORITY_URI,
+                Settings.QUERY_DEFAULT_ACCOUNT_METHOD, null, null);
+        account = response.getParcelable(Settings.KEY_DEFAULT_ACCOUNT);
+        assertEquals("account1", account.name);
+        assertEquals("account type1", account.type);
+
+        // Set NULL account as default account.
+        bundle = new Bundle();
+        mResolver.call(ContactsContract.AUTHORITY_URI, Settings.SET_DEFAULT_ACCOUNT_METHOD,
+            null, bundle);
+
+        response = mResolver.call(ContactsContract.AUTHORITY_URI,
+                Settings.QUERY_DEFAULT_ACCOUNT_METHOD, null, null);
+        account = response.getParcelable(Settings.KEY_DEFAULT_ACCOUNT);
+        assertNull(account);
+    }
+
     public void testPinnedPositionsDemoteIllegalArguments() {
         try {
             mResolver.call(ContactsContract.AUTHORITY_URI, PinnedPositions.UNDEMOTE_METHOD,
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 0000000..c230cea
--- /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
diff --git a/tests/src/com/android/providers/contacts/aggregation/ContactAggregatorTest.java b/tests/src/com/android/providers/contacts/aggregation/ContactAggregatorTest.java
index 56f0883..37e3184 100644
--- a/tests/src/com/android/providers/contacts/aggregation/ContactAggregatorTest.java
+++ b/tests/src/com/android/providers/contacts/aggregation/ContactAggregatorTest.java
@@ -1396,7 +1396,7 @@
         // Action: make raw contact 2's name super primary
         storeValue(nameUri2, Data.IS_SUPER_PRIMARY, 1);
 
-        // Sanity check.
+        // Initial check.
         assertStoredValue(nameUri1, Data.IS_SUPER_PRIMARY, 0);
         assertStoredValue(nameUri2, Data.IS_SUPER_PRIMARY, 1);
 
@@ -1750,7 +1750,7 @@
         final Uri uri = DataUtil.insertStructuredName(mResolver, rawContactId1, "name1",
                 null, null, /* isSuperPrimary = */ true);
 
-        // Sanity check.
+        // Initial check.
         assertStoredValue(uri, Data.IS_SUPER_PRIMARY, 1);
 
         // Action: aggregate
diff --git a/tests/src/com/android/providers/contacts/database/MoreDatabaseUtilTest.java b/tests/src/com/android/providers/contacts/database/MoreDatabaseUtilTest.java
index 6eb8b55..5a40bec 100644
--- a/tests/src/com/android/providers/contacts/database/MoreDatabaseUtilTest.java
+++ b/tests/src/com/android/providers/contacts/database/MoreDatabaseUtilTest.java
@@ -54,4 +54,19 @@
         assertEquals("testtable_testfield_index",
                 MoreDatabaseUtils.buildIndexName("testtable", "testfield"));
     }
+
+    public void testSqlEscapeNullableString() {
+        assertEquals("NULL", MoreDatabaseUtils.sqlEscapeNullableString(null));
+        assertEquals("'foo'", MoreDatabaseUtils.sqlEscapeNullableString("foo"));
+    }
+
+    public void testAppendEscapedSQLStringOrLiteralNull() {
+        StringBuilder sb1 = new StringBuilder();
+        MoreDatabaseUtils.appendEscapedSQLStringOrLiteralNull(sb1, null);
+        assertEquals("NULL", sb1.toString());
+
+        StringBuilder sb2 = new StringBuilder();
+        MoreDatabaseUtils.appendEscapedSQLStringOrLiteralNull(sb2, "foo");
+        assertEquals("'foo'", sb2.toString());
+    }
 }
diff --git a/tests/src/com/android/providers/contacts/enterprise/EnterprisePolicyGuardTest.java b/tests/src/com/android/providers/contacts/enterprise/EnterprisePolicyGuardTest.java
index c068459..2e5241f 100644
--- a/tests/src/com/android/providers/contacts/enterprise/EnterprisePolicyGuardTest.java
+++ b/tests/src/com/android/providers/contacts/enterprise/EnterprisePolicyGuardTest.java
@@ -16,7 +16,6 @@
 package com.android.providers.contacts.enterprise;
 
 import android.app.admin.DevicePolicyManager;
-import android.content.ContentResolver;
 import android.content.Context;
 import android.content.pm.UserInfo;
 import android.net.Uri;
@@ -265,7 +264,7 @@
 
         List<UserInfo> userInfos = MANAGED_USERINFO_LIST;
         UserManager mockUm = mock(UserManager.class);
-        when(mockUm.getUserHandle()).thenReturn(CURRENT_USER_ID);
+        when(mockUm.getProcessUserId()).thenReturn(CURRENT_USER_ID);
         when(mockUm.getUsers()).thenReturn(userInfos);
         when(mockUm.getProfiles(Matchers.anyInt())).thenReturn(userInfos);
         when(mockUm.getProfileParent(WORK_USER_ID)).thenReturn(CURRENT_USER_INFO);
diff --git a/tests2/Android.bp b/tests2/Android.bp
deleted file mode 100644
index 3dcdebb..0000000
--- a/tests2/Android.bp
+++ /dev/null
@@ -1,47 +0,0 @@
-//
-// Copyright (C) 2016 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 {
-    // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "packages_providers_ContactsProvider_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    default_applicable_licenses: [
-        "packages_providers_ContactsProvider_license",
-    ],
-}
-
-android_test {
-    name: "ContactsProviderTests2",
-    static_libs: [
-        "ContactsProviderTestUtils",
-        "androidx.test.rules",
-        "mockito-target-minus-junit4",
-    ],
-    libs: [
-        "android.test.runner",
-        "android.test.base",
-    ],
-    srcs: ["src/**/*.java"],
-    platform_apis: true,
-    test_suites: ["device-tests"],
-    instrumentation_for: "ContactsProvider",
-    certificate: "shared",
-    optimize: {
-        enabled: false,
-    },
-}
diff --git a/tests2/AndroidManifest.xml b/tests2/AndroidManifest.xml
deleted file mode 100644
index fc00251..0000000
--- a/tests2/AndroidManifest.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 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.
--->
-
-<!--
-  Another unit tests against CP2 that runs in a separate process.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.providers.contacts.tests2" >
-
-    <uses-permission android:name="android.permission.GET_ACCOUNTS" />
-    <uses-permission android:name="android.permission.READ_CONTACTS" />
-    <uses-permission android:name="android.permission.WRITE_CONTACTS" />
-
-    <application>
-        <uses-library android:name="android.test.runner" />
-    </application>
-
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.providers.contacts.tests2" />
-</manifest>
diff --git a/tests2/AndroidTest.xml b/tests2/AndroidTest.xml
deleted file mode 100644
index 957350b..0000000
--- a/tests2/AndroidTest.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2017 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.
--->
-<configuration description="Runs Contacts Provider Tests.">
-    <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
-        <option name="test-file-name" value="ContactsProviderTests2.apk" />
-    </target_preparer>
-
-    <option name="test-suite-tag" value="apct" />
-    <option name="test-tag" value="ContactsProviderTests2" />
-    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
-        <option name="package" value="com.android.providers.contacts.tests2" />
-        <option name="hidden-api-checks" value="false"/>
-    </test>
-</configuration>
diff --git a/tests2/src/com/android/providers/contacts/tests2/AllUriTest.java b/tests2/src/com/android/providers/contacts/tests2/AllUriTest.java
deleted file mode 100644
index 30fd3be..0000000
--- a/tests2/src/com/android/providers/contacts/tests2/AllUriTest.java
+++ /dev/null
@@ -1,730 +0,0 @@
-/*
- * Copyright (C) 2016 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.tests2;
-
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.CancellationSignal;
-import android.provider.ContactsContract;
-import android.provider.ContactsContract.Contacts;
-import android.provider.ContactsContract.RawContacts;
-import android.provider.ContactsContract.SyncState;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.LargeTest;
-import android.util.Log;
-
-import junit.framework.AssertionFailedError;
-
-import java.io.FileNotFoundException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.ArrayList;
-
-/*
- * TODO The following operations would fail, not because they're not supported, but because of
- * missing parameters.  Fix them.
-insert for 'content://com.android.contacts/contacts' failed: Aggregate contacts are created automatically
-insert for 'content://com.android.contacts/raw_contacts/1/data' failed: mimetype is required
-update for 'content://com.android.contacts/raw_contacts/1/stream_items/1' failed: Empty values
-insert for 'content://com.android.contacts/data' failed: raw_contact_id is required
-insert for 'content://com.android.contacts/settings' failed: Must specify both or neither of ACCOUNT_NAME and ACCOUNT_TYPE; URI: content://com.android.contacts/settings?account_type=1, calling user: com.android.providers.contacts.tests2, calling package:com.android.providers.contacts.tests2
-insert for 'content://com.android.contacts/status_updates' failed: PROTOCOL and IM_HANDLE are required
-insert for 'content://com.android.contacts/profile' failed: The profile contact is created automatically
-insert for 'content://com.android.contacts/profile/data' failed: raw_contact_id is required
-insert for 'content://com.android.contacts/profile/raw_contacts/1/data' failed: mimetype is required
-insert for 'content://com.android.contacts/profile/status_updates' failed: PROTOCOL and IM_HANDLE are required
-
-
-openInputStream for 'content://com.android.contacts/contacts/as_multi_vcard/XXX' failed: Caught Exception: Invalid lookup id: XXX
-openInputStream for 'content://com.android.contacts/directory_file_enterprise/XXX?directory=0' failed: Caught Exception: java.lang.IllegalArgumentException: Directory is not a remote directory: content://com.android.contacts/directory_file_enterprise/XXX?directory=0
-openOutputStream for 'content://com.android.contacts/directory_file_enterprise/XXX?directory=0' failed: Caught Exception: java.lang.IllegalArgumentException: Directory is not a remote directory: content://com.android.contacts/directory_file_enterprise/XXX?directory=0
-*/
-
-/**
- * TODO Add test for delete/update/insert too.
- * TODO Copy it to CTS
- */
-@LargeTest
-public class AllUriTest extends AndroidTestCase {
-    private static final String TAG = "AllUrlTest";
-
-    // "-" : Query not supported.
-    // "!" : Can't query because it requires the cross-user permission.
-    // The following markers are planned, but not implemented and the definition below is not all
-    // correct yet.
-    // "d" : supports delete.
-    // "u" : supports update.
-    // "i" : supports insert.
-    // "r" : supports read.
-    // "w" : supports write.
-    // "s" : has x_times_contacted and x_last_time_contacted.
-    // "t" : has x_times_used and x_last_time_used.
-    private static final String[][] URIs = {
-            {"content://com.android.contacts/contacts", "sud"},
-            {"content://com.android.contacts/contacts/1", "sud"},
-            {"content://com.android.contacts/contacts/1/data", "t"},
-            {"content://com.android.contacts/contacts/1/entities", "t"},
-            {"content://com.android.contacts/contacts/1/suggestions"},
-            {"content://com.android.contacts/contacts/1/suggestions/XXX"},
-            {"content://com.android.contacts/contacts/1/photo", "r"},
-            {"content://com.android.contacts/contacts/1/display_photo", "-r"},
-            {"content://com.android.contacts/contacts_corp/1/photo", "-r"},
-            {"content://com.android.contacts/contacts_corp/1/display_photo", "-r"},
-
-            {"content://com.android.contacts/contacts/filter", "s"},
-            {"content://com.android.contacts/contacts/filter/XXX", "s"},
-
-            {"content://com.android.contacts/contacts/lookup/nlookup", "sud"},
-            {"content://com.android.contacts/contacts/lookup/nlookup/data", "t"},
-            {"content://com.android.contacts/contacts/lookup/nlookup/photo", "tr"},
-
-            {"content://com.android.contacts/contacts/lookup/nlookup/1", "sud"},
-            {"content://com.android.contacts/contacts/lookup/nlookup/1/data"},
-            {"content://com.android.contacts/contacts/lookup/nlookup/1/photo", "r"},
-            {"content://com.android.contacts/contacts/lookup/nlookup/display_photo", "-r"},
-            {"content://com.android.contacts/contacts/lookup/nlookup/1/display_photo", "-r"},
-            {"content://com.android.contacts/contacts/lookup/nlookup/entities"},
-            {"content://com.android.contacts/contacts/lookup/nlookup/1/entities"},
-
-            {"content://com.android.contacts/contacts/as_vcard/nlookup", "r"},
-            {"content://com.android.contacts/contacts/as_multi_vcard/XXX"},
-
-            {"content://com.android.contacts/contacts/strequent/", "s"},
-            {"content://com.android.contacts/contacts/strequent/filter/XXX", "s"},
-
-            {"content://com.android.contacts/contacts/group/XXX"},
-
-            {"content://com.android.contacts/contacts/frequent", "s"},
-            {"content://com.android.contacts/contacts/delete_usage", "-d"},
-            {"content://com.android.contacts/contacts/filter_enterprise?directory=0", "s"},
-            {"content://com.android.contacts/contacts/filter_enterprise/XXX?directory=0", "s"},
-
-            {"content://com.android.contacts/raw_contacts", "siud"},
-            {"content://com.android.contacts/raw_contacts/1", "sud"},
-            {"content://com.android.contacts/raw_contacts/1/data", "tu"},
-            {"content://com.android.contacts/raw_contacts/1/display_photo", "-rw"},
-            {"content://com.android.contacts/raw_contacts/1/entity"},
-
-            {"content://com.android.contacts/raw_contact_entities"},
-            {"content://com.android.contacts/raw_contact_entities_corp", "!"},
-
-            {"content://com.android.contacts/data", "tud"},
-            {"content://com.android.contacts/data/1", "tudr"},
-            {"content://com.android.contacts/data/phones", "t"},
-            {"content://com.android.contacts/data_enterprise/phones", "!"},
-            {"content://com.android.contacts/data/phones/1", "tud"},
-            {"content://com.android.contacts/data/phones/filter", "t"},
-            {"content://com.android.contacts/data/phones/filter/XXX", "t"},
-
-            {"content://com.android.contacts/data/phones/filter_enterprise?directory=0", "t"},
-            {"content://com.android.contacts/data/phones/filter_enterprise/XXX?directory=0", "t"},
-
-            {"content://com.android.contacts/data/emails", "t"},
-            {"content://com.android.contacts/data/emails/1", "tud"},
-            {"content://com.android.contacts/data/emails/lookup", "t"},
-            {"content://com.android.contacts/data/emails/lookup/XXX", "t"},
-            {"content://com.android.contacts/data/emails/filter", "t"},
-            {"content://com.android.contacts/data/emails/filter/XXX", "t"},
-            {"content://com.android.contacts/data/emails/filter_enterprise?directory=0", "t"},
-            {"content://com.android.contacts/data/emails/filter_enterprise/XXX?directory=0", "t"},
-            {"content://com.android.contacts/data/emails/lookup_enterprise", "t"},
-            {"content://com.android.contacts/data/emails/lookup_enterprise/XXX", "t"},
-            {"content://com.android.contacts/data/postals", "t"},
-            {"content://com.android.contacts/data/postals/1", "tud"},
-            {"content://com.android.contacts/data/usagefeedback/1,2,3", "-u"},
-            {"content://com.android.contacts/data/callables/", "t"},
-            {"content://com.android.contacts/data/callables/1", "tud"},
-            {"content://com.android.contacts/data/callables/filter", "t"},
-            {"content://com.android.contacts/data/callables/filter/XXX", "t"},
-            {"content://com.android.contacts/data/callables/filter_enterprise?directory=0", "t"},
-            {"content://com.android.contacts/data/callables/filter_enterprise/XXX?directory=0",
-                    "t"},
-            {"content://com.android.contacts/data/contactables/", "t"},
-            {"content://com.android.contacts/data/contactables/filter", "t"},
-            {"content://com.android.contacts/data/contactables/filter/XXX", "t"},
-
-            {"content://com.android.contacts/groups", "iud"},
-            {"content://com.android.contacts/groups/1", "ud"},
-            {"content://com.android.contacts/groups_summary"},
-            {"content://com.android.contacts/syncstate", "iud"},
-            {"content://com.android.contacts/syncstate/1", "-ud"},
-            {"content://com.android.contacts/profile/syncstate", "iud"},
-            {"content://com.android.contacts/phone_lookup/XXX"},
-            {"content://com.android.contacts/phone_lookup_enterprise/XXX"},
-            {"content://com.android.contacts/aggregation_exceptions", "u"},
-            {"content://com.android.contacts/settings", "ud"},
-            {"content://com.android.contacts/status_updates", "ud"},
-            {"content://com.android.contacts/status_updates/1"},
-            {"content://com.android.contacts/search_suggest_query"},
-            {"content://com.android.contacts/search_suggest_query/XXX"},
-            {"content://com.android.contacts/search_suggest_shortcut/XXX"},
-            {"content://com.android.contacts/provider_status"},
-            {"content://com.android.contacts/directories", "u"},
-            {"content://com.android.contacts/directories/1"},
-            {"content://com.android.contacts/directories_enterprise"},
-            {"content://com.android.contacts/directories_enterprise/1"},
-            {"content://com.android.contacts/complete_name"},
-            {"content://com.android.contacts/profile", "su"},
-            {"content://com.android.contacts/profile/entities", "s"},
-            {"content://com.android.contacts/profile/data", "tud"},
-            {"content://com.android.contacts/profile/data/1", "td"},
-            {"content://com.android.contacts/profile/photo", "t"},
-            {"content://com.android.contacts/profile/display_photo", "-r"},
-            {"content://com.android.contacts/profile/as_vcard", "r"},
-            {"content://com.android.contacts/profile/raw_contacts", "siud"},
-
-            // Note this should have supported update... Too late to add.
-            {"content://com.android.contacts/profile/raw_contacts/1", "sd"},
-            {"content://com.android.contacts/profile/raw_contacts/1/data", "tu"},
-            {"content://com.android.contacts/profile/raw_contacts/1/entity"},
-            {"content://com.android.contacts/profile/status_updates", "ud"},
-            {"content://com.android.contacts/profile/raw_contact_entities"},
-            {"content://com.android.contacts/display_photo/1", "-r"},
-            {"content://com.android.contacts/photo_dimensions"},
-            {"content://com.android.contacts/deleted_contacts"},
-            {"content://com.android.contacts/deleted_contacts/1"},
-            {"content://com.android.contacts/directory_file_enterprise/XXX?directory=0", "-"},
-    };
-
-    private static final String[] ARG1 = {"-1"};
-
-    private ContentResolver mResolver;
-
-    private ArrayList<String> mFailures;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-
-        mFailures = new ArrayList<>();
-        mResolver = getContext().getContentResolver();
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        if (mFailures != null) {
-            fail("mFailures is not null.  Did you forget to call failIfFailed()?");
-        }
-
-        super.tearDown();
-    }
-
-    private void addFailure(String message, Throwable th) {
-        Log.e(TAG, "Failed: " + message, th);
-
-        final int MAX = 100;
-        if (mFailures.size() == MAX) {
-            mFailures.add("Too many failures.");
-        } else if (mFailures.size() > MAX) {
-            // Too many failures already...
-        } else {
-            mFailures.add(message);
-        }
-    }
-
-    private void failIfFailed() {
-        if (mFailures == null) {
-            fail("mFailures is null.  Maybe called failIfFailed() twice?");
-        }
-        if (mFailures.size() > 0) {
-            StringBuilder message = new StringBuilder();
-
-            if (mFailures.size() > 0) {
-                Log.e(TAG, "Something went wrong:");
-                for (String s : mFailures) {
-                    Log.e(TAG, s);
-                    if (message.length() > 0) {
-                        message.append("\n");
-                    }
-                    message.append(s);
-                }
-            }
-            mFailures = null;
-            fail("Following test(s) failed:\n" + message);
-        }
-        mFailures = null;
-    }
-
-    private static Uri getUri(String[] path) {
-        return Uri.parse(path[0]);
-    }
-
-    private static boolean supportsQuery(String[] path) {
-        if (path.length == 1) {
-            return true; // supports query by default.
-        }
-        return !(path[1].contains("-") || path[1].contains("!"));
-    }
-
-    private static boolean supportsInsert(String[] path) {
-        return (path.length) >= 2 && path[1].contains("i");
-    }
-
-    private static boolean supportsUpdate(String[] path) {
-        return (path.length) >= 2 && path[1].contains("u");
-    }
-
-    private static boolean supportsDelete(String[] path) {
-        return (path.length) >= 2 && path[1].contains("d");
-    }
-
-    private static boolean supportsRead(String[] path) {
-        return (path.length) >= 2 && path[1].contains("r");
-    }
-
-    private static boolean supportsWrite(String[] path) {
-        return (path.length) >= 2 && path[1].contains("w");
-    }
-
-    private String[] getColumns(Uri uri) {
-        try (Cursor c = mResolver.query(uri,
-                null, // projection
-                "1=2", // selection
-                null, // selection args
-                null // sort order
-                )) {
-            return c.getColumnNames();
-        }
-    }
-
-    private void checkQueryExecutable(Uri uri,
-            String[] projection, String selection,
-            String[] selectionArgs, String sortOrder) {
-        try {
-            try (Cursor c = mResolver.query(uri, projection, selection,
-                    selectionArgs, sortOrder)) {
-                c.moveToFirst();
-            }
-        } catch (Throwable th) {
-            addFailure("Query failed: URI=" + uri + " Message=" + th.getMessage(), th);
-        }
-        try {
-            // With CancellationSignal.
-            try (Cursor c = mResolver.query(uri, projection, selection,
-                    selectionArgs, sortOrder, new CancellationSignal())) {
-                c.moveToFirst();
-            }
-        } catch (Throwable th) {
-            addFailure("Query with cancel failed: URI=" + uri + " Message=" + th.getMessage(), th);
-        }
-        try {
-            // With limit.
-            try (Cursor c = mResolver.query(
-                    uri.buildUpon().appendQueryParameter(
-                            ContactsContract.LIMIT_PARAM_KEY, "0").build(),
-                    projection, selection, selectionArgs, sortOrder)) {
-                c.moveToFirst();
-            }
-        } catch (Throwable th) {
-            addFailure("Query with limit failed: URI=" + uri + " Message=" + th.getMessage(), th);
-        }
-
-        try {
-            // With account.
-            try (Cursor c = mResolver.query(
-                    uri.buildUpon()
-                            .appendQueryParameter(RawContacts.ACCOUNT_NAME, "a")
-                            .appendQueryParameter(RawContacts.ACCOUNT_TYPE, "b")
-                            .appendQueryParameter(RawContacts.DATA_SET, "c")
-                            .build(),
-                    projection, selection, selectionArgs, sortOrder)) {
-                c.moveToFirst();
-            }
-        } catch (Throwable th) {
-            addFailure("Query with limit failed: URI=" + uri + " Message=" + th.getMessage(), th);
-        }
-    }
-
-    private void checkQueryNotExecutable(Uri uri,
-            String[] projection, String selection,
-            String[] selectionArgs, String sortOrder) {
-        try {
-            try (Cursor c = mResolver.query(uri, projection, selection,
-                    selectionArgs, sortOrder)) {
-                c.moveToFirst();
-            }
-        } catch (Throwable th) {
-            // pass.
-            return;
-        }
-        addFailure("Query on " + uri + " expected to fail, but succeeded.", null);
-    }
-
-    /**
-     * Make sure all URLs are accessible with all arguments = null.
-     */
-    public void testSelect() {
-        for (String[] path : URIs) {
-            if (!supportsQuery(path)) continue;
-            try {
-                final Uri uri = getUri(path);
-
-                checkQueryExecutable(uri, // uri
-                        null, // projection
-                        null, // selection
-                        null, // selection args
-                        null // sort order
-                        );
-            } catch (Throwable th) {
-                addFailure("Failed: URI=" + path[0] + " Message=" + th.getMessage(), th);
-            }
-        }
-        failIfFailed();
-    }
-
-    public void testNoHiddenColumns() {
-        for (String[] path : URIs) {
-            if (!supportsQuery(path)) continue;
-            try {
-                final Uri uri = getUri(path);
-
-                for (String column : getColumns(uri)) {
-                    if (column.toLowerCase().startsWith(ContactsContract.HIDDEN_COLUMN_PREFIX)) {
-                        addFailure("Uri " + uri + " returned hidden column " + column, null);
-                    }
-                }
-            } catch (Throwable th) {
-                addFailure("Failed: URI=" + path[0] + " Message=" + th.getMessage(), th);
-            }
-        }
-        failIfFailed();
-    }
-
-// Temporarily disabled due to taking too much time.
-//    /**
-//     * Make sure all URLs are accessible with a projection.
-//     */
-//    public void testSelectWithProjection() {
-//        for (String[] path : URIs) {
-//            if (!supportsQuery(path)) continue;
-//            final Uri uri = getUri(path);
-//
-//            for (String column : getColumns(uri)) {
-//                // Some columns are not selectable alone due to bugs, and we don't want to fix them
-//                // in order to avoid expanding the differences between versions, so here're some
-//                // hacks to make it work...
-//
-//                String[] projection = {column};
-//
-//                final String u = path[0];
-//                if ((u.startsWith("content://com.android.contacts/status_updates")
-//                        || u.startsWith("content://com.android.contacts/profile/status_updates"))
-//                        && ("im_handle".equals(column)
-//                        || "im_account".equals(column)
-//                        || "protocol".equals(column)
-//                        || "custom_protocol".equals(column)
-//                        || "presence_raw_contact_id".equals(column)
-//                        )) {
-//                    // These columns only show up when the projection contains certain columns.
-//
-//                    projection = new String[]{"mode", column};
-//                } else if ((u.startsWith("content://com.android.contacts/search_suggest_query")
-//                        || u.startsWith("content://contacts/search_suggest_query"))
-//                        && "suggest_intent_action".equals(column)) {
-//                    // Can't be included in the projection due to a bug in GlobalSearchSupport.
-//                    continue;
-//                } else if (RawContacts.BACKUP_ID.equals(column)) {
-//                    // Some URIs don't support a projection with BAKCUP_ID only.
-//                    projection = new String[]{RawContacts.BACKUP_ID, RawContacts.SOURCE_ID};
-//                }
-//
-//                checkQueryExecutable(uri,
-//                        projection, // projection
-//                        null, // selection
-//                        null, // selection args
-//                        null // sort order
-//                );
-//            }
-//        }
-//        failIfFailed();
-//    }
-
-    /**
-     * Make sure all URLs are accessible with a selection.
-     */
-    public void testSelectWithSelection() {
-        for (String[] path : URIs) {
-            if (!supportsQuery(path)) continue;
-            final Uri uri = getUri(path);
-
-            checkQueryExecutable(uri,
-                    null, // projection
-                    "1=?", // selection
-                    ARG1, // , // selection args
-                    null // sort order
-            );
-        }
-        failIfFailed();
-    }
-
-//    /**
-//     * Make sure all URLs are accessible with a selection.
-//     */
-//    public void testSelectWithSelectionUsingColumns() {
-//        for (String[] path : URIs) {
-//            if (!supportsQuery(path)) continue;
-//            final Uri uri = getUri(path);
-//
-//            for (String column : getColumns(uri)) {
-//                checkQueryExecutable(uri,
-//                        null, // projection
-//                        column + "=?", // selection
-//                        ARG1, // , //  selection args
-//                        null // sort order
-//                );
-//            }
-//        }
-//        failIfFailed();
-//    }
-
-// Temporarily disabled due to taking too much time.
-//    /**
-//     * Make sure all URLs are accessible with an order-by.
-//     */
-//    public void testSelectWithSortOrder() {
-//        for (String[] path : URIs) {
-//            if (!supportsQuery(path)) continue;
-//            final Uri uri = getUri(path);
-//
-//            for (String column : getColumns(uri)) {
-//                checkQueryExecutable(uri,
-//                        null, // projection
-//                        "1=2", // selection
-//                        null, // , // selection args
-//                        column // sort order
-//                );
-//            }
-//        }
-//        failIfFailed();
-//    }
-
-    /**
-     * Make sure all URLs are accessible with all arguments.
-     */
-    public void testSelectWithAllArgs() {
-        for (String[] path : URIs) {
-            if (!supportsQuery(path)) continue;
-            final Uri uri = getUri(path);
-
-            final String[] projection = {getColumns(uri)[0]};
-
-            checkQueryExecutable(uri,
-                    projection, // projection
-                    "1=?", // selection
-                    ARG1, // , // selection args
-                    getColumns(uri)[0] // sort order
-            );
-        }
-        failIfFailed();
-    }
-
-    public void testNonSelect() {
-        for (String[] path : URIs) {
-            if (supportsQuery(path)) continue;
-            final Uri uri = getUri(path);
-
-            checkQueryNotExecutable(uri, // uri
-                    null, // projection
-                    null, // selection
-                    null, // selection args
-                    null // sort order
-            );
-        }
-        failIfFailed();
-    }
-
-    private static boolean supportsTimesContacted(String[] path) {
-        return path.length > 1 && path[1].contains("s");
-    }
-
-    private static boolean supportsTimesUsed(String[] path) {
-        return path.length > 1 && path[1].contains("t");
-    }
-
-    private void checkColumnAccessible(Uri uri, String column) {
-        try {
-            try (Cursor c = mResolver.query(
-                    uri, new String[]{column}, column + "=0", null, column
-            )) {
-                c.moveToFirst();
-            }
-        } catch (Throwable th) {
-            addFailure("Query failed: URI=" + uri + " Message=" + th.getMessage(), th);
-        }
-    }
-
-    /** Test for {@link #checkColumnAccessible} */
-    public void testCheckColumnAccessible() {
-        checkColumnAccessible(Contacts.CONTENT_URI, "x_times_contacted");
-        try {
-            failIfFailed();
-        } catch (AssertionFailedError expected) {
-            return; // expected.
-        }
-        fail("Failed to detect issue.");
-    }
-
-    private void checkColumnNotAccessibleInner(Uri uri, String[] projection, String selection,
-            String[] selectionArgs, String sortOrder) {
-        try {
-            try (Cursor c = mResolver.query(uri, projection, selection,
-                    selectionArgs, sortOrder)) {
-                c.moveToFirst();
-            }
-        } catch (IllegalArgumentException th) {
-            // pass.
-            return;
-        }
-        addFailure("Query on " + uri +
-                " expected to throw IllegalArgumentException, but succeeded.", null);
-    }
-
-    private void checkColumnNotAccessible(Uri uri, String column) {
-        checkColumnNotAccessibleInner(uri, new String[] {column}, null, null, null);
-        checkColumnNotAccessibleInner(uri, null, column + "=1", null, null);
-        checkColumnNotAccessibleInner(uri, null, null, null, /* order by */ column);
-    }
-
-    /** Test for {@link #checkColumnNotAccessible} */
-    public void testCheckColumnNotAccessible() {
-        checkColumnNotAccessible(Contacts.CONTENT_URI, "times_contacted");
-        try {
-            failIfFailed();
-        } catch (AssertionFailedError expected) {
-            return; // expected.
-        }
-        fail("Failed to detect issue.");
-    }
-
-    /**
-     * Make sure the x_ columns are not accessible.
-     */
-    public void testProhibitedColumns() {
-        for (String[] path : URIs) {
-            final Uri uri = getUri(path);
-            if (supportsTimesContacted(path)) {
-                checkColumnAccessible(uri, "times_contacted");
-                checkColumnAccessible(uri, "last_time_contacted");
-
-                checkColumnNotAccessible(uri, "X_times_contacted");
-                checkColumnNotAccessible(uri, "X_slast_time_contacted");
-            }
-            if (supportsTimesUsed(path)) {
-                checkColumnAccessible(uri, "times_used");
-                checkColumnAccessible(uri, "last_time_used");
-
-                checkColumnNotAccessible(uri, "X_times_used");
-                checkColumnNotAccessible(uri, "X_last_time_used");
-            }
-        }
-        failIfFailed();
-    }
-
-    private void checkExecutable(String operation, Uri uri, boolean shouldWork, Runnable r) {
-        if (shouldWork) {
-            try {
-                r.run();
-            } catch (Exception e) {
-                addFailure(operation + " for '" + uri + "' failed: " + e.getMessage(), e);
-            }
-        } else {
-            try {
-                r.run();
-                addFailure(operation + " for '" + uri + "' NOT failed.", null);
-            } catch (Exception expected) {
-            }
-        }
-    }
-
-    public void testAllOperations() {
-        final ContentValues cv = new ContentValues();
-
-        for (String[] path : URIs) {
-            final Uri uri = getUri(path);
-
-            try {
-                cv.clear();
-                if (supportsQuery(path)) {
-                    cv.put(getColumns(uri)[0], 1);
-                } else {
-                    cv.put("_id", 1);
-                }
-                if (uri.toString().contains("syncstate")) {
-                    cv.put(SyncState.ACCOUNT_NAME, "abc");
-                    cv.put(SyncState.ACCOUNT_TYPE, "def");
-                }
-
-                checkExecutable("insert", uri, supportsInsert(path), () -> {
-                    final Uri newUri = mResolver.insert(uri, cv);
-                    if (newUri == null) {
-                        addFailure("Insert for '" + uri + "' returned null.", null);
-                    } else {
-                        // "profile/raw_contacts/#" is missing update support.  too late to add, so
-                        // just skip.
-                        if (!newUri.toString().startsWith(
-                                "content://com.android.contacts/profile/raw_contacts/")) {
-                            checkExecutable("insert -> update", newUri, true, () -> {
-                                mResolver.update(newUri, cv, null, null);
-                            });
-                        }
-                        checkExecutable("insert -> delete", newUri, true, () -> {
-                            mResolver.delete(newUri, null, null);
-                        });
-                    }
-                });
-                checkExecutable("update", uri, supportsUpdate(path), () -> {
-                    mResolver.update(uri, cv, "1=2", null);
-                });
-                checkExecutable("delete", uri, supportsDelete(path), () -> {
-                    mResolver.delete(uri, "1=2", null);
-                });
-            } catch (Throwable th) {
-                addFailure("Failed: URI=" + uri + " Message=" + th.getMessage(), th);
-            }
-        }
-        failIfFailed();
-    }
-
-    public void testAllFileOperations() {
-        for (String[] path : URIs) {
-            final Uri uri = getUri(path);
-
-            checkExecutable("openInputStream", uri, supportsRead(path), () -> {
-                try (InputStream st = mResolver.openInputStream(uri)) {
-                } catch (FileNotFoundException e) {
-                    // TODO This happens because we try to read nonexistent photos.  Ideally
-                    // we should actually check it's readable.
-                    if (e.getMessage().contains("Stream I/O not supported")) {
-                        throw new RuntimeException("Caught Exception: " + e.toString(), e);
-                    }
-                } catch (Exception e) {
-                    throw new RuntimeException("Caught Exception: " + e.toString(), e);
-                }
-            });
-            checkExecutable("openOutputStream", uri, supportsWrite(path), () -> {
-                try (OutputStream st = mResolver.openOutputStream(uri)) {
-                } catch (Exception e) {
-                    throw new RuntimeException("Caught Exception: " + e.toString(), e);
-                }
-            });
-        }
-        failIfFailed();
-    }
-}
-
-