[automerger skipped] Import translations. DO NOT MERGE am: 2ebfe4131e -s ours

am skip reason: subject contains skip directive

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

Change-Id: Ia763141bf7a6cfdb52e50e7bdb722992daf9e519
diff --git a/Android.bp b/Android.bp
index b6bddce..8991628 100644
--- a/Android.bp
+++ b/Android.bp
@@ -6,8 +6,7 @@
         "src/com/android/providers/contacts/EventLogTags.logtags",
     ],
     libs: [
-        "ext",
-        "telephony-common",
+        "ext"
     ],
     static_libs: [
         "android-common",
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 2da56fc..9542e6d 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -11,6 +11,7 @@
     <uses-permission android:name="android.permission.PROCESS_PHONE_ACCOUNT_REGISTRATION" />
     <uses-permission android:name="android.permission.READ_CONTACTS" />
     <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+    <uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE" />
     <uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
     <uses-permission android:name="android.permission.SEND_CALL_LOG_CHANGE" />
     <uses-permission android:name="android.permission.UPDATE_APP_OPS_STATS" />
@@ -25,8 +26,8 @@
 
     <application android:process="android.process.acore"
         android:label="@string/app_label"
-        android:icon="@drawable/app_icon"
         android:allowBackup="false"
+        android:forceQueryable="true"
         android:usesCleartextTraffic="false">
 
         <provider android:name="ContactsProvider2"
diff --git a/TEST_MAPPING b/TEST_MAPPING
new file mode 100644
index 0000000..c66ff24
--- /dev/null
+++ b/TEST_MAPPING
@@ -0,0 +1,15 @@
+{
+    "presubmit": [
+        {
+            "name": "ContactsProviderTests"
+        },
+        {
+            "name": "CtsProviderTestCases",
+            "options": [
+                {
+                    "include-filter": "android.provider.cts.contacts."
+                }
+            ]
+        }
+    ]
+}
diff --git a/res/drawable-hdpi/app_icon.png b/res/drawable-hdpi/app_icon.png
deleted file mode 100644
index 64eff00..0000000
--- a/res/drawable-hdpi/app_icon.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/app_icon.png b/res/drawable-mdpi/app_icon.png
deleted file mode 100644
index b4ee821..0000000
--- a/res/drawable-mdpi/app_icon.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/app_icon.png b/res/drawable-xhdpi/app_icon.png
deleted file mode 100644
index 6feeadf..0000000
--- a/res/drawable-xhdpi/app_icon.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/app_icon.png b/res/drawable-xxhdpi/app_icon.png
deleted file mode 100644
index 01a3fde..0000000
--- a/res/drawable-xxhdpi/app_icon.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxxhdpi/app_icon.png b/res/drawable-xxxhdpi/app_icon.png
deleted file mode 100644
index 328e067..0000000
--- a/res/drawable-xxxhdpi/app_icon.png
+++ /dev/null
Binary files differ
diff --git a/src/com/android/providers/contacts/CallLogProvider.java b/src/com/android/providers/contacts/CallLogProvider.java
index ff3e65c..ec1d40e 100644
--- a/src/com/android/providers/contacts/CallLogProvider.java
+++ b/src/com/android/providers/contacts/CallLogProvider.java
@@ -183,6 +183,7 @@
     private CallLogDatabaseHelper mDbHelper;
     private DatabaseUtils.InsertHelper mCallsInserter;
     private boolean mUseStrictPhoneNumberComparation;
+    private int mMinMatch;
     private VoicemailPermissions mVoicemailPermissions;
     private CallLogInsertionHelper mCallLogInsertionHelper;
 
@@ -214,6 +215,9 @@
         mUseStrictPhoneNumberComparation =
             context.getResources().getBoolean(
                     com.android.internal.R.bool.config_use_strict_phone_number_comparation);
+        mMinMatch =
+            context.getResources().getInteger(
+                    com.android.internal.R.integer.config_phonenumber_compare_min_match);
         mVoicemailPermissions = new VoicemailPermissions(context);
         mCallLogInsertionHelper = createCallLogInsertionHelper(context);
 
@@ -239,6 +243,16 @@
         return DefaultCallLogInsertionHelper.getInstance(context);
     }
 
+    @VisibleForTesting
+    public void setMinMatchForTest(int minMatch) {
+        mMinMatch = minMatch;
+    }
+
+    @VisibleForTesting
+    public int getMinMatchForTest() {
+        return mMinMatch;
+    }
+
     protected CallLogDatabaseHelper getDatabaseHelper(final Context context) {
         return CallLogDatabaseHelper.getInstance(context);
     }
@@ -299,6 +313,7 @@
             Log.v(TAG, "query: uri=" + uri + "  projection=" + Arrays.toString(projection) +
                     "  selection=[" + selection + "]  args=" + Arrays.toString(selectionArgs) +
                     "  order=[" + sortOrder + "] CPID=" + Binder.getCallingPid() +
+                    " CUID=" + Binder.getCallingUid() +
                     " User=" + UserUtils.getCurrentUserHandle(getContext()));
         }
 
@@ -331,7 +346,8 @@
                 if (!TextUtils.isEmpty(phoneNumber)) {
                     qb.appendWhere("PHONE_NUMBERS_EQUAL(number, ");
                     qb.appendWhereEscapeString(phoneNumber);
-                    qb.appendWhere(mUseStrictPhoneNumberComparation ? ", 1)" : ", 0)");
+                    qb.appendWhere(mUseStrictPhoneNumberComparation ? ", 1)"
+                            : ", 0, " + mMinMatch + ")");
                 } else {
                     qb.appendWhere(Calls.NUMBER_PRESENTATION + "!="
                             + Calls.PRESENTATION_ALLOWED);
@@ -465,7 +481,8 @@
     private Uri insertInternal(Uri uri, ContentValues values) {
         if (VERBOSE_LOGGING) {
             Log.v(TAG, "insert: uri=" + uri + "  values=[" + values + "]" +
-                    " CPID=" + Binder.getCallingPid());
+                    " CPID=" + Binder.getCallingPid() +
+                    " CUID=" + Binder.getCallingUid());
         }
         waitForAccess(mReadAccessLatch);
         checkForSupportedColumns(sCallsProjectionMap, values);
@@ -498,6 +515,7 @@
             Log.v(TAG, "update: uri=" + uri +
                     "  selection=[" + selection + "]  args=" + Arrays.toString(selectionArgs) +
                     "  values=[" + values + "] CPID=" + Binder.getCallingPid() +
+                    " CUID=" + Binder.getCallingUid() +
                     " User=" + UserUtils.getCurrentUserHandle(getContext()));
         }
         waitForAccess(mReadAccessLatch);
@@ -534,6 +552,7 @@
             Log.v(TAG, "delete: uri=" + uri +
                     "  selection=[" + selection + "]  args=" + Arrays.toString(selectionArgs) +
                     " CPID=" + Binder.getCallingPid() +
+                    " CUID=" + Binder.getCallingUid() +
                     " User=" + UserUtils.getCurrentUserHandle(getContext()));
         }
         waitForAccess(mReadAccessLatch);
@@ -728,9 +747,8 @@
                     mDbHelper.getWritableDatabase().execSQL(
                             UNHIDE_BY_PHONE_ACCOUNT_QUERY, handleArgs);
                 } else {
-                    TelecomManager tm = TelecomManager.from(getContext());
+                    TelecomManager tm = getContext().getSystemService(TelecomManager.class);
                     if (tm != null) {
-
                         PhoneAccount account = tm.getPhoneAccount(handle);
                         if (account != null && account.getAddress() != null) {
                             // We did not find any items for the specific phone account, so run the
diff --git a/src/com/android/providers/contacts/ContactsDatabaseHelper.java b/src/com/android/providers/contacts/ContactsDatabaseHelper.java
index 4c521f4..2e5cdac 100644
--- a/src/com/android/providers/contacts/ContactsDatabaseHelper.java
+++ b/src/com/android/providers/contacts/ContactsDatabaseHelper.java
@@ -982,6 +982,7 @@
     private boolean mUseStrictPhoneNumberComparisonBase;
     private boolean mUseStrictPhoneNumberComparisonForRussia;
     private boolean mUseStrictPhoneNumberComparisonForKazakhstan;
+    private int mMinMatch;
 
     private String[] mSelectionArgs1 = new String[1];
     private NameSplitter.Name mName = new NameSplitter.Name();
@@ -1071,6 +1072,9 @@
         } else {
             mUseStrictPhoneNumberComparison = mUseStrictPhoneNumberComparisonBase;
         }
+
+        mMinMatch = mContext.getResources().getInteger(
+                com.android.internal.R.integer.config_phonenumber_compare_min_match);
     }
 
     private boolean getConfig(String configKey, int defaultResId) {
@@ -3217,8 +3221,7 @@
         //       ON_BOOT_COMPLETE instead of PRE_BOOT_COMPLETE.
         SubscriptionManager sm = SubscriptionManager.from(mContext);
         if (sm != null) {
-            Log.i(TAG, "count: " + sm.getAllSubscriptionInfoCount());
-            for (SubscriptionInfo info : sm.getAllSubscriptionInfoList()) {
+            for (SubscriptionInfo info : sm.getActiveSubscriptionInfoList()) {
                 String iccId = info.getIccId();
                 int subId = info.getSubscriptionId();
                 if (!TextUtils.isEmpty(iccId) &&
@@ -4239,7 +4242,7 @@
         sb.setLength(0);
         sb.append("PHONE_NUMBERS_EQUAL(" + Tables.DATA + "." + Phone.NUMBER + ", ");
         DatabaseUtils.appendEscapedSQLString(sb, number);
-        sb.append(mUseStrictPhoneNumberComparison ? ", 1)" : ", 0)");
+        sb.append(mUseStrictPhoneNumberComparison ? ", 1)" : ", 0, " + mMinMatch + ")");
         qb.appendWhere(sb.toString());
     }
 
@@ -4336,6 +4339,10 @@
         return mUseStrictPhoneNumberComparison ? "1" : "0";
     }
 
+    public String getMinMatchParameter() {
+        return String.valueOf(mMinMatch);
+    }
+
     /**
      * Loads common nickname mappings into the database.
      */
@@ -4954,6 +4961,16 @@
         return mUseStrictPhoneNumberComparison;
     }
 
+    @VisibleForTesting
+    public void setMinMatchForTest(int minMatch) {
+        mMinMatch = minMatch;
+    }
+
+    @VisibleForTesting
+    public int getMinMatchForTest() {
+        return mMinMatch;
+    }
+
     @NeededForTesting
     /* package */ String querySearchIndexContentForTest(long contactId) {
         return DatabaseUtils.stringForQuery(getReadableDatabase(),
diff --git a/src/com/android/providers/contacts/ContactsProvider2.java b/src/com/android/providers/contacts/ContactsProvider2.java
index 02c8bf0..5159fb9 100644
--- a/src/com/android/providers/contacts/ContactsProvider2.java
+++ b/src/com/android/providers/contacts/ContactsProvider2.java
@@ -16,6 +16,10 @@
 
 package com.android.providers.contacts;
 
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.Manifest.permission.INTERACT_ACROSS_USERS;
+import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
+
 import android.accounts.Account;
 import android.accounts.AccountManager;
 import android.accounts.OnAccountsUpdateListener;
@@ -214,7 +218,6 @@
 
     private static final String READ_PERMISSION = "android.permission.READ_CONTACTS";
     private static final String WRITE_PERMISSION = "android.permission.WRITE_CONTACTS";
-    private static final String INTERACT_ACROSS_USERS = "android.permission.INTERACT_ACROSS_USERS";
 
 
     /* package */ static final String PHONEBOOK_COLLATOR_NAME = "PHONEBOOK";
@@ -532,23 +535,6 @@
             " SET " + RawContacts.DIRTY + "=1" +
             " WHERE " + RawContacts._ID + " IN (";
 
-    /** Sql for updating METADATA_DIRTY flag on multiple raw contacts */
-    private static final String UPDATE_RAW_CONTACT_SET_METADATA_DIRTY_SQL =
-            "UPDATE " + Tables.RAW_CONTACTS +
-                    " SET " + RawContacts.METADATA_DIRTY + "=1" +
-                    " WHERE " + RawContacts._ID + " IN (";
-
-    // Sql for updating MetadataSync.DELETED flag on multiple raw contacts.
-    // When using this sql, add comma separated raw contacts ids and "))".
-    private static final String UPDATE_METADATASYNC_SET_DELETED_SQL =
-            "UPDATE " + Tables.METADATA_SYNC
-                    + " SET " + MetadataSync.DELETED + "=1"
-                    + " WHERE " + MetadataSync._ID + " IN "
-                            + "(SELECT " + MetadataSyncColumns.CONCRETE_ID
-                            + " FROM " + Tables.RAW_CONTACTS_JOIN_METADATA_SYNC
-                            + " WHERE " + RawContactsColumns.CONCRETE_DELETED + "=1 AND "
-                            + RawContactsColumns.CONCRETE_ID + " IN (";
-
     /** Sql for updating VERSION on multiple raw contacts */
     private static final String UPDATE_RAW_CONTACT_SET_VERSION_SQL =
             "UPDATE " + Tables.RAW_CONTACTS +
@@ -2433,14 +2419,6 @@
         for (long rawContactId : mTransactionContext.get().getInsertedRawContactIds()) {
             mDbHelper.get().updateRawContactDisplayName(db, rawContactId);
             mAggregator.get().onRawContactInsert(mTransactionContext.get(), db, rawContactId);
-            if (mMetadataSyncEnabled) {
-                updateMetadataOnRawContactInsert(db, rawContactId);
-            }
-        }
-        if (mMetadataSyncEnabled) {
-            for (long rawContactId : mTransactionContext.get().getBackupIdChangedRawContacts()) {
-                updateMetadataOnRawContactInsert(db, rawContactId);
-            }
         }
 
         final Set<Long> dirtyRawContacts = mTransactionContext.get().getDirtyRawContactIds();
@@ -2461,29 +2439,8 @@
             db.execSQL(mSb.toString());
         }
 
-        final Set<Long> metadataDirtyRawContacts =
-                mTransactionContext.get().getMetadataDirtyRawContactIds();
-        if (!metadataDirtyRawContacts.isEmpty() && mMetadataSyncEnabled) {
-            mSb.setLength(0);
-            mSb.append(UPDATE_RAW_CONTACT_SET_METADATA_DIRTY_SQL);
-            appendIds(mSb, metadataDirtyRawContacts);
-            mSb.append(")");
-            db.execSQL(mSb.toString());
-            mSyncToMetadataNetWork = true;
-        }
-
         final Set<Long> changedRawContacts = mTransactionContext.get().getChangedRawContactIds();
         ContactsTableUtil.updateContactLastUpdateByRawContactId(db, changedRawContacts);
-        if (!changedRawContacts.isEmpty() && mMetadataSyncEnabled) {
-            // For the deleted raw contact, set related metadata as deleted
-            // if metadata flag is enabled.
-            mSb.setLength(0);
-            mSb.append(UPDATE_METADATASYNC_SET_DELETED_SQL);
-            appendIds(mSb, changedRawContacts);
-            mSb.append("))");
-            db.execSQL(mSb.toString());
-            mSyncToMetadataNetWork = true;
-        }
 
         // Update sync states.
         for (Map.Entry<Long, Object> entry : mTransactionContext.get().getUpdatedSyncStates()) {
@@ -2502,49 +2459,6 @@
         mMetadataSyncEnabled = enabled;
     }
 
-    interface MetadataSyncQuery {
-        String TABLE = Tables.RAW_CONTACTS_JOIN_METADATA_SYNC;
-        String[] COLUMNS = new String[] {
-                MetadataSyncColumns.CONCRETE_ID,
-                MetadataSync.DATA
-        };
-        int METADATA_SYNC_ID = 0;
-        int METADATA_SYNC_DATA = 1;
-        String SELECTION = MetadataSyncColumns.CONCRETE_DELETED + "=0 AND " +
-                RawContactsColumns.CONCRETE_ID + "=?";
-    }
-
-    /**
-     * Fetch the related metadataSync data column for the raw contact id.
-     * Returns null if there's no metadata for the raw contact.
-     */
-    private String queryMetadataSyncData(SQLiteDatabase db, long rawContactId) {
-        String metadataSyncData = null;
-        mSelectionArgs1[0] = String.valueOf(rawContactId);
-        final Cursor cursor = db.query(MetadataSyncQuery.TABLE,
-                MetadataSyncQuery.COLUMNS, MetadataSyncQuery.SELECTION,
-                mSelectionArgs1, null, null, null);
-        try {
-            if (cursor.moveToFirst()) {
-                metadataSyncData = cursor.getString(MetadataSyncQuery.METADATA_SYNC_DATA);
-            }
-        } finally {
-            cursor.close();
-        }
-        return metadataSyncData;
-    }
-
-    private void updateMetadataOnRawContactInsert(SQLiteDatabase db, long rawContactId) {
-        // Read metadata from MetadataSync table for the raw contact, and update.
-        final String metadataSyncData = queryMetadataSyncData(db, rawContactId);
-        if (TextUtils.isEmpty(metadataSyncData)) {
-            return;
-        }
-        final MetadataEntry metadataEntry = MetadataEntryParser.parseDataToMetaDataEntry(
-                metadataSyncData);
-        updateFromMetaDataEntry(db, metadataEntry);
-    }
-
     /**
      * Appends comma separated IDs.
      * @param ids Should not be empty
@@ -2566,10 +2480,7 @@
 
     protected void notifyChange(boolean syncToNetwork, boolean syncToMetadataNetwork) {
         getContext().getContentResolver().notifyChange(ContactsContract.AUTHORITY_URI, null,
-                syncToNetwork || syncToMetadataNetwork);
-
-        getContext().getContentResolver().notifyChange(MetadataSync.METADATA_AUTHORITY_URI,
-                null, syncToMetadataNetwork);
+                syncToNetwork);
     }
 
     protected void setProviderStatus(int status) {
@@ -2606,7 +2517,8 @@
     protected Uri insertInTransaction(Uri uri, ContentValues values) {
         if (VERBOSE_LOGGING) {
             Log.v(TAG, "insertInTransaction: uri=" + uri + "  values=[" + values + "]" +
-                    " CPID=" + Binder.getCallingPid());
+                    " CPID=" + Binder.getCallingPid() +
+                    " CUID=" + Binder.getCallingUid());
         }
 
         final SQLiteDatabase db = mDbHelper.get().getWritableDatabase();
@@ -2836,7 +2748,6 @@
             values.put(RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_DISABLED);
         }
 
-        final boolean needToUpdateMetadata = shouldMarkMetadataDirtyForRawContact(values);
         // Databases that were created prior to the 906 upgrade have a default of Int.MAX_VALUE
         // for RawContacts.PINNED. Manually set the value to the correct default (0) if it is not
         // set.
@@ -2848,14 +2759,6 @@
         final SQLiteDatabase db = mDbHelper.get().getWritableDatabase();
         final long rawContactId = db.insert(Tables.RAW_CONTACTS, RawContacts.CONTACT_ID, values);
 
-        if (needToUpdateMetadata) {
-            mTransactionContext.get().markRawContactMetadataDirty(rawContactId,
-                    /* isMetadataSyncAdapter =*/false);
-        }
-        // If the new raw contact is inserted by a sync adapter, mark mSyncToMetadataNetWork as true
-        // so that it can trigger the metadata syncing from the server.
-        mSyncToMetadataNetWork |= callerIsSyncAdapter;
-
         final int aggregationMode = getIntValue(values, RawContacts.AGGREGATION_MODE,
                 RawContacts.AGGREGATION_MODE_DEFAULT);
         mAggregator.get().markNewForAggregation(rawContactId, aggregationMode);
@@ -3550,6 +3453,7 @@
             Log.v(TAG, "deleteInTransaction: uri=" + uri +
                     "  selection=[" + selection + "]  args=" + Arrays.toString(selectionArgs) +
                     " CPID=" + Binder.getCallingPid() +
+                    " CUID=" + Binder.getCallingUid() +
                     " User=" + UserUtils.getCurrentUserHandle(getContext()));
         }
 
@@ -3802,15 +3706,25 @@
     }
 
     private int deleteContact(long contactId, boolean callerIsSyncAdapter) {
+        ArrayList<Long> localRawContactIds = new ArrayList();
+
         final SQLiteDatabase db = mDbHelper.get().getWritableDatabase();
         mSelectionArgs1[0] = Long.toString(contactId);
         Cursor c = db.query(Tables.RAW_CONTACTS, new String[] {RawContacts._ID},
                 RawContacts.CONTACT_ID + "=?", mSelectionArgs1,
                 null, null, null);
+
+        // Raw contacts need to be deleted after the contact so just loop through and mark
+        // non-local raw contacts as deleted and collect the local raw contacts that will be
+        // deleted after the contact is deleted.
         try {
             while (c.moveToNext()) {
                 long rawContactId = c.getLong(0);
-                markRawContactAsDeleted(db, rawContactId, callerIsSyncAdapter);
+                if (rawContactIsLocal(rawContactId)) {
+                    localRawContactIds.add(rawContactId);
+                } else {
+                    markRawContactAsDeleted(db, rawContactId, callerIsSyncAdapter);
+                }
             }
         } finally {
             c.close();
@@ -3819,6 +3733,10 @@
         mProviderStatusUpdateNeeded = true;
 
         int result = ContactsTableUtil.deleteContact(db, contactId);
+
+        // Now purge the local raw contacts
+        deleteRawContactsImmediately(db, localRawContactIds);
+
         scheduleBackgroundTask(BACKGROUND_TASK_CLEAN_DELETE_LOG);
         return result;
     }
@@ -3842,19 +3760,19 @@
             c.close();
         }
 
+        // When a raw contact is deleted, a sqlite trigger deletes the parent contact.
+        // TODO: all contact deletes was consolidated into ContactTableUtil but this one can't
+        // because it's in a trigger.  Consider removing trigger and replacing with java code.
+        // This has to happen before the raw contact is deleted since it relies on the number
+        // of raw contacts.
         final boolean contactIsSingleton =
                 ContactsTableUtil.deleteContactIfSingleton(db, rawContactId) == 1;
         final int count;
 
         if (callerIsSyncAdapter || rawContactIsLocal(rawContactId)) {
-            // When a raw contact is deleted, a SQLite trigger deletes the parent contact.
-            // TODO: all contact deletes was consolidated into ContactTableUtil but this one can't
-            // because it's in a trigger.  Consider removing trigger and replacing with java code.
-            // This has to happen before the raw contact is deleted since it relies on the number
-            // of raw contacts.
-            db.delete(Tables.PRESENCE, PresenceColumns.RAW_CONTACT_ID + "=" + rawContactId, null);
-            count = db.delete(Tables.RAW_CONTACTS, RawContacts._ID + "=" + rawContactId, null);
-            mTransactionContext.get().markRawContactChangedOrDeletedOrInserted(rawContactId);
+            ArrayList<Long> rawContactsIds = new ArrayList<>();
+            rawContactsIds.add(rawContactId);
+            count = deleteRawContactsImmediately(db, rawContactsIds);
         } else {
             count = markRawContactAsDeleted(db, rawContactId, callerIsSyncAdapter);
         }
@@ -3865,6 +3783,43 @@
     }
 
     /**
+     * Returns the number of raw contacts that were deleted immediately -- we don't merely set
+     * the DELETED column to 1, the entire raw contact row is deleted straightaway.
+     */
+    private int deleteRawContactsImmediately(SQLiteDatabase db, List<Long> rawContactIds) {
+        if (rawContactIds == null || rawContactIds.isEmpty()) {
+            return 0;
+        }
+
+        // Build the where clause for the raw contacts to be deleted
+        ArrayList<String> whereArgs = new ArrayList<>();
+        StringBuilder whereClause = new StringBuilder(rawContactIds.size() * 2 - 1);
+        whereClause.append(" IN (?");
+        whereArgs.add(String.valueOf(rawContactIds.get(0)));
+        for (int i = 1; i < rawContactIds.size(); i++) {
+            whereClause.append(",?");
+            whereArgs.add(String.valueOf(rawContactIds.get(i)));
+        }
+        whereClause.append(")");
+
+        // Remove presence rows
+        db.delete(Tables.PRESENCE, PresenceColumns.RAW_CONTACT_ID + whereClause.toString(),
+                whereArgs.toArray(new String[0]));
+
+        // Remove raw contact rows
+        int result = db.delete(Tables.RAW_CONTACTS, RawContacts._ID + whereClause.toString(),
+                whereArgs.toArray(new String[0]));
+
+        if (result > 0) {
+            for (Long rawContactId : rawContactIds) {
+                mTransactionContext.get().markRawContactChangedOrDeletedOrInserted(rawContactId);
+            }
+        }
+
+        return result;
+    }
+
+    /**
      * Returns whether the given raw contact ID is local (i.e. has no account associated with it).
      */
     private boolean rawContactIsLocal(long rawContactId) {
@@ -3964,6 +3919,7 @@
             Log.v(TAG, "updateInTransaction: uri=" + uri +
                     "  selection=[" + selection + "]  args=" + Arrays.toString(selectionArgs) +
                     "  values=[" + values + "] CPID=" + Binder.getCallingPid() +
+                    " CUID=" + Binder.getCallingUid() +
                     " User=" + UserUtils.getCurrentUserHandle(getContext()));
         }
 
@@ -4498,7 +4454,6 @@
         final boolean isDataSetChanging = values.containsKey(RawContacts.DATA_SET);
         final boolean isAccountChanging =
                 isAccountNameChanging || isAccountTypeChanging || isDataSetChanging;
-        final boolean isBackupIdChanging = values.containsKey(RawContacts.BACKUP_ID);
 
         int previousDeleted = 0;
         long accountId = 0;
@@ -4561,32 +4516,6 @@
             if (aggregationMode != RawContacts.AGGREGATION_MODE_DEFAULT) {
                 aggregator.markForAggregation(rawContactId, aggregationMode, false);
             }
-            if (shouldMarkMetadataDirtyForRawContact(values)) {
-                mTransactionContext.get().markRawContactMetadataDirty(
-                        rawContactId, callerIsMetadataSyncAdapter);
-            }
-            if (isBackupIdChanging) {
-                Cursor cursor = db.query(Tables.RAW_CONTACTS,
-                        new String[] {RawContactsColumns.CONCRETE_METADATA_DIRTY},
-                        selection, mSelectionArgs1, null, null, null);
-                int metadataDirty = 0;
-                try {
-                    if (cursor.moveToFirst()) {
-                        metadataDirty = cursor.getInt(0);
-                    }
-                } finally {
-                    cursor.close();
-                }
-
-                if (metadataDirty == 1) {
-                    // Re-notify metadata network if backup_id is updated and metadata is dirty.
-                    mTransactionContext.get().markRawContactMetadataDirty(
-                            rawContactId, callerIsMetadataSyncAdapter);
-                } else {
-                    // Merge from metadata sync table if backup_id is updated and no dirty change.
-                    mTransactionContext.get().markBackupIdChangedRawContact(rawContactId);
-                }
-            }
             if (flagExists(values, RawContacts.STARRED)) {
                 if (!callerIsSyncAdapter) {
                     updateFavoritesMembership(rawContactId, flagIsSet(values, RawContacts.STARRED));
@@ -4760,10 +4689,6 @@
             // Mark dirty when changing starred to trigger sync.
             values.put(RawContacts.DIRTY, 1);
         }
-        if (mMetadataSyncEnabled && (hasStarredValue || hasPinnedValue || hasVoiceMailValue)) {
-            // Mark dirty to trigger metadata syncing.
-            values.put(RawContacts.METADATA_DIRTY, 1);
-        }
 
         mSelectionArgs1[0] = String.valueOf(contactId);
         db.update(Tables.RAW_CONTACTS, values, RawContacts.CONTACT_ID + "=?"
@@ -4781,11 +4706,6 @@
                                 flagIsSet(values, RawContacts.STARRED));
                         mSyncToNetwork |= !callerIsSyncAdapter;
                     }
-
-                    if (hasStarredValue || hasPinnedValue || hasVoiceMailValue) {
-                        mTransactionContext.get().markRawContactMetadataDirty(rawContactId,
-                                false /*callerIsMetadataSyncAdapter*/);
-                    }
                 }
             } finally {
                 cursor.close();
@@ -4863,21 +4783,12 @@
 
         aggregator.aggregateContact(mTransactionContext.get(), db, rawContactId1);
         aggregator.aggregateContact(mTransactionContext.get(), db, rawContactId2);
-        mTransactionContext.get().markRawContactMetadataDirty(rawContactId1,
-                callerIsMetadataSyncAdapter);
-        mTransactionContext.get().markRawContactMetadataDirty(rawContactId2,
-                callerIsMetadataSyncAdapter);
 
         // The return value is fake - we just confirm that we made a change, not count actual
         // rows changed.
         return 1;
     }
 
-    private boolean shouldMarkMetadataDirtyForRawContact(ContentValues values) {
-        return (flagExists(values, RawContacts.STARRED) || flagExists(values, RawContacts.PINNED)
-                || flagExists(values, RawContacts.SEND_TO_VOICEMAIL));
-    }
-
     @Override
     public void onAccountsUpdated(Account[] accounts) {
         scheduleBackgroundTask(BACKGROUND_TASK_UPDATE_ACCOUNTS);
@@ -5441,6 +5352,7 @@
             Log.v(TAG, "query: uri=" + uri + "  projection=" + Arrays.toString(projection) +
                     "  selection=[" + selection + "]  args=" + Arrays.toString(selectionArgs) +
                     "  order=[" + sortOrder + "] CPID=" + Binder.getCallingPid() +
+                    " CUID=" + Binder.getCallingUid() +
                     " User=" + UserUtils.getCurrentUserHandle(getContext()));
         }
 
@@ -5454,10 +5366,12 @@
             return null;
         }
 
-        // Check enterprise policy if caller does not come from same profile
-        if (!(isCallerFromSameUser() || mEnterprisePolicyGuard.isCrossProfileAllowed(uri))) {
-            return createEmptyCursor(uri, projection);
+        // If caller does not come from same profile, Check if it's privileged or allowed by
+        // enterprise policy
+        if (!queryAllowedByEnterprisePolicy(uri)) {
+            return null;
         }
+
         // Query the profile DB if appropriate.
         if (mapsToProfileDb(uri)) {
             switchToProfileMode();
@@ -5477,9 +5391,46 @@
         }
     }
 
+    private boolean queryAllowedByEnterprisePolicy(Uri uri) {
+        if (isCallerFromSameUser()) {
+            // Caller is on the same user; query allowed.
+            return true;
+        }
+        if (!doesCallerHoldInteractAcrossUserPermission()) {
+            // Cross-user and the caller has no INTERACT_ACROSS_USERS; don't allow query.
+            // Technically, in a cross-profile sharing case, this would be a valid query.
+            // But for now we don't allow it. (We never allowe it and no one complained about it.)
+            return false;
+        }
+        if (isCallerAnotherSelf()) {
+            // The caller is the other CP2 (which has INTERACT_ACROSS_USERS), meaning the reuest
+            // is on behalf of a "real" client app.
+            // Consult the enterprise policy.
+            return mEnterprisePolicyGuard.isCrossProfileAllowed(uri);
+        }
+        return true;
+    }
+
     private boolean isCallerFromSameUser() {
-        return Binder.getCallingUserHandle().getIdentifier() == UserUtils
-                .getCurrentUserHandle(getContext());
+        return UserHandle.getUserId(Binder.getCallingUid()) == UserHandle.myUserId();
+    }
+
+    /**
+     * Returns true if called by a different user's CP2.
+     */
+    private boolean isCallerAnotherSelf() {
+        // Note normally myUid is always different from the callerUid in the code path where
+        // this method is used, except during unit tests, where the caller is always the same
+        // process.
+        final int myUid = android.os.Process.myUid();
+        final int callingUid = Binder.getCallingUid();
+        return (myUid != callingUid) && UserHandle.isSameApp(myUid, callingUid);
+    }
+
+    private boolean doesCallerHoldInteractAcrossUserPermission() {
+        final Context context = getContext();
+        return context.checkCallingPermission(INTERACT_ACROSS_USERS_FULL) == PERMISSION_GRANTED
+                || context.checkCallingPermission(INTERACT_ACROSS_USERS) == PERMISSION_GRANTED;
     }
 
     private Cursor queryDirectoryIfNecessary(Uri uri, String[] projection, String selection,
@@ -5526,7 +5477,7 @@
 
     private String getRealCallerPackageName(Uri queryUri) {
         // If called by another CP2, then the URI should contain the original package name.
-        if (calledByAnotherSelf()) {
+        if (isCallerAnotherSelf()) {
             final String passedPackage = queryUri.getQueryParameter(
                     Directory.CALLER_PACKAGE_PARAM_KEY);
             if (TextUtils.isEmpty(passedPackage)) {
@@ -5541,18 +5492,6 @@
         }
     }
 
-    /**
-     * Returns true if called by a different user's CP2.
-     */
-    private boolean calledByAnotherSelf() {
-        // Note normally myUid is always different from the callerUid in the code path where
-        // this method is used, except during unit tests, where the caller is always the same
-        // process.
-        final int myUid = android.os.Process.myUid();
-        final int callerUid = Binder.getCallingUid();
-        return (myUid != callerUid) && UserHandle.isSameApp(myUid, callerUid);
-    }
-
     private Cursor queryDirectoryAuthority(Uri uri, String[] projection, String selection,
             String[] selectionArgs, String sortOrder, String directory,
             final CancellationSignal cancellationSignal) {
@@ -8525,9 +8464,7 @@
             if (!isDirectoryParamValid(uri)){
                 return null;
             }
-            if (!isCallerFromSameUser() /* From differnt user */
-                    && !mEnterprisePolicyGuard.isCrossProfileAllowed(uri)
-                    /* Policy not allowed */){
+            if (!queryAllowedByEnterprisePolicy(uri)) {
                 return null;
             }
             waitForAccess(mode.equals("r") ? mReadAccessLatch : mWriteAccessLatch);
@@ -8545,6 +8482,7 @@
             if (VERBOSE_LOGGING) {
                 Log.v(TAG, "openAssetFile uri=" + uri + " mode=" + mode + " success=" + success +
                         " CPID=" + Binder.getCallingPid() +
+                        " CUID=" + Binder.getCallingUid() +
                         " User=" + UserUtils.getCurrentUserHandle(getContext()));
             }
         }
@@ -9664,24 +9602,15 @@
     @VisibleForTesting
     protected boolean isPhone() {
         if (!mIsPhoneInitialized) {
-            mIsPhone = new TelephonyManager(getContext()).isVoiceCapable();
+            mIsPhone = isVoiceCapable();
             mIsPhoneInitialized = true;
         }
         return mIsPhone;
     }
 
     protected boolean isVoiceCapable() {
-        // this copied from com.android.phone.PhoneApp.onCreate():
-
-        // "voice capable" flag.
-        // This flag currently comes from a resource (which is
-        // overrideable on a per-product basis):
-        return getContext().getResources()
-                .getBoolean(com.android.internal.R.bool.config_voice_capable);
-        // ...but this might eventually become a PackageManager "system
-        // feature" instead, in which case we'd do something like:
-        // return
-        //   getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY_VOICE_CALLS);
+        TelephonyManager tm = getContext().getSystemService(TelephonyManager.class);
+        return tm.isVoiceCapable();
     }
 
     private void undemoteContact(SQLiteDatabase db, long id) {
@@ -9828,7 +9757,7 @@
      * @return the currently active {@link ContactsDatabaseHelper} for the current thread.
      */
     @NeededForTesting
-    protected ContactsDatabaseHelper getThreadActiveDatabaseHelperForTest() {
+    public ContactsDatabaseHelper getThreadActiveDatabaseHelperForTest() {
         return mDbHelper.get();
     }
 
diff --git a/src/com/android/providers/contacts/MetadataEntryParser.java b/src/com/android/providers/contacts/MetadataEntryParser.java
index 1a630a3..2fe423a 100644
--- a/src/com/android/providers/contacts/MetadataEntryParser.java
+++ b/src/com/android/providers/contacts/MetadataEntryParser.java
@@ -60,8 +60,11 @@
 
     @NeededForTesting
     public static class UsageStats {
+        @NeededForTesting
         final String mUsageType;
+        @NeededForTesting
         final long mLastTimeUsed;
+        @NeededForTesting
         final int mTimesUsed;
 
         @NeededForTesting
@@ -74,9 +77,13 @@
 
     @NeededForTesting
     public static class FieldData {
+        @NeededForTesting
         final String mDataHashId;
+        @NeededForTesting
         final boolean mIsPrimary;
+        @NeededForTesting
         final boolean mIsSuperPrimary;
+        @NeededForTesting
         final ArrayList<UsageStats> mUsageStatsList;
 
         @NeededForTesting
@@ -91,9 +98,13 @@
 
     @NeededForTesting
     public static class RawContactInfo {
+        @NeededForTesting
         final String mBackupId;
+        @NeededForTesting
         final String mAccountType;
+        @NeededForTesting
         final String mAccountName;
+        @NeededForTesting
         final String mDataSet;
 
         @NeededForTesting
@@ -108,8 +119,11 @@
 
     @NeededForTesting
     public static class AggregationData {
+        @NeededForTesting
         final RawContactInfo mRawContactInfo1;
+        @NeededForTesting
         final RawContactInfo mRawContactInfo2;
+        @NeededForTesting
         final String mType;
 
         @NeededForTesting
@@ -123,11 +137,17 @@
 
     @NeededForTesting
     public static class MetadataEntry {
+        @NeededForTesting
         final RawContactInfo mRawContactInfo;
+        @NeededForTesting
         final int mSendToVoicemail;
+        @NeededForTesting
         final int mStarred;
+        @NeededForTesting
         final int mPinned;
+        @NeededForTesting
         final ArrayList<FieldData> mFieldDatas;
+        @NeededForTesting
         final ArrayList<AggregationData> mAggregationDatas;
 
         @NeededForTesting
diff --git a/src/com/android/providers/contacts/VoicemailContentProvider.java b/src/com/android/providers/contacts/VoicemailContentProvider.java
index 1ced1be..daecd97 100644
--- a/src/com/android/providers/contacts/VoicemailContentProvider.java
+++ b/src/com/android/providers/contacts/VoicemailContentProvider.java
@@ -22,6 +22,7 @@
 import static com.android.providers.contacts.util.DbQueryUtils.getEqualityClause;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.AppOpsManager;
 import android.content.ContentProvider;
 import android.content.ContentValues;
@@ -118,24 +119,24 @@
     }
 
     @Override
-    protected int enforceReadPermissionInner(Uri uri, String callingPkg, IBinder callerToken)
-            throws SecurityException {
+    protected int enforceReadPermissionInner(Uri uri, String callingPkg,
+            @Nullable String featureId, IBinder callerToken) throws SecurityException {
         // Permit carrier-privileged apps regardless of ADD_VOICEMAIL permission state.
         if (mVoicemailPermissions.callerHasCarrierPrivileges()) {
             return MODE_ALLOWED;
         }
-        return super.enforceReadPermissionInner(uri, callingPkg, callerToken);
+        return super.enforceReadPermissionInner(uri, callingPkg, featureId, callerToken);
     }
 
 
     @Override
-    protected int enforceWritePermissionInner(Uri uri, String callingPkg, IBinder callerToken)
-            throws SecurityException {
+    protected int enforceWritePermissionInner(Uri uri, String callingPkg,
+            @Nullable String featureId, IBinder callerToken) throws SecurityException {
         // Permit carrier-privileged apps regardless of ADD_VOICEMAIL permission state.
         if (mVoicemailPermissions.callerHasCarrierPrivileges()) {
             return MODE_ALLOWED;
         }
-        return super.enforceWritePermissionInner(uri, callingPkg, callerToken);
+        return super.enforceWritePermissionInner(uri, callingPkg, featureId, callerToken);
     }
 
     @VisibleForTesting
@@ -179,7 +180,8 @@
     public Uri insert(Uri uri, ContentValues values) {
         if (VERBOSE_LOGGING) {
             Log.v(TAG, "insert: uri=" + uri + "  values=[" + values + "]" +
-                    " CPID=" + Binder.getCallingPid());
+                    " CPID=" + Binder.getCallingPid() +
+                    " CUID=" + Binder.getCallingUid());
         }
         UriData uriData = checkPermissionsAndCreateUriDataForWrite(uri, values);
         return getTableDelegate(uriData).insert(uriData, values);
@@ -198,6 +200,7 @@
             Log.v(TAG, "query: uri=" + uri + "  projection=" + Arrays.toString(projection) +
                     "  selection=[" + selection + "]  args=" + Arrays.toString(selectionArgs) +
                     "  order=[" + sortOrder + "] CPID=" + Binder.getCallingPid() +
+                    " CUID=" + Binder.getCallingUid() +
                     " User=" + UserUtils.getCurrentUserHandle(getContext()));
         }
         UriData uriData = checkPermissionsAndCreateUriDataForRead(uri);
@@ -213,6 +216,7 @@
             Log.v(TAG, "update: uri=" + uri +
                     "  selection=[" + selection + "]  args=" + Arrays.toString(selectionArgs) +
                     "  values=[" + values + "] CPID=" + Binder.getCallingPid() +
+                    " CUID=" + Binder.getCallingUid() +
                     " User=" + UserUtils.getCurrentUserHandle(getContext()));
         }
         UriData uriData = checkPermissionsAndCreateUriDataForWrite(uri, values);
@@ -228,6 +232,7 @@
             Log.v(TAG, "delete: uri=" + uri +
                     "  selection=[" + selection + "]  args=" + Arrays.toString(selectionArgs) +
                     " CPID=" + Binder.getCallingPid() +
+                    " CUID=" + Binder.getCallingUid() +
                     " User=" + UserUtils.getCurrentUserHandle(getContext()));
         }
         UriData uriData = checkPermissionsAndCreateUriDataForWrite(uri);
@@ -254,6 +259,7 @@
             if (VERBOSE_LOGGING) {
                 Log.v(TAG, "openFile uri=" + uri + " mode=" + mode + " success=" + success +
                         " CPID=" + Binder.getCallingPid() +
+                        " CUID=" + Binder.getCallingUid() +
                         " User=" + UserUtils.getCurrentUserHandle(getContext()));
             }
         }
diff --git a/src/com/android/providers/contacts/VoicemailPermissions.java b/src/com/android/providers/contacts/VoicemailPermissions.java
index 7e409ea..58e7a14 100644
--- a/src/com/android/providers/contacts/VoicemailPermissions.java
+++ b/src/com/android/providers/contacts/VoicemailPermissions.java
@@ -18,8 +18,9 @@
 
 import android.content.Context;
 import android.os.Binder;
-import android.telecom.DefaultDialerManager;
+import android.telecom.TelecomManager;
 import android.telephony.TelephonyManager;
+import android.text.TextUtils;
 
 import com.android.providers.contacts.util.ContactsPermissions;
 
@@ -41,9 +42,20 @@
                 || callerHasCarrierPrivileges();
     }
 
+    private boolean isDefaultOrSystemDialer(String callingPackage) {
+        // Note: Mimics previous dependency on DefaultDialerManager; that code just returns false
+        // here if the calling package is empty.
+        if (TextUtils.isEmpty(callingPackage)) {
+            return false;
+        }
+        TelecomManager tm = mContext.getSystemService(TelecomManager.class);
+        return (callingPackage.equals(tm.getDefaultDialerPackage())
+                || callingPackage.equals(tm.getSystemDialerPackage()));
+    }
+
     /** Determine if the calling process has full read access to all voicemails. */
     public boolean callerHasReadAccess(String callingPackage) {
-        if (DefaultDialerManager.isDefaultOrSystemDialer(mContext, callingPackage)) {
+        if (isDefaultOrSystemDialer(callingPackage)) {
             return true;
         }
         return callerHasPermission(android.Manifest.permission.READ_VOICEMAIL);
@@ -52,7 +64,7 @@
     /** Determine if the calling process has the permission required to update and remove all
      * voicemails */
     public boolean callerHasWriteAccess(String callingPackage) {
-        if (DefaultDialerManager.isDefaultOrSystemDialer(mContext, callingPackage)) {
+        if (isDefaultOrSystemDialer(callingPackage)) {
             return true;
         }
         return callerHasPermission(android.Manifest.permission.WRITE_VOICEMAIL);
diff --git a/src/com/android/providers/contacts/aggregation/AbstractContactAggregator.java b/src/com/android/providers/contacts/aggregation/AbstractContactAggregator.java
index 965780e..82e35f1 100644
--- a/src/com/android/providers/contacts/aggregation/AbstractContactAggregator.java
+++ b/src/com/android/providers/contacts/aggregation/AbstractContactAggregator.java
@@ -174,6 +174,7 @@
 
     protected String[] mSelectionArgs1 = new String[1];
     protected String[] mSelectionArgs2 = new String[2];
+    protected String[] mSelectionArgs3 = new String[3];
 
     protected long mMimeTypeIdIdentity;
     protected long mMimeTypeIdEmail;
@@ -794,8 +795,8 @@
                 " AND d1." + Data.RAW_CONTACT_ID + " IN (" + rawContactIdSet1 + ")" +
                 " AND d2." + Data.RAW_CONTACT_ID + " IN (" + rawContactIdSet2 + ")" +
                 " AND PHONE_NUMBERS_EQUAL(d1." + Phone.NUMBER + ",d2." + Phone.NUMBER + "," +
-                        String.valueOf(mDbHelper.getUseStrictPhoneNumberComparisonParameter()) +
-                        ")";
+                        (mDbHelper.getUseStrictPhoneNumberComparisonParameter().equals("1") ? "1)"
+                         : "0," + mDbHelper.getMinMatchParameter() + ")");
         return (countOnly) ? RawContactMatchingSelectionStatement.SELECT_COUNT + sql :
                 RawContactMatchingSelectionStatement.SELECT_ID + sql;
     }
@@ -1195,6 +1196,12 @@
                 + " AND " + RawContactsColumns.AGGREGATION_NEEDED + "=0"
                 + " AND " + RawContacts.CONTACT_ID + " IN " + Tables.DEFAULT_DIRECTORY;
 
+        String SELECTION_MIN_MATCH = "dataA." + Data.RAW_CONTACT_ID + "=?"
+                + " AND PHONE_NUMBERS_EQUAL(dataA." + Phone.NUMBER + ", "
+                        + "dataB." + Phone.NUMBER + ",?,?)"
+                + " AND " + RawContactsColumns.AGGREGATION_NEEDED + "=0"
+                + " AND " + RawContacts.CONTACT_ID + " IN " + Tables.DEFAULT_DIRECTORY;
+
         String[] COLUMNS = new String[] {
                 Tables.RAW_CONTACTS + "." + RawContacts._ID,
                 RawContacts.CONTACT_ID,
diff --git a/src/com/android/providers/contacts/aggregation/ContactAggregator.java b/src/com/android/providers/contacts/aggregation/ContactAggregator.java
index e5bd2ea..a104339 100644
--- a/src/com/android/providers/contacts/aggregation/ContactAggregator.java
+++ b/src/com/android/providers/contacts/aggregation/ContactAggregator.java
@@ -800,11 +800,25 @@
 
     private void updateMatchScoresBasedOnPhoneMatches(SQLiteDatabase db, long rawContactId,
             ContactMatcher matcher) {
-        mSelectionArgs2[0] = String.valueOf(rawContactId);
-        mSelectionArgs2[1] = mDbHelper.getUseStrictPhoneNumberComparisonParameter();
-        Cursor c = db.query(PhoneLookupQuery.TABLE, PhoneLookupQuery.COLUMNS,
+        Cursor c;
+        String useStrictPhoneNumberComparison =
+                mDbHelper.getUseStrictPhoneNumberComparisonParameter();
+
+        if (useStrictPhoneNumberComparison.equals("1")) {
+            mSelectionArgs2[0] = String.valueOf(rawContactId);
+            mSelectionArgs2[1] = useStrictPhoneNumberComparison;
+            c = db.query(PhoneLookupQuery.TABLE, PhoneLookupQuery.COLUMNS,
                 PhoneLookupQuery.SELECTION,
                 mSelectionArgs2, null, null, null, SECONDARY_HIT_LIMIT_STRING);
+        } else {
+            mSelectionArgs3[0] = String.valueOf(rawContactId);
+            mSelectionArgs3[1] = useStrictPhoneNumberComparison;
+            mSelectionArgs3[2] = mDbHelper.getMinMatchParameter();
+            c = db.query(PhoneLookupQuery.TABLE, PhoneLookupQuery.COLUMNS,
+                PhoneLookupQuery.SELECTION_MIN_MATCH,
+                mSelectionArgs3, null, null, null, SECONDARY_HIT_LIMIT_STRING);
+        }
+
         try {
             while (c.moveToNext()) {
                 long contactId = c.getLong(PhoneLookupQuery.CONTACT_ID);
diff --git a/src/com/android/providers/contacts/aggregation/ContactAggregator2.java b/src/com/android/providers/contacts/aggregation/ContactAggregator2.java
index e0bc3bb..6b1424c 100644
--- a/src/com/android/providers/contacts/aggregation/ContactAggregator2.java
+++ b/src/com/android/providers/contacts/aggregation/ContactAggregator2.java
@@ -665,11 +665,25 @@
 
     private void updateMatchScoresBasedOnPhoneMatches(SQLiteDatabase db, long rawContactId,
             RawContactMatcher matcher) {
-        mSelectionArgs2[0] = String.valueOf(rawContactId);
-        mSelectionArgs2[1] = mDbHelper.getUseStrictPhoneNumberComparisonParameter();
-        Cursor c = db.query(PhoneLookupQuery.TABLE, PhoneLookupQuery.COLUMNS,
+        Cursor c;
+        String useStrictPhoneNumberComparison =
+                mDbHelper.getUseStrictPhoneNumberComparisonParameter();
+
+        if (useStrictPhoneNumberComparison.equals("1")) {
+            mSelectionArgs2[0] = String.valueOf(rawContactId);
+            mSelectionArgs2[1] = useStrictPhoneNumberComparison;
+            c = db.query(PhoneLookupQuery.TABLE, PhoneLookupQuery.COLUMNS,
                 PhoneLookupQuery.SELECTION,
                 mSelectionArgs2, null, null, null, SECONDARY_HIT_LIMIT_STRING);
+        } else {
+            mSelectionArgs3[0] = String.valueOf(rawContactId);
+            mSelectionArgs3[1] = useStrictPhoneNumberComparison;
+            mSelectionArgs3[2] = mDbHelper.getMinMatchParameter();
+            c = db.query(PhoneLookupQuery.TABLE, PhoneLookupQuery.COLUMNS,
+                PhoneLookupQuery.SELECTION_MIN_MATCH,
+                mSelectionArgs3, null, null, null, SECONDARY_HIT_LIMIT_STRING);
+        }
+
         try {
             while (c.moveToNext()) {
                 long rId = c.getLong(PhoneLookupQuery.RAW_CONTACT_ID);
@@ -1029,6 +1043,11 @@
                 + "dataB." + Phone.NUMBER + ",?)"
                 + " AND " + RawContacts.CONTACT_ID + " IN " + Tables.DEFAULT_DIRECTORY;
 
+        String SELECTION_MIN_MATCH = "dataA." + Data.RAW_CONTACT_ID + "=?"
+                + " AND PHONE_NUMBERS_EQUAL(dataA." + Phone.NUMBER + ", "
+                + "dataB." + Phone.NUMBER + ",?,?)"
+                + " AND " + RawContacts.CONTACT_ID + " IN " + Tables.DEFAULT_DIRECTORY;
+
         String[] COLUMNS = new String[] {
                 Tables.RAW_CONTACTS + "." + RawContacts._ID,
                 RawContacts.CONTACT_ID,
diff --git a/test_common/src/com/android/providers/contacts/testutil/DatabaseAsserts.java b/test_common/src/com/android/providers/contacts/testutil/DatabaseAsserts.java
index 2af9829..e0a7836 100644
--- a/test_common/src/com/android/providers/contacts/testutil/DatabaseAsserts.java
+++ b/test_common/src/com/android/providers/contacts/testutil/DatabaseAsserts.java
@@ -16,6 +16,7 @@
 
 package com.android.providers.contacts.testutil;
 
+import android.accounts.Account;
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.net.Uri;
@@ -50,12 +51,21 @@
     }
 
     /**
-     * Create a contact and assert that the record exists.
+     * Create a contact in the local account and assert that the record exists.
      *
      * @return The created contact id pair.
      */
     public static ContactIdPair assertAndCreateContact(ContentResolver resolver) {
-        long rawContactId = RawContactUtil.createRawContactWithName(resolver);
+        return assertAndCreateContact(resolver, null);
+    }
+
+    /**
+     * Create a contact in the given account and assert that the record exists.
+     *
+     * @return The created contact id pair.
+     */
+    public static ContactIdPair assertAndCreateContact(ContentResolver resolver, Account account) {
+        long rawContactId = RawContactUtil.createRawContactWithName(resolver, account);
 
         long contactId = RawContactUtil.queryContactIdByRawContactId(resolver, rawContactId);
         MoreAsserts.assertNotEqual(CommonDatabaseUtils.NOT_FOUND, contactId);
diff --git a/tests/src/com/android/providers/contacts/CallLogProviderTest.java b/tests/src/com/android/providers/contacts/CallLogProviderTest.java
index 93806e0..9efdfaa 100644
--- a/tests/src/com/android/providers/contacts/CallLogProviderTest.java
+++ b/tests/src/com/android/providers/contacts/CallLogProviderTest.java
@@ -16,9 +16,7 @@
 
 package com.android.providers.contacts;
 
-import com.android.internal.telephony.CallerInfo;
-import com.android.internal.telephony.PhoneConstants;
-import com.android.providers.contacts.CallLogDatabaseHelper.DbProperties;
+import android.telecom.CallerInfo;
 import com.android.providers.contacts.testutil.CommonDatabaseUtils;
 
 import android.content.ComponentName;
@@ -63,6 +61,10 @@
     /** Total number of columns exposed by call_log provider. */
     private static final int NUM_CALLLOG_FIELDS = 34;
 
+    private static final int MIN_MATCH = 7;
+
+    private int mOldMinMatch;
+
     private CallLogProviderTestable mCallLogProvider;
 
     @Override
@@ -79,6 +81,8 @@
     protected void setUp() throws Exception {
         super.setUp();
         mCallLogProvider = addProvider(CallLogProviderTestable.class, CallLog.AUTHORITY);
+        mOldMinMatch = mCallLogProvider.getMinMatchForTest();
+        mCallLogProvider.setMinMatchForTest(MIN_MATCH);
     }
 
     @Override
@@ -86,6 +90,7 @@
         setUpWithVoicemailPermissions();
         mResolver.delete(Calls.CONTENT_URI_WITH_VOICEMAIL, null, null);
         setTimeForTest(null);
+        mCallLogProvider.setMinMatchForTest(mOldMinMatch);
         super.tearDown();
     }
 
@@ -180,7 +185,7 @@
 
     public void testAddCall() {
         CallerInfo ci = new CallerInfo();
-        ci.name = "1-800-GOOG-411";
+        ci.setName("1-800-GOOG-411");
         ci.numberType = Phone.TYPE_CUSTOM;
         ci.numberLabel = "Directory";
         final ComponentName sComponentName = new ComponentName(
@@ -190,7 +195,7 @@
                 sComponentName, "sub0");
 
         Uri uri = Calls.addCall(ci, getMockContext(), "1-800-263-7643",
-                PhoneConstants.PRESENTATION_ALLOWED, Calls.OUTGOING_TYPE, 0, subscription, 2000,
+                Calls.PRESENTATION_ALLOWED, Calls.OUTGOING_TYPE, 0, subscription, 2000,
                 40, null);
         assertNotNull(uri);
         assertEquals("0@" + CallLog.AUTHORITY, uri.getAuthority());
@@ -202,7 +207,7 @@
         values.put(Calls.NUMBER_PRESENTATION, Calls.PRESENTATION_ALLOWED);
         values.put(Calls.DATE, 2000);
         values.put(Calls.DURATION, 40);
-        values.put(Calls.CACHED_NAME, ci.name);
+        values.put(Calls.CACHED_NAME, ci.getName());
         values.put(Calls.CACHED_NUMBER_TYPE, (String) null);
         values.put(Calls.CACHED_NUMBER_LABEL, (String) null);
         values.put(Calls.COUNTRY_ISO, "us");
diff --git a/tests/src/com/android/providers/contacts/CallerInfoIntegrationTest.java b/tests/src/com/android/providers/contacts/CallerInfoIntegrationTest.java
index 867f01f..f57c060 100644
--- a/tests/src/com/android/providers/contacts/CallerInfoIntegrationTest.java
+++ b/tests/src/com/android/providers/contacts/CallerInfoIntegrationTest.java
@@ -22,7 +22,7 @@
 import android.provider.ContactsContract.RawContacts;
 import android.test.suitebuilder.annotation.MediumTest;
 
-import com.android.internal.telephony.CallerInfo;
+import android.telecom.CallerInfo;
 import com.android.providers.contacts.testutil.DataUtil;
 
 /**
@@ -49,9 +49,9 @@
         insertPhoneNumber(rawContactId, "800-466-4411");
 
         CallerInfo callerInfo = CallerInfo.getCallerInfo(getProvider().getContext(), "18004664411");
-        assertEquals("800-466-4411", callerInfo.phoneNumber);
+        assertEquals("800-466-4411", callerInfo.getPhoneNumber());
         assertEquals("Home", callerInfo.phoneLabel);
-        assertEquals("Hot Tamale", callerInfo.name);
+        assertEquals("Hot Tamale", callerInfo.getName());
         assertEquals("ring", String.valueOf(callerInfo.contactRingtoneUri));
         assertEquals(true, callerInfo.shouldSendToVoicemail);
         assertEquals("content://com.android.contacts/phone_lookup_enterprise/18004664411",
diff --git a/tests/src/com/android/providers/contacts/ContactsActor.java b/tests/src/com/android/providers/contacts/ContactsActor.java
index 470f64b..804e79a 100644
--- a/tests/src/com/android/providers/contacts/ContactsActor.java
+++ b/tests/src/com/android/providers/contacts/ContactsActor.java
@@ -16,6 +16,8 @@
 
 package com.android.providers.contacts;
 
+import static org.mockito.Mockito.when;
+
 import android.accounts.Account;
 import android.accounts.AccountManager;
 import android.accounts.AccountManagerCallback;
@@ -57,6 +59,7 @@
 import android.provider.ContactsContract.Data;
 import android.provider.ContactsContract.RawContacts;
 import android.provider.ContactsContract.StatusUpdates;
+import android.telecom.TelecomManager;
 import android.telephony.TelephonyManager;
 import android.test.IsolatedContext;
 import android.test.mock.MockContentResolver;
@@ -68,6 +71,8 @@
 
 import com.google.android.collect.Sets;
 
+import org.mockito.Mockito;
+
 import java.io.File;
 import java.io.IOException;
 import java.util.ArrayList;
@@ -91,6 +96,8 @@
     public static final String PACKAGE_GREEN = "com.example.green";
     public static final String PACKAGE_BLUE = "org.example.blue";
 
+    private static final int DEFAULT_USER_ID = 0;
+
     public Context context;
     public String packageName;
     public MockContentResolver resolver;
@@ -153,11 +160,8 @@
 
     public static class MockUserManager extends UserManager {
         public static UserInfo createUserInfo(String name, int id, int groupId, int flags) {
-            final UserInfo ui = new UserInfo();
-            ui.name = name;
-            ui.id = id;
+            final UserInfo ui = new UserInfo(id, name, flags | UserInfo.FLAG_INITIALIZED);
             ui.profileGroupId = groupId;
-            ui.flags = flags | UserInfo.FLAG_INITIALIZED;
             return ui;
         }
 
@@ -168,7 +172,7 @@
         public static final UserInfo SECONDARY_USER = createUserInfo("2nd", 11, 11, 0);
 
         /** "My" user.  Set it to change the current user. */
-        public int myUser = 0;
+        public int myUser = DEFAULT_USER_ID;
 
         private ArrayList<UserInfo> mUsers = new ArrayList<>();
 
@@ -271,6 +275,8 @@
         }
     }
 
+    private TelecomManager mMockTelecomManager;
+
     /**
      * A context wrapper that reports a different user id.
      *
@@ -322,6 +328,9 @@
                 if (Context.TELEPHONY_SERVICE.equals(name)) {
                     return mMockTelephonyManager;
                 }
+                if (Context.TELECOM_SERVICE.equals(name)) {
+                    return mMockTelecomManager;
+                }
                 // Use overallContext here; super.getSystemService() somehow won't return
                 // DevicePolicyManager.
                 return overallContext.getSystemService(name);
@@ -369,6 +378,9 @@
                 if (Context.TELEPHONY_SERVICE.equals(name)) {
                     return mMockTelephonyManager;
                 }
+                if (Context.TELECOM_SERVICE.equals(name)) {
+                    return mMockTelecomManager;
+                }
                 // Use overallContext here; super.getSystemService() somehow won't return
                 // DevicePolicyManager.
                 return overallContext.getSystemService(name);
@@ -386,7 +398,11 @@
 
             @Override
             public int getUserId() {
-                return mockUserManager.getUserHandle();
+                if (mockUserManager != null) {
+                    return mockUserManager.getUserHandle();
+                } else {
+                    return DEFAULT_USER_ID;
+                }
             }
 
             @Override
@@ -403,6 +419,9 @@
         mMockAccountManager = new MockAccountManager(mProviderContext);
         mockUserManager = new MockUserManager(mProviderContext);
         mMockTelephonyManager = new MockTelephonyManager(mProviderContext);
+        mMockTelecomManager = Mockito.mock(TelecomManager.class);
+        when(mMockTelecomManager.getDefaultDialerPackage()).thenReturn("");
+        when(mMockTelecomManager.getSystemDialerPackage()).thenReturn("");
         provider = addProvider(providerClass, authority);
     }
 
diff --git a/tests/src/com/android/providers/contacts/ContactsProvider2Test.java b/tests/src/com/android/providers/contacts/ContactsProvider2Test.java
index 32a0306..d5643d2 100644
--- a/tests/src/com/android/providers/contacts/ContactsProvider2Test.java
+++ b/tests/src/com/android/providers/contacts/ContactsProvider2Test.java
@@ -70,6 +70,7 @@
 import android.provider.ContactsContract.StreamItemPhotos;
 import android.provider.ContactsContract.StreamItems;
 import android.provider.OpenableColumns;
+import android.telephony.PhoneNumberUtils;
 import android.test.MoreAsserts;
 import android.test.suitebuilder.annotation.LargeTest;
 import android.text.TextUtils;
@@ -129,6 +130,31 @@
 
     private static final String TAG = ContactsProvider2Test.class.getSimpleName();
 
+    private static final int MIN_MATCH = 7;
+
+    private int mOldMinMatch1;
+    private int mOldMinMatch2;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        final ContactsProvider2 cp = (ContactsProvider2) getProvider();
+        final ContactsDatabaseHelper dbHelper = cp.getThreadActiveDatabaseHelperForTest();
+        mOldMinMatch1 = PhoneNumberUtils.getMinMatchForTest();
+        mOldMinMatch2 = dbHelper.getMinMatchForTest();
+        PhoneNumberUtils.setMinMatchForTest(MIN_MATCH);
+        dbHelper.setMinMatchForTest(MIN_MATCH);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        final ContactsProvider2 cp = (ContactsProvider2) getProvider();
+        final ContactsDatabaseHelper dbHelper = cp.getThreadActiveDatabaseHelperForTest();
+        PhoneNumberUtils.setMinMatchForTest(mOldMinMatch1);
+        dbHelper.setMinMatchForTest(mOldMinMatch2);
+        super.tearDown();
+    }
+
     public void testConvertEnterpriseUriWithEnterpriseDirectoryToLocalUri() {
         String phoneNumber = "886";
         String directory = String.valueOf(Directory.ENTERPRISE_DEFAULT);
@@ -3035,16 +3061,16 @@
         long rawContactId = RawContactUtil.createRawContactWithBackupId(mResolver, backupId,
                 account);
         Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
-        // Check if the raw contact is updated.
+        // Check if the raw contact is not updated since Lychee is removed.
         assertStoredValue(rawContactUri, RawContacts._ID, rawContactId);
         assertStoredValue(rawContactUri, RawContacts.ACCOUNT_TYPE, accountType);
         assertStoredValue(rawContactUri, RawContacts.ACCOUNT_NAME, accountName);
         assertStoredValue(rawContactUri, RawContacts.BACKUP_ID, backupId);
-        assertStoredValue(rawContactUri, RawContacts.SEND_TO_VOICEMAIL, "1");
-        assertStoredValue(rawContactUri, RawContacts.STARRED, "1");
-        assertStoredValue(rawContactUri, RawContacts.PINNED, "1");
-        // Notify metadata network on raw contact insertion
-        assertMetadataNetworkNotified(true);
+        assertStoredValue(rawContactUri, RawContacts.SEND_TO_VOICEMAIL, "0");
+        assertStoredValue(rawContactUri, RawContacts.STARRED, "0");
+        assertStoredValue(rawContactUri, RawContacts.PINNED, "0");
+        // No metadata network notify.
+        assertMetadataNetworkNotified(false);
     }
 
     public void testUpdateMetadataOnRawContactBackupIdChange() throws Exception {
@@ -3102,15 +3128,15 @@
         ContentValues updatedValues = new ContentValues();
         updatedValues.put(RawContacts.BACKUP_ID, backupId);
         mResolver.update(RawContacts.CONTENT_URI, updatedValues, null, null);
-        // Check if the raw contact is updated because of backup_id change.
+        // Check if the raw contact is still not updated.
         assertStoredValue(rawContactUri, RawContacts._ID, rawContactId);
         assertStoredValue(rawContactUri, RawContacts.ACCOUNT_TYPE, accountType);
         assertStoredValue(rawContactUri, RawContacts.ACCOUNT_NAME, accountName);
-        assertStoredValue(rawContactUri, RawContacts.SEND_TO_VOICEMAIL, "1");
-        assertStoredValue(rawContactUri, RawContacts.STARRED, "1");
-        assertStoredValue(rawContactUri, RawContacts.PINNED, "1");
-        // Notify metadata network because of the changed raw contact.
-        assertMetadataNetworkNotified(true);
+        assertStoredValue(rawContactUri, RawContacts.SEND_TO_VOICEMAIL, "0");
+        assertStoredValue(rawContactUri, RawContacts.STARRED, "0");
+        assertStoredValue(rawContactUri, RawContacts.PINNED, "0");
+        // No metadata network notify.
+        assertMetadataNetworkNotified(false);
     }
 
     public void testDeleteMetadataOnRawContactDelete() throws Exception {
@@ -3160,12 +3186,12 @@
 
         // Delete raw contact.
         mResolver.delete(rawContactUri, null, null);
-        // Check if the metadata is deleted.
-        assertStoredValue(metadataUri, MetadataSync.DELETED, "1");
+        // Check if the metadata is not deleted.
+        assertStoredValue(metadataUri, MetadataSync.DELETED, "0");
         // check raw contact metadata_dirty column is not changed on raw contact deletion
         assertMetadataDirty(rawContactUri, false);
-        // Notify metadata network on raw contact deletion
-        assertMetadataNetworkNotified(true);
+        // Lychee removed. Will not notify it.
+        assertMetadataNetworkNotified(false);
 
         // Add another rawcontact and metadata, and don't delete them.
         // Insert a raw contact.
@@ -3189,10 +3215,10 @@
 
         // Check if the metadata is not marked as deleted.
         assertStoredValue(metadataUri2, MetadataSync.DELETED, "0");
-        // check raw contact metadata_dirty column is changed on raw contact update
-        assertMetadataDirty(rawContactUri2, true);
-        // Notify metadata network on raw contact update
-        assertMetadataNetworkNotified(true);
+        // Will not set metadata_dirty since Lychee is removed.
+        assertMetadataDirty(rawContactUri2, false);
+        // Will not notify Lychee since it's removed.
+        assertMetadataNetworkNotified(false);
     }
 
     public void testPostalsQuery() {
@@ -5184,16 +5210,16 @@
         updateSendToVoicemailAndRingtone(contactId, true, "foo");
         assertSendToVoicemailAndRingtone(contactId, true, "foo");
         assertNetworkNotified(false);
-        assertMetadataNetworkNotified(true);
+        assertMetadataNetworkNotified(false);
         assertDirty(rawContactUri, false);
-        assertMetadataDirty(rawContactUri, true);
+        assertMetadataDirty(rawContactUri, false);
 
         updateSendToVoicemailAndRingtoneWithSelection(contactId, false, "bar");
         assertSendToVoicemailAndRingtone(contactId, false, "bar");
         assertNetworkNotified(false);
-        assertMetadataNetworkNotified(true);
+        assertMetadataNetworkNotified(false);
         assertDirty(rawContactUri, false);
-        assertMetadataDirty(rawContactUri, true);
+        assertMetadataDirty(rawContactUri, false);
     }
 
     public void testSendToVoicemailAndRingtoneAfterAggregation() {
@@ -5271,10 +5297,10 @@
 
         assertDirty(rawContactUri1, false);
         assertDirty(rawContactUri2, false);
-        assertMetadataDirty(rawContactUri1, true);
-        assertMetadataDirty(rawContactUri2, true);
+        assertMetadataDirty(rawContactUri1, false);
+        assertMetadataDirty(rawContactUri2, false);
         assertNetworkNotified(false);
-        assertMetadataNetworkNotified(true);
+        assertMetadataNetworkNotified(false);
     }
 
     public void testStatusUpdateInsert() {
@@ -6875,7 +6901,7 @@
         assertDirty(rawContactUri, false);
         assertMetadataDirty(rawContactUri, false);
         assertNetworkNotified(true);
-        assertMetadataNetworkNotified(true);
+        assertMetadataNetworkNotified(false);
 
         // When inserting a rawcontact with metadata.
         ContentValues values = new ContentValues();
@@ -6884,9 +6910,9 @@
         values.put(ContactsContract.RawContacts.ACCOUNT_TYPE, mAccount.type);
         Uri rawContactId2Uri = mResolver.insert(RawContacts.CONTENT_URI, values);
         assertDirty(rawContactId2Uri, false);
-        assertMetadataDirty(rawContactId2Uri, true);
+        assertMetadataDirty(rawContactId2Uri, false);
         assertNetworkNotified(true);
-        assertMetadataNetworkNotified(true);
+        assertMetadataNetworkNotified(false);
     }
 
     public void testRawContactDirtyAndVersion() {
@@ -6908,8 +6934,8 @@
 
         assertDirty(uri, false);
         assertNetworkNotified(false);
-        assertMetadataDirty(uri, true);
-        assertMetadataNetworkNotified(true);
+        assertMetadataDirty(uri, false);
+        assertMetadataNetworkNotified(false);
 
         Uri emailUri = insertEmail(rawContactId, "goo@woo.com");
         assertDirty(uri, true);
@@ -6979,9 +7005,8 @@
         long rawContactId = ContentUris.parseId(mResolver.insert(uri, new ContentValues()));
         Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
         assertMetadataDirty(rawContactUri, false);
-        // If the raw contact is inserted by sync adapter, it will notify metadata change no matter
-        // if there is any metadata change.
-        assertMetadataNetworkNotified(true);
+        // Will not notify Lychee since it's removed.
+        assertMetadataNetworkNotified(false);
     }
 
     public void testMarkAsMetadataDirtyForRawContactMetadataChange() {
@@ -6999,8 +7024,8 @@
         assertStoredValue(contactUri, Contacts.STARRED, 1);
 
         Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
-        assertMetadataDirty(rawContactUri, true);
-        assertMetadataNetworkNotified(true);
+        assertMetadataDirty(rawContactUri, false);
+        assertMetadataNetworkNotified(false);
 
         clearMetadataDirty(rawContactUri);
         values = new ContentValues();
@@ -7008,8 +7033,8 @@
         mResolver.update(contactUri, values, null, null);
         assertStoredValue(contactUri, Contacts.PINNED, 1);
 
-        assertMetadataDirty(rawContactUri, true);
-        assertMetadataNetworkNotified(true);
+        assertMetadataDirty(rawContactUri, false);
+        assertMetadataNetworkNotified(false);
 
         clearMetadataDirty(rawContactUri);
         values = new ContentValues();
@@ -7017,8 +7042,8 @@
         mResolver.update(contactUri, values, null, null);
         assertStoredValue(contactUri, Contacts.SEND_TO_VOICEMAIL, 1);
 
-        assertMetadataDirty(rawContactUri, true);
-        assertMetadataNetworkNotified(true);
+        assertMetadataDirty(rawContactUri, false);
+        assertMetadataNetworkNotified(false);
     }
 
     public void testMarkAsMetadataDirtyForRawContactBackupIdChange() {
@@ -7033,15 +7058,15 @@
         ContentValues values = new ContentValues();
         values.put(RawContacts.SEND_TO_VOICEMAIL, "1");
         mResolver.update(rawContactUri, values, null, null);
-        assertMetadataDirty(rawContactUri, true);
+        assertMetadataDirty(rawContactUri, false);
 
         // Update the backup_id and check metadata network should be notified.
         values = new ContentValues();
         values.put(RawContacts.BACKUP_ID, "newBackupId");
         mResolver.update(rawContactUri, values, null, null);
         assertStoredValue(rawContactUri, RawContacts.BACKUP_ID, "newBackupId");
-        assertMetadataDirty(rawContactUri, true);
-        assertMetadataNetworkNotified(true);
+        assertMetadataDirty(rawContactUri, false);
+        assertMetadataNetworkNotified(false);
     }
 
     public void testMarkAsMetadataDirtyForAggregationExceptionChange() {
@@ -7056,10 +7081,10 @@
                 rawContactId1, rawContactId2);
 
         assertMetadataDirty(ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId1),
-                true);
+                false);
         assertMetadataDirty(ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId2),
-                true);
-        assertMetadataNetworkNotified(true);
+                false);
+        assertMetadataNetworkNotified(false);
     }
 
     public void testMarkAsMetadataNotDirtyForUsageStatsChange() {
@@ -7087,8 +7112,8 @@
         assertStoredValue(mailUri11, Data.IS_PRIMARY, 1);
         assertStoredValue(mailUri11, Data.IS_SUPER_PRIMARY, 1);
         assertMetadataDirty(ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId1),
-                true);
-        assertMetadataNetworkNotified(true);
+                false);
+        assertMetadataNetworkNotified(false);
     }
 
     public void testMarkAsMetadataDirtyForDataPrimarySettingUpdate() {
@@ -7107,8 +7132,8 @@
         mResolver.update(mailUri1, values, null, null);
 
         assertMetadataDirty(ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId),
-                true);
-        assertMetadataNetworkNotified(true);
+                false);
+        assertMetadataNetworkNotified(false);
     }
 
     public void testMarkAsMetadataDirtyForDataDelete() {
@@ -7122,8 +7147,8 @@
         mResolver.delete(mailUri1, null, null);
 
         assertMetadataDirty(ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId),
-                true);
-        assertMetadataNetworkNotified(true);
+                false);
+        assertMetadataNetworkNotified(false);
     }
 
     public void testDeleteContactWithoutName() {
@@ -7164,6 +7189,98 @@
         assertEquals(1, mResolver.delete(lookupUri, null, null));
     }
 
+    public void testDeleteContactComposedOfSingleLocalRawContact() {
+        // Create a raw contact in the local (null) account
+        long rawContactId = RawContactUtil.createRawContact(mResolver, null);
+        DataUtil.insertStructuredName(mResolver, rawContactId, "John", "Smith");
+
+        // Delete the contact
+        long contactId = queryContactId(rawContactId);
+        Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
+        assertEquals(1, mResolver.delete(contactUri, null, null));
+
+        // Assert that the raw contact was removed
+        Cursor c1 = queryRawContact(rawContactId);
+        assertEquals(0, c1.getCount());
+        c1.close();
+
+        // Assert that the contact was removed
+        Cursor c2 = mResolver.query(contactUri, null, null, null, "");
+        assertEquals(0, c2.getCount());
+        c2.close();
+    }
+
+    public void testDeleteContactComposedOfTwoLocalRawContacts() {
+        // Create a raw contact in the local (null) account
+        long rawContactId1 = RawContactUtil.createRawContact(mResolver, null);
+        DataUtil.insertStructuredName(mResolver, rawContactId1, "John", "Smith");
+
+        // Create another local raw contact with the same name
+        long rawContactId2 = RawContactUtil.createRawContact(mResolver, null);
+        DataUtil.insertStructuredName(mResolver, rawContactId2, "John", "Smith");
+
+        // Join the two raw contacts explicitly
+        setAggregationException(AggregationExceptions.TYPE_KEEP_TOGETHER,
+                rawContactId1, rawContactId2);
+
+        // Check that the two raw contacts are aggregated together
+        assertAggregated(rawContactId1, rawContactId2, "John Smith");
+
+        // Delete the aggregate contact
+        long contactId = queryContactId(rawContactId1);
+        Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
+        assertEquals(1, mResolver.delete(contactUri, null, null));
+
+        // Assert that both of the local raw contacts were removed completely
+        Cursor c1 = queryRawContact(rawContactId1);
+        assertEquals(0, c1.getCount());
+        c1.close();
+
+        Cursor c2 = queryRawContact(rawContactId2);
+        assertEquals(0, c2.getCount());
+        c2.close();
+
+        // Assert that the contact was removed
+        Cursor c3 = queryContact(contactId);
+        assertEquals(0, c3.getCount());
+        c3.close();
+    }
+
+    public void testDeleteContactComposedOfSomeLocalRawContacts() {
+        // Create a raw contact in the local (null) account
+        long rawContactId1 = RawContactUtil.createRawContact(mResolver, null);
+        DataUtil.insertStructuredName(mResolver, rawContactId1, "John", "Smith");
+
+        // Create another one in a non-local account with the same name
+        long rawContactId2 = RawContactUtil.createRawContact(mResolver, mAccount);
+        DataUtil.insertStructuredName(mResolver, rawContactId2, "John", "Smith");
+
+        // Check that the two new raw contacts are aggregated together
+        assertAggregated(rawContactId1, rawContactId2, "John Smith");
+
+        // Delete the aggregate contact
+        long contactId = queryContactId(rawContactId1);
+        Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
+        assertEquals(1, mResolver.delete(contactUri, null, null));
+
+        // Assert that the local raw contact was removed completely
+        Cursor c1 = queryRawContact(rawContactId1);
+        assertEquals(0, c1.getCount());
+        c1.close();
+
+        // Assert that the non-local raw contact is still present just marked as deleted
+        Cursor c2 = queryRawContact(rawContactId2);
+        assertEquals(1, c2.getCount());
+        assertTrue(c2.moveToFirst());
+        assertEquals(1, c2.getInt(c2.getColumnIndex(RawContacts.DELETED)));
+        c2.close();
+
+        // Assert that the contact was removed
+        Cursor c3 = queryContact(contactId);
+        assertEquals(0, c3.getCount());
+        c3.close();
+    }
+
     public void testQueryContactWithEscapedUri() {
         ContentValues values = new ContentValues();
         values.put(RawContacts.SOURCE_ID, "!@#$%^&*()_+=-/.,<>?;'\":[]}{\\|`~");
@@ -8863,7 +8980,7 @@
     }
 
     public void testContactDelete_marksRawContactsForDeletion() {
-        DatabaseAsserts.ContactIdPair ids = assertContactCreateDelete();
+        DatabaseAsserts.ContactIdPair ids = assertContactCreateDelete(mAccount);
 
         String[] projection = new String[]{ContactsContract.RawContacts.DIRTY,
                 ContactsContract.RawContacts.DELETED};
@@ -8877,7 +8994,7 @@
     }
 
     public void testContactDelete_checkRawContactContactId() {
-        DatabaseAsserts.ContactIdPair ids = assertContactCreateDelete();
+        DatabaseAsserts.ContactIdPair ids = assertContactCreateDelete(mAccount);
 
         String[] projection = new String[]{ContactsContract.RawContacts.CONTACT_ID};
         String[] record = RawContactUtil.queryByRawContactId(mResolver, ids.mRawContactId,
@@ -8903,9 +9020,9 @@
 
         ContactUtil.update(mResolver, ids.mContactId, values);
         assertDirty(rawContactUri, false);
-        assertMetadataDirty(rawContactUri, true);
+        assertMetadataDirty(rawContactUri, false);
         assertNetworkNotified(false);
-        assertMetadataNetworkNotified(true);
+        assertMetadataNetworkNotified(false);
     }
 
     public void testContactUpdate_updatesContactUpdatedTimestamp() {
@@ -9118,13 +9235,23 @@
     }
 
     /**
-     * Create a contact. Assert it's not present in the delete log. Delete it.
-     * And assert that the contact record is no longer present.
+     * Creates a contact in the local account. Assert it's not present in the delete log.
+     * Delete it. And assert that the contact record is no longer present.
      *
      * @return The contact id and raw contact id that was created.
      */
     private DatabaseAsserts.ContactIdPair assertContactCreateDelete() {
-        DatabaseAsserts.ContactIdPair ids = DatabaseAsserts.assertAndCreateContact(mResolver);
+        return assertContactCreateDelete(null);
+    }
+
+    /**
+     * Creates a contact in the given account. Assert it's not present in the delete log.
+     * Delete it. And assert that the contact record is no longer present.
+     * @return The contact id and raw contact id that was created.
+     */
+    private DatabaseAsserts.ContactIdPair assertContactCreateDelete(Account account) {
+        DatabaseAsserts.ContactIdPair ids = DatabaseAsserts.assertAndCreateContact(mResolver,
+                account);
 
         assertEquals(CommonDatabaseUtils.NOT_FOUND,
                 DeletedContactUtil.queryDeletedTimestampForContactId(mResolver, ids.mContactId));
diff --git a/tests/src/com/android/providers/contacts/aggregation/ContactAggregator2Test.java b/tests/src/com/android/providers/contacts/aggregation/ContactAggregator2Test.java
index 927b215..b19a10f 100644
--- a/tests/src/com/android/providers/contacts/aggregation/ContactAggregator2Test.java
+++ b/tests/src/com/android/providers/contacts/aggregation/ContactAggregator2Test.java
@@ -34,10 +34,12 @@
 import android.provider.ContactsContract.Data;
 import android.provider.ContactsContract.RawContacts;
 import android.provider.ContactsContract.StatusUpdates;
+import android.telephony.PhoneNumberUtils;
 import android.test.MoreAsserts;
 import android.test.suitebuilder.annotation.MediumTest;
 
 import com.android.providers.contacts.BaseContactsProvider2Test;
+import com.android.providers.contacts.ContactsDatabaseHelper;
 import com.android.providers.contacts.ContactsProvider2;
 import com.android.providers.contacts.TestUtils;
 import com.android.providers.contacts.tests.R;
@@ -69,11 +71,32 @@
             AggregationExceptions.RAW_CONTACT_ID2
     };
 
+    private static final int MIN_MATCH = 7;
+
+    private static int mOldMinMatch1;
+    private static int mOldMinMatch2;
+
+    @Override
     protected void setUp() throws Exception {
         super.setUp();
         // Enable new aggregator.
         final ContactsProvider2 cp = (ContactsProvider2) getProvider();
         cp.setNewAggregatorForTest(true);
+
+        final ContactsDatabaseHelper dbHelper = cp.getThreadActiveDatabaseHelperForTest();
+        mOldMinMatch1 = PhoneNumberUtils.getMinMatchForTest();
+        mOldMinMatch2 = dbHelper.getMinMatchForTest();
+        PhoneNumberUtils.setMinMatchForTest(MIN_MATCH);
+        dbHelper.setMinMatchForTest(MIN_MATCH);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        final ContactsProvider2 cp = (ContactsProvider2) getProvider();
+        final ContactsDatabaseHelper dbHelper = cp.getThreadActiveDatabaseHelperForTest();
+        PhoneNumberUtils.setMinMatchForTest(mOldMinMatch1);
+        dbHelper.setMinMatchForTest(mOldMinMatch2);
+        super.tearDown();
     }
 
     public void testCrudAggregationExceptions() throws Exception {
diff --git a/tests/src/com/android/providers/contacts/aggregation/ContactAggregatorTest.java b/tests/src/com/android/providers/contacts/aggregation/ContactAggregatorTest.java
index fb4f930..56f0883 100644
--- a/tests/src/com/android/providers/contacts/aggregation/ContactAggregatorTest.java
+++ b/tests/src/com/android/providers/contacts/aggregation/ContactAggregatorTest.java
@@ -34,10 +34,12 @@
 import android.provider.ContactsContract.Data;
 import android.provider.ContactsContract.RawContacts;
 import android.provider.ContactsContract.StatusUpdates;
+import android.telephony.PhoneNumberUtils;
 import android.test.MoreAsserts;
 import android.test.suitebuilder.annotation.MediumTest;
 
 import com.android.providers.contacts.BaseContactsProvider2Test;
+import com.android.providers.contacts.ContactsDatabaseHelper;
 import com.android.providers.contacts.ContactsProvider2;
 import com.android.providers.contacts.TestUtils;
 import com.android.providers.contacts.tests.R;
@@ -63,17 +65,38 @@
     private static final Account ACCOUNT_2 = new Account("account_name_2", "account_type_2");
     private static final Account ACCOUNT_3 = new Account("account_name_3", "account_type_3");
 
+    private static final int MIN_MATCH = 7;
+
+    private int mOldMinMatch1;
+    private int mOldMinMatch2;
+
     private static final String[] AGGREGATION_EXCEPTION_PROJECTION = new String[] {
             AggregationExceptions.TYPE,
             AggregationExceptions.RAW_CONTACT_ID1,
             AggregationExceptions.RAW_CONTACT_ID2
     };
 
+    @Override
     protected void setUp() throws Exception {
         super.setUp();
         final ContactsProvider2 cp = (ContactsProvider2) getProvider();
         // Make sure to use ContactAggregator.java class
         cp.setNewAggregatorForTest(false);
+
+        final ContactsDatabaseHelper dbHelper = cp.getThreadActiveDatabaseHelperForTest();
+        mOldMinMatch1 = PhoneNumberUtils.getMinMatchForTest();
+        mOldMinMatch2 = dbHelper.getMinMatchForTest();
+        PhoneNumberUtils.setMinMatchForTest(MIN_MATCH);
+        dbHelper.setMinMatchForTest(MIN_MATCH);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        final ContactsProvider2 cp = (ContactsProvider2) getProvider();
+        final ContactsDatabaseHelper dbHelper = cp.getThreadActiveDatabaseHelperForTest();
+        PhoneNumberUtils.setMinMatchForTest(mOldMinMatch1);
+        dbHelper.setMinMatchForTest(mOldMinMatch2);
+        super.tearDown();
     }
 
     public void testCrudAggregationExceptions() throws Exception {