[automerger skipped] Import translations. DO NOT MERGE ANYWHERE am: 78b9cbd6fb -s ours

am skip reason: subject contains skip directive

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

Change-Id: I439aef57d072ba849ca576ae02df030aee6607fb
diff --git a/Android.bp b/Android.bp
index 8991628..4b345e6 100644
--- a/Android.bp
+++ b/Android.bp
@@ -1,5 +1,25 @@
+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",
+    ],
+}
+
 android_app {
     name: "ContactsProvider",
+    defaults: ["platform_app_defaults"],
     // Only compile source java files in this apk.
     srcs: [
         "src/**/*.java",
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 9542e6d..2b101be 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -4,6 +4,8 @@
         android:sharedUserLabel="@string/sharedUserLabel">
 
     <uses-permission android:name="android.permission.BIND_DIRECTORY_SEARCH" />
+    <!-- For sending voicemail intents -->
+    <uses-permission android:name="android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST" />
     <uses-permission android:name="android.permission.GET_ACCOUNTS" />
     <uses-permission android:name="android.permission.GET_ACCOUNTS_PRIVILEGED" />
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
@@ -18,12 +20,19 @@
     <uses-permission android:name="android.permission.WRITE_CONTACTS" />
     <uses-permission android:name="android.permission.USE_RESERVED_DISK" />
     <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" />
 
     <permission
             android:name="android.permission.SEND_CALL_LOG_CHANGE"
             android:label="Broadcast that a change happened to the call log."
             android:protectionLevel="signature|system"/>
 
+    <permission
+        android:name="android.contacts.permission.MANAGE_SIM_ACCOUNTS"
+        android:label="Change known SIM accounts in ContactsProvider."
+        android:protectionLevel="signature"/>
+
     <application android:process="android.process.acore"
         android:label="@string/app_label"
         android:allowBackup="false"
@@ -59,13 +68,24 @@
             android:writePermission="android.permission.WRITE_CALL_LOG">
         </provider>
 
+        <!-- Separate provider for the locations stored in call provider.
+             Uses a different db in order to prevent SQL injection attacks from bypassing
+             location permission requirements -->
+        <provider android:name="CallComposerLocationProvider"
+            android:authorities="call_composer_locations"
+            android:syncable="false" android:multiprocess="false"
+            android:exported="true"
+            android:readPermission="android.permission.READ_CALL_LOG"
+            android:writePermission="android.permission.WRITE_CALL_LOG">
+        </provider>
+
         <provider android:name="ShadowCallLogProvider"
                   android:authorities="call_log_shadow"
                   android:syncable="false" android:multiprocess="false"
                   android:exported="true"
                   android:directBootAware="true"
-                  android:readPermission="android.permission.MANAGE_USERS"
-                  android:writePermission="android.permission.MANAGE_USERS">
+                  android:readPermission="android.permission.INTERACT_ACROSS_USERS"
+                  android:writePermission="android.permission.INTERACT_ACROSS_USERS">
         </provider>
 
         <!-- Note: While this provider does not declare a permission explicitly, it enforces that
@@ -77,14 +97,9 @@
             android:exported="true">
         </provider>
 
-        <provider android:name="ContactMetadataProvider"
-                  android:authorities="com.android.contacts.metadata"
-                  android:multiprocess="false"
-                  android:exported="true">
-        </provider>
-
         <!-- Handles database upgrades after OTAs, then disables itself -->
-        <receiver android:name="ContactsUpgradeReceiver">
+        <receiver android:name="ContactsUpgradeReceiver"
+            android:exported="true">
             <!-- This broadcast is sent after the core system has finished
                  booting, before the home app is launched or BOOT_COMPLETED
                  is sent. -->
@@ -94,6 +109,7 @@
         </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>
@@ -101,7 +117,8 @@
             </intent-filter>
         </receiver>
 
-        <receiver android:name="LocaleChangeReceiver">
+        <receiver android:name="LocaleChangeReceiver"
+            android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.LOCALE_CHANGED"/>
             </intent-filter>
@@ -110,6 +127,7 @@
         <activity android:name=".debug.ContactsDumpActivity"
                 android:label="@string/debug_dump_title"
                 android:theme="@android:style/Theme.Holo.Dialog"
+                android:exported="true"
                 >
             <intent-filter>
                 <action android:name="com.android.providers.contacts.DUMP_DATABASE"/>
diff --git a/MODULE_LICENSE_APACHE2 b/MODULE_LICENSE_APACHE2
deleted file mode 100644
index e69de29..0000000
--- a/MODULE_LICENSE_APACHE2
+++ /dev/null
diff --git a/OWNERS b/OWNERS
index dc2d0b3..6ee21e6 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,2 +1,8 @@
 omakoto@google.com
 yamasani@google.com
+
+# For calllog provider
+tgunn@google.com
+hallliu@google.com
+breadley@google.com
+rgreenwalt@google.com
diff --git a/src/com/android/providers/contacts/AccountWithDataSet.java b/src/com/android/providers/contacts/AccountWithDataSet.java
index bfae112..e1f633e 100644
--- a/src/com/android/providers/contacts/AccountWithDataSet.java
+++ b/src/com/android/providers/contacts/AccountWithDataSet.java
@@ -17,10 +17,14 @@
 package com.android.providers.contacts;
 
 import android.accounts.Account;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.SimAccount;
 import android.text.TextUtils;
 
 import com.google.common.base.Objects;
 
+import java.util.List;
+
 /**
  * Account information that includes the data set, if any.
  */
@@ -105,4 +109,23 @@
         }
         return false;
     }
+
+    /**
+     * @return {@code true} if there is a {@link SimAccount} with a matching account name and type
+     * in the passed list.
+     * The list should be obtained from {@link ContactsDatabaseHelper#getAllSimAccounts()} so it
+     * will already have valid SIM accounts so only name and type need to be compared.
+     */
+    public boolean inSimAccounts(List<SimAccount> simAccountList) {
+        // Note we don't want to create a new SimAccount object from this instance, as this method
+        // does not need to know about the SIM slot or the EF type. It only needs to know whether
+        // the name and type match since the passed list will only contain valid SIM accounts.
+        for (SimAccount simAccount : simAccountList) {
+            if (Objects.equal(simAccount.getAccountName(), getAccountName())
+                    && Objects.equal(simAccount.getAccountType(), getAccountType())) {
+                return true;
+            }
+        }
+        return false;
+    }
 }
diff --git a/src/com/android/providers/contacts/CallComposerLocationProvider.java b/src/com/android/providers/contacts/CallComposerLocationProvider.java
new file mode 100644
index 0000000..568a189
--- /dev/null
+++ b/src/com/android/providers/contacts/CallComposerLocationProvider.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2021 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 static com.android.providers.contacts.util.DbQueryUtils.getEqualityClause;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ContentProvider;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.UriMatcher;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.database.sqlite.SQLiteQueryBuilder;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Process;
+import android.provider.CallLog;
+import android.telecom.TelecomManager;
+import android.text.TextUtils;
+import android.util.Log;
+
+
+import com.android.providers.contacts.util.SelectionBuilder;
+
+import java.util.Objects;
+
+public class CallComposerLocationProvider extends ContentProvider {
+    private static final String TAG = CallComposerLocationProvider.class.getSimpleName();
+    private static final String DB_NAME = "call_composer_locations.db";
+    private static final String TABLE_NAME = "locations";
+    private static final int VERSION = 1;
+
+    private static final int LOCATION = 1;
+    private static final int LOCATION_ID = 2;
+
+    private static class OpenHelper extends SQLiteOpenHelper {
+        public OpenHelper(@Nullable Context context, @Nullable String name,
+                @Nullable SQLiteDatabase.CursorFactory factory, int version) {
+            super(context, name, factory, version);
+        }
+
+        @Override
+        public void onCreate(SQLiteDatabase db) {
+            db.execSQL("CREATE TABLE " + TABLE_NAME+ " (" +
+                    CallLog.Locations._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
+                    CallLog.Locations.LATITUDE + " REAL, " +
+                    CallLog.Locations.LONGITUDE + " REAL" +
+                    ");");
+        }
+
+        @Override
+        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+            // Nothing to do here, still on version 1.
+        }
+    }
+
+    private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
+    static {
+        sURIMatcher.addURI(CallLog.Locations.AUTHORITY, "", LOCATION);
+        sURIMatcher.addURI(CallLog.Locations.AUTHORITY, "/#", LOCATION_ID);
+    }
+
+    private OpenHelper mOpenHelper;
+
+    @Override
+    public boolean onCreate() {
+        mOpenHelper = new OpenHelper(getContext(), DB_NAME, null, VERSION);
+        return true;
+    }
+
+    @Nullable
+    @Override
+    public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection,
+            @Nullable String[] selectionArgs, @Nullable String sortOrder) {
+        enforceAccessRestrictions();
+        final SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
+        qb.setTables(TABLE_NAME);
+        qb.setStrict(true);
+        qb.setStrictGrammar(true);
+        final int match = sURIMatcher.match(uri);
+
+        final SelectionBuilder selectionBuilder = new SelectionBuilder(selection);
+        switch (match) {
+            case LOCATION_ID: {
+                selectionBuilder.addClause(getEqualityClause(CallLog.Locations._ID,
+                        parseLocationIdFromUri(uri)));
+                break;
+            }
+            default:
+                throw new IllegalArgumentException("Provided URI is not supported for query.");
+        }
+
+        final SQLiteDatabase db = mOpenHelper.getReadableDatabase();
+        return qb.query(db, projection, selectionBuilder.build(), selectionArgs, null,
+                null, sortOrder, null);
+    }
+
+    @Nullable
+    @Override
+    public String getType(@NonNull Uri uri) {
+        final int match = sURIMatcher.match(uri);
+        switch (match) {
+            case LOCATION_ID:
+                return CallLog.Locations.CONTENT_ITEM_TYPE;
+            case LOCATION:
+                return CallLog.Locations.CONTENT_TYPE;
+            default:
+                return null;
+        }
+    }
+
+    @Nullable
+    @Override
+    public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
+        enforceAccessRestrictions();
+        long id = mOpenHelper.getWritableDatabase().insert(TABLE_NAME, null, values);
+        return ContentUris.withAppendedId(CallLog.Locations.CONTENT_URI, id);
+    }
+
+    @Override
+    public int delete(@NonNull Uri uri, @Nullable String selection,
+            @Nullable String[] selectionArgs) {
+        enforceAccessRestrictions();
+        final int match = sURIMatcher.match(uri);
+        switch (match) {
+            case LOCATION_ID:
+                long id = parseLocationIdFromUri(uri);
+                return mOpenHelper.getWritableDatabase().delete(TABLE_NAME,
+                        CallLog.Locations._ID + " = ?", new String[] {String.valueOf(id)});
+            case LOCATION:
+                Log.w(TAG, "Deleting entire location table!");
+                return mOpenHelper.getWritableDatabase().delete(TABLE_NAME, "1", null);
+            default:
+                throw new IllegalArgumentException("delete() on the locations"
+                        + " does not support the uri " + uri.toString());
+        }
+    }
+
+    @Override
+    public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection,
+            @Nullable String[] selectionArgs) {
+        enforceAccessRestrictions();
+        throw new UnsupportedOperationException(
+                "Call composer location db does not support updates");
+    }
+
+    private long parseLocationIdFromUri(Uri uri) {
+        try {
+            return Long.parseLong(uri.getPathSegments().get(0));
+        } catch (NumberFormatException e) {
+            throw new IllegalArgumentException("Invalid location id in uri: " + uri, e);
+        }
+    }
+
+    private void enforceAccessRestrictions() {
+        int uid = Binder.getCallingUid();
+        if (uid == Process.SYSTEM_UID || uid == Process.myUid() || uid == Process.PHONE_UID) {
+            return;
+        }
+        String defaultDialerPackageName = getContext().getSystemService(TelecomManager.class)
+                .getDefaultDialerPackage();
+        if (TextUtils.isEmpty(defaultDialerPackageName)) {
+            throw new SecurityException("Access to call composer locations is only allowed for the"
+                    + " default dialer, but the default dialer is unset");
+        }
+        String[] callingPackageCandidates = getContext().getPackageManager().getPackagesForUid(uid);
+        for (String packageCandidate : callingPackageCandidates) {
+            if (Objects.equals(packageCandidate, defaultDialerPackageName)) {
+                return;
+            }
+        }
+        throw new SecurityException("Access to call composer locations is only allowed for the "
+                + "default dialer: " + defaultDialerPackageName);
+    }
+}
diff --git a/src/com/android/providers/contacts/CallLogDatabaseHelper.java b/src/com/android/providers/contacts/CallLogDatabaseHelper.java
index 39c3d5f..22f1cad 100644
--- a/src/com/android/providers/contacts/CallLogDatabaseHelper.java
+++ b/src/com/android/providers/contacts/CallLogDatabaseHelper.java
@@ -39,7 +39,7 @@
 public class CallLogDatabaseHelper {
     private static final String TAG = "CallLogDatabaseHelper";
 
-    private static final int DATABASE_VERSION = 8;
+    private static final int DATABASE_VERSION = 10;
 
     private static final boolean DEBUG = false; // DON'T SUBMIT WITH TRUE
 
@@ -152,6 +152,11 @@
                     Calls.CALL_SCREENING_COMPONENT_NAME + " TEXT," +
                     Calls.CALL_SCREENING_APP_NAME + " TEXT," +
                     Calls.BLOCK_REASON + " 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," +
 
                     Voicemails._DATA + " TEXT," +
                     Voicemails.HAS_CONTENT + " INTEGER," +
@@ -220,6 +225,14 @@
             if (oldVersion < 8) {
                 upgradetoVersion8(db);
             }
+
+            if (oldVersion < 9) {
+                upgradeToVersion9(db);
+            }
+
+            if (oldVersion < 10) {
+                upgradeToVersion10(db);
+            }
         }
     }
 
@@ -450,6 +463,16 @@
         }
     }
 
+    private void upgradeToVersion9(SQLiteDatabase db) {
+        db.execSQL("ALTER TABLE calls ADD missed_reason INTEGER NOT NULL DEFAULT 0");
+    }
+
+    private void upgradeToVersion10(SQLiteDatabase db) {
+        db.execSQL("ALTER TABLE calls ADD priority INTEGER NOT NULL DEFAULT 0");
+        db.execSQL("ALTER TABLE calls ADD subject TEXT");
+        db.execSQL("ALTER TABLE calls ADD location TEXT");
+        db.execSQL("ALTER TABLE calls ADD composer_photo_uri TEXT");
+    }
     /**
      * Perform the migration from the contacts2.db (of the latest version) to the current calllog/
      * voicemail status tables.
diff --git a/src/com/android/providers/contacts/CallLogProvider.java b/src/com/android/providers/contacts/CallLogProvider.java
index ebe111f..c832e9b 100644
--- a/src/com/android/providers/contacts/CallLogProvider.java
+++ b/src/com/android/providers/contacts/CallLogProvider.java
@@ -20,6 +20,8 @@
 import static com.android.providers.contacts.util.DbQueryUtils.getEqualityClause;
 import static com.android.providers.contacts.util.DbQueryUtils.getInequalityClause;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.AppOpsManager;
 import android.content.ContentProvider;
 import android.content.ContentProviderOperation;
@@ -36,6 +38,10 @@
 import android.database.sqlite.SQLiteQueryBuilder;
 import android.net.Uri;
 import android.os.Binder;
+import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
+import android.os.ParcelableException;
+import android.os.StatFs;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.CallLog;
@@ -43,6 +49,7 @@
 import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
 import android.telecom.TelecomManager;
+import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.Log;
@@ -55,11 +62,25 @@
 import com.android.providers.contacts.util.UserUtils;
 
 import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.OutputStream;
 import java.io.PrintWriter;
+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.List;
+import java.util.UUID;
 import java.util.concurrent.CountDownLatch;
+import java.util.stream.Collectors;
 
 /**
  * Call log content provider.
@@ -81,18 +102,57 @@
     private static final String EXCLUDE_HIDDEN_SELECTION = getEqualityClause(
             Calls.PHONE_ACCOUNT_HIDDEN, 0);
 
+    private static final String CALL_COMPOSER_PICTURE_DIRECTORY_NAME = "call_composer_pics";
+    private static final String CALL_COMPOSER_ALL_USERS_DIRECTORY_NAME = "all_users";
+
+    // Constants to be used with ContentProvider#call in order to sync call composer pics between
+    // users. Defined here because they're for internal use only.
+    /**
+     * Method name used to get a list of {@link Uri}s for call composer pictures inserted for all
+     * users after a certain date
+     */
+    private static final String GET_CALL_COMPOSER_IMAGE_URIS =
+            "com.android.providers.contacts.GET_CALL_COMPOSER_IMAGE_URIS";
+
+    /**
+     * Long-valued extra containing the date to filter by expressed as milliseconds after the epoch.
+     */
+    private static final String EXTRA_SINCE_DATE =
+            "com.android.providers.contacts.extras.SINCE_DATE";
+
+    /**
+     * Boolean-valued extra indicating whether to read from the shadow portion of the calllog
+     * (i.e. device-encrypted storage rather than credential-encrypted)
+     */
+    private static final String EXTRA_IS_SHADOW =
+            "com.android.providers.contacts.extras.IS_SHADOW";
+
+    /**
+     * Boolean-valued extra indicating whether to return Uris only for those images that are
+     * supposed to be inserted for all users.
+     */
+    private static final String EXTRA_ALL_USERS_ONLY =
+            "com.android.providers.contacts.extras.ALL_USERS_ONLY";
+
+    private static final String EXTRA_RESULT_URIS =
+            "com.android.provider.contacts.extras.EXTRA_RESULT_URIS";
+
     @VisibleForTesting
     static final String[] CALL_LOG_SYNC_PROJECTION = new String[] {
-        Calls.NUMBER,
-        Calls.NUMBER_PRESENTATION,
-        Calls.TYPE,
-        Calls.FEATURES,
-        Calls.DATE,
-        Calls.DURATION,
-        Calls.DATA_USAGE,
-        Calls.PHONE_ACCOUNT_COMPONENT_NAME,
-        Calls.PHONE_ACCOUNT_ID,
-        Calls.ADD_FOR_ALL_USERS
+            Calls.NUMBER,
+            Calls.NUMBER_PRESENTATION,
+            Calls.TYPE,
+            Calls.FEATURES,
+            Calls.DATE,
+            Calls.DURATION,
+            Calls.DATA_USAGE,
+            Calls.PHONE_ACCOUNT_COMPONENT_NAME,
+            Calls.PHONE_ACCOUNT_ID,
+            Calls.PRIORITY,
+            Calls.SUBJECT,
+            Calls.COMPOSER_PHOTO_URI,
+            // Location is deliberately omitted
+            Calls.ADD_FOR_ALL_USERS
     };
 
     static final String[] MINIMAL_PROJECTION = new String[] { Calls._ID };
@@ -103,6 +163,10 @@
 
     private static final int CALLS_FILTER = 3;
 
+    private static final int CALL_COMPOSER_NEW_PICTURE = 4;
+
+    private static final int CALL_COMPOSER_PICTURE = 5;
+
     private static final String UNHIDE_BY_PHONE_ACCOUNT_QUERY =
             "UPDATE " + Tables.CALLS + " SET " + Calls.PHONE_ACCOUNT_HIDDEN + "=0 WHERE " +
             Calls.PHONE_ACCOUNT_COMPONENT_NAME + "=? AND " + Calls.PHONE_ACCOUNT_ID + "=?;";
@@ -116,12 +180,20 @@
         sURIMatcher.addURI(CallLog.AUTHORITY, "calls", CALLS);
         sURIMatcher.addURI(CallLog.AUTHORITY, "calls/#", CALLS_ID);
         sURIMatcher.addURI(CallLog.AUTHORITY, "calls/filter/*", CALLS_FILTER);
+        sURIMatcher.addURI(CallLog.AUTHORITY, CallLog.CALL_COMPOSER_SEGMENT,
+                CALL_COMPOSER_NEW_PICTURE);
+        sURIMatcher.addURI(CallLog.AUTHORITY, CallLog.CALL_COMPOSER_SEGMENT + "/*",
+                CALL_COMPOSER_PICTURE);
 
-        // Shadow provider only supports "/calls".
+        // Shadow provider only supports "/calls" and "/call_composer".
         sURIMatcher.addURI(CallLog.SHADOW_AUTHORITY, "calls", CALLS);
+        sURIMatcher.addURI(CallLog.SHADOW_AUTHORITY, CallLog.CALL_COMPOSER_SEGMENT,
+                CALL_COMPOSER_NEW_PICTURE);
+        sURIMatcher.addURI(CallLog.SHADOW_AUTHORITY, CallLog.CALL_COMPOSER_SEGMENT + "/*",
+                CALL_COMPOSER_PICTURE);
     }
 
-    private static final ArrayMap<String, String> sCallsProjectionMap;
+    public static final ArrayMap<String, String> sCallsProjectionMap;
     static {
 
         // Calls projection map
@@ -162,6 +234,11 @@
             .put(Calls.CALL_SCREENING_COMPONENT_NAME, Calls.CALL_SCREENING_COMPONENT_NAME);
         sCallsProjectionMap.put(Calls.CALL_SCREENING_APP_NAME, Calls.CALL_SCREENING_APP_NAME);
         sCallsProjectionMap.put(Calls.BLOCK_REASON, Calls.BLOCK_REASON);
+        sCallsProjectionMap.put(Calls.MISSED_REASON, Calls.MISSED_REASON);
+        sCallsProjectionMap.put(Calls.PRIORITY, Calls.PRIORITY);
+        sCallsProjectionMap.put(Calls.COMPOSER_PHOTO_URI, Calls.COMPOSER_PHOTO_URI);
+        sCallsProjectionMap.put(Calls.SUBJECT, Calls.SUBJECT);
+        sCallsProjectionMap.put(Calls.LOCATION, Calls.LOCATION);
     }
 
     private static final String ALLOWED_PACKAGE_FOR_TESTING = "com.android.providers.contacts";
@@ -442,6 +519,11 @@
                 return Calls.CONTENT_ITEM_TYPE;
             case CALLS_FILTER:
                 return Calls.CONTENT_TYPE;
+            case CALL_COMPOSER_NEW_PICTURE:
+                return null; // No type for newly created files
+            case CALL_COMPOSER_PICTURE:
+                // We don't know the exact image format, so this is as specific as we can be.
+                return "application/octet-stream";
             default:
                 throw new IllegalArgumentException("Unknown URI: " + uri);
         }
@@ -493,6 +575,30 @@
                     " CUID=" + Binder.getCallingUid());
         }
         waitForAccess(mReadAccessLatch);
+        int match = sURIMatcher.match(uri);
+        switch (match) {
+            case CALL_COMPOSER_PICTURE: {
+                String fileName = uri.getLastPathSegment();
+                try {
+                    return allocateNewCallComposerPicture(values,
+                            CallLog.SHADOW_AUTHORITY.equals(uri.getAuthority()),
+                            fileName);
+                } catch (IOException e) {
+                    throw new ParcelableException(e);
+                }
+            }
+            case CALL_COMPOSER_NEW_PICTURE: {
+                try {
+                    return allocateNewCallComposerPicture(values,
+                            CallLog.SHADOW_AUTHORITY.equals(uri.getAuthority()));
+                } catch (IOException e) {
+                    throw new ParcelableException(e);
+                }
+            }
+            default:
+                // Fall through and execute the rest of the method for ordinary call log insertions.
+        }
+
         checkForSupportedColumns(sCallsProjectionMap, values);
         // Inserting a voicemail record through call_log requires the voicemail
         // permission and also requires the additional voicemail param set.
@@ -517,6 +623,170 @@
         return null;
     }
 
+    @Override
+    public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode)
+            throws FileNotFoundException {
+        int match = sURIMatcher.match(uri);
+        if (match != CALL_COMPOSER_PICTURE) {
+            throw new UnsupportedOperationException("The call log provider only supports opening"
+                    + " call composer pictures.");
+        }
+        int modeInt;
+        switch (mode) {
+            case "r":
+                modeInt = ParcelFileDescriptor.MODE_READ_ONLY;
+                break;
+            case "w":
+                modeInt = ParcelFileDescriptor.MODE_WRITE_ONLY;
+                break;
+            default:
+                throw new UnsupportedOperationException("The call log does not support opening"
+                        + " a call composer picture with mode " + mode);
+        }
+
+        try {
+            Path callComposerDir = getCallComposerPictureDirectory(getContext(), uri);
+            Path pictureFile = callComposerDir.resolve(uri.getLastPathSegment());
+            if (Files.notExists(pictureFile)) {
+                throw new FileNotFoundException(uri.toString()
+                        + " does not correspond to a valid file.");
+            }
+            return ParcelFileDescriptor.open(pictureFile.toFile(), modeInt);
+        } catch (IOException e) {
+            Log.e(TAG, "IOException while opening call composer file: " + e);
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public Bundle call(@NonNull String method, @Nullable String arg, @Nullable Bundle extras) {
+        Log.i(TAG, "Fetching list of Uris to sync");
+        if (!UserHandle.isSameApp(android.os.Process.myUid(), Binder.getCallingUid())) {
+            throw new SecurityException("call() functionality reserved"
+                    + " for internal use by the call log.");
+        }
+        if (!GET_CALL_COMPOSER_IMAGE_URIS.equals(method)) {
+            throw new UnsupportedOperationException("Invalid method passed to call(): " + method);
+        }
+        if (!extras.containsKey(EXTRA_SINCE_DATE)) {
+            throw new IllegalArgumentException("SINCE_DATE required");
+        }
+        if (!extras.containsKey(EXTRA_IS_SHADOW)) {
+            throw new IllegalArgumentException("IS_SHADOW required");
+        }
+        if (!extras.containsKey(EXTRA_ALL_USERS_ONLY)) {
+            throw new IllegalArgumentException("ALL_USERS_ONLY required");
+        }
+        boolean isShadow = extras.getBoolean(EXTRA_IS_SHADOW);
+        boolean allUsers = extras.getBoolean(EXTRA_ALL_USERS_ONLY);
+        long sinceDate = extras.getLong(EXTRA_SINCE_DATE);
+
+        try {
+            Path queryDir = allUsers
+                    ? getCallComposerAllUsersPictureDirectory(getContext(), isShadow)
+                    : getCallComposerPictureDirectory(getContext(), isShadow);
+            List<Path> newestPics = new ArrayList<>();
+            try (DirectoryStream<Path> dirStream =
+                         Files.newDirectoryStream(queryDir, entry -> {
+                             if (Files.isDirectory(entry)) {
+                                 return false;
+                             }
+                             FileTime createdAt =
+                                     (FileTime) Files.getAttribute(entry, "creationTime");
+                             return createdAt.toMillis() > sinceDate;
+                         })) {
+                dirStream.forEach(newestPics::add);
+            }
+            List<Uri> fileUris = newestPics.stream().map((path) -> {
+                String fileName = path.getFileName().toString();
+                // We don't need to worry about if it's for all users -- anything that's for
+                // all users is also stored in the regular location.
+                Uri base = isShadow ? CallLog.SHADOW_CALL_COMPOSER_PICTURE_URI
+                        : CallLog.CALL_COMPOSER_PICTURE_URI;
+                return base.buildUpon().appendPath(fileName).build();
+            }).collect(Collectors.toList());
+            Bundle result = new Bundle();
+            result.putParcelableList(EXTRA_RESULT_URIS, fileUris);
+            Log.i(TAG, "Will sync following Uris:" + fileUris);
+            return result;
+        } catch (IOException e) {
+            Log.e(TAG, "IOException while trying to fetch URI list: " + e);
+            return null;
+        }
+    }
+
+    private static @NonNull Path getCallComposerPictureDirectory(Context context, Uri uri)
+            throws IOException {
+        boolean isShadow = CallLog.SHADOW_AUTHORITY.equals(uri.getAuthority());
+        return getCallComposerPictureDirectory(context, isShadow);
+    }
+
+    private static @NonNull Path getCallComposerPictureDirectory(Context context, boolean isShadow)
+            throws IOException {
+        if (isShadow) {
+            context = context.createDeviceProtectedStorageContext();
+        }
+        Path path = context.getFilesDir().toPath().resolve(CALL_COMPOSER_PICTURE_DIRECTORY_NAME);
+        if (!Files.isDirectory(path)) {
+            Files.createDirectory(path);
+        }
+        return path;
+    }
+
+    private static @NonNull Path getCallComposerAllUsersPictureDirectory(
+            Context context, boolean isShadow) throws IOException {
+        Path pathToCallComposerDir = getCallComposerPictureDirectory(context, isShadow);
+        Path path = pathToCallComposerDir.resolve(CALL_COMPOSER_ALL_USERS_DIRECTORY_NAME);
+        if (!Files.isDirectory(path)) {
+            Files.createDirectory(path);
+        }
+        return path;
+    }
+
+    private Uri allocateNewCallComposerPicture(ContentValues values, boolean isShadow)
+            throws IOException {
+        return allocateNewCallComposerPicture(values, isShadow, UUID.randomUUID().toString());
+    }
+
+    private Uri allocateNewCallComposerPicture(ContentValues values,
+            boolean isShadow, String fileName) throws IOException {
+        Uri baseUri = isShadow ?
+                CallLog.CALL_COMPOSER_PICTURE_URI.buildUpon()
+                        .authority(CallLog.SHADOW_AUTHORITY).build()
+                : CallLog.CALL_COMPOSER_PICTURE_URI;
+
+        boolean forAllUsers = values.containsKey(Calls.ADD_FOR_ALL_USERS)
+                && (values.getAsInteger(Calls.ADD_FOR_ALL_USERS) == 1);
+        Path pathToCallComposerDir = getCallComposerPictureDirectory(getContext(), isShadow);
+
+        if (new StatFs(pathToCallComposerDir.toString()).getAvailableBytes()
+                < TelephonyManager.getMaximumCallComposerPictureSize()) {
+            return null;
+        }
+        Path pathToFile = pathToCallComposerDir.resolve(fileName);
+        Files.createFile(pathToFile);
+
+        if (forAllUsers) {
+            // Create a symlink in a subdirectory for copying later.
+            Path allUsersDir = getCallComposerAllUsersPictureDirectory(getContext(), isShadow);
+            Files.createSymbolicLink(allUsersDir.resolve(fileName), pathToFile);
+        }
+        return baseUri.buildUpon().appendPath(fileName).build();
+    }
+
+    private int deleteCallComposerPicture(Uri uri) {
+        try {
+            Path pathToCallComposerDir = getCallComposerPictureDirectory(getContext(), uri);
+            String fileName = uri.getLastPathSegment();
+            boolean successfulDelete =
+                    Files.deleteIfExists(pathToCallComposerDir.resolve(fileName));
+            return successfulDelete ? 1 : 0;
+        } catch (IOException e) {
+            Log.e(TAG, "IOException encountered deleting the call composer pics dir " + e);
+            return 0;
+        }
+    }
+
     private int updateInternal(Uri uri, ContentValues values,
             String selection, String[] selectionArgs) {
         if (VERBOSE_LOGGING) {
@@ -536,19 +806,8 @@
 
         SelectionBuilder selectionBuilder = new SelectionBuilder(selection);
         checkVoicemailPermissionAndAddRestriction(uri, selectionBuilder, false /*isQuery*/);
-
-        final SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
-        qb.setTables(Tables.CALLS);
-        qb.setProjectionMap(sCallsProjectionMap);
-        qb.setStrict(true);
-        // If the caller doesn't have READ_VOICEMAIL, make sure they can't
-        // do any SQL shenanigans to get access to the voicemails. If the caller does have the
-        // READ_VOICEMAIL permission, then they have sufficient permissions to access any data in
-        // the database, so the strict check is unnecessary.
-        if (!mVoicemailPermissions.callerHasReadAccess(getCallingPackage())) {
-            qb.setStrictGrammar(true);
-        }
-
+        boolean hasReadVoicemailPermission = mVoicemailPermissions.callerHasReadAccess(
+                getCallingPackage());
         final SQLiteDatabase db = mDbHelper.getWritableDatabase();
         final int matchedUriId = sURIMatcher.match(uri);
         switch (matchedUriId) {
@@ -563,7 +822,8 @@
                 throw new UnsupportedOperationException("Cannot update URL: " + uri);
         }
 
-        return qb.update(db, values, selectionBuilder.build(), selectionArgs);
+        return createDatabaseModifier(db, hasReadVoicemailPermission).update(uri, Tables.CALLS,
+                values, selectionBuilder.build(), selectionArgs);
     }
 
     private int deleteInternal(Uri uri, String selection, String[] selectionArgs) {
@@ -578,25 +838,18 @@
         SelectionBuilder selectionBuilder = new SelectionBuilder(selection);
         checkVoicemailPermissionAndAddRestriction(uri, selectionBuilder, false /*isQuery*/);
 
-        final SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
-        qb.setTables(Tables.CALLS);
-        qb.setProjectionMap(sCallsProjectionMap);
-        qb.setStrict(true);
-        // If the caller doesn't have READ_VOICEMAIL, make sure they can't
-        // do any SQL shenanigans to get access to the voicemails. If the caller does have the
-        // READ_VOICEMAIL permission, then they have sufficient permissions to access any data in
-        // the database, so the strict check is unnecessary.
-        if (!mVoicemailPermissions.callerHasReadAccess(getCallingPackage())) {
-            qb.setStrictGrammar(true);
-        }
-
+        boolean hasReadVoicemailPermission =
+                mVoicemailPermissions.callerHasReadAccess(getCallingPackage());
         final SQLiteDatabase db = mDbHelper.getWritableDatabase();
         final int matchedUriId = sURIMatcher.match(uri);
         switch (matchedUriId) {
             case CALLS:
-                // TODO: Special case - We may want to forward the delete request on user 0 to the
-                // shadow provider too.
-                return qb.delete(db, selectionBuilder.build(), selectionArgs);
+                return createDatabaseModifier(db, hasReadVoicemailPermission).delete(Tables.CALLS,
+                    selectionBuilder.build(), selectionArgs);
+            case CALL_COMPOSER_PICTURE:
+                // TODO(hallliu): implement deletion of file when the corresponding calllog entry
+                // gets deleted as well.
+                return deleteCallComposerPicture(uri);
             default:
                 throw new UnsupportedOperationException("Cannot delete that URL: " + uri);
         }
@@ -610,8 +863,9 @@
      * Returns a {@link DatabaseModifier} that takes care of sending necessary notifications
      * after the operation is performed.
      */
-    private DatabaseModifier createDatabaseModifier(SQLiteDatabase db) {
-        return new DbModifierWithNotification(Tables.CALLS, db, getContext());
+    private DatabaseModifier createDatabaseModifier(SQLiteDatabase db, boolean hasReadVoicemail) {
+        return new DbModifierWithNotification(Tables.CALLS, db, null, hasReadVoicemail,
+                getContext());
     }
 
     /**
@@ -753,8 +1007,68 @@
             // delete all entries in shadow.
             cr.delete(uri, Calls.DATE + "<= ?", new String[] {String.valueOf(newestTimeStamp)});
         }
+
+        try {
+            syncCallComposerPics(sourceUserId, sourceIsShadow, forAllUsersOnly, lastSyncTime);
+        } catch (Exception e) {
+            // Catch any exceptions to make sure we don't bring down the entire process if something
+            // goes wrong
+            StringWriter w = new StringWriter();
+            PrintWriter pw = new PrintWriter(w);
+            e.printStackTrace(pw);
+            Log.e(TAG, "Caught exception syncing call composer pics: " + e
+                    + "\n" + pw.toString());
+        }
     }
 
+    private void syncCallComposerPics(int sourceUserId, boolean sourceIsShadow,
+            boolean forAllUsersOnly, long lastSyncTime) {
+        Log.i(TAG, "Syncing call composer pics -- source user=" + sourceUserId + ","
+                + " isShadow=" + sourceIsShadow + ", forAllUser=" + forAllUsersOnly);
+        ContentResolver contentResolver = getContext().getContentResolver();
+        Bundle args = new Bundle();
+        args.putLong(EXTRA_SINCE_DATE, lastSyncTime);
+        args.putBoolean(EXTRA_ALL_USERS_ONLY, forAllUsersOnly);
+        args.putBoolean(EXTRA_IS_SHADOW, sourceIsShadow);
+        Uri queryUri = ContentProvider.maybeAddUserId(
+                sourceIsShadow
+                        ? CallLog.SHADOW_CALL_COMPOSER_PICTURE_URI
+                        : CallLog.CALL_COMPOSER_PICTURE_URI,
+                sourceUserId);
+        Bundle result = contentResolver.call(queryUri, GET_CALL_COMPOSER_IMAGE_URIS, null, args);
+        if (result == null || !result.containsKey(EXTRA_RESULT_URIS)) {
+            Log.e(TAG, "Failed to sync call composer pics -- invalid return from call()");
+            return;
+        }
+        List<Uri> urisToCopy = result.getParcelableArrayList(EXTRA_RESULT_URIS);
+        Log.i(TAG, "Syncing call composer pics -- got " + urisToCopy);
+        for (Uri uri : urisToCopy) {
+            try {
+                Uri uriWithUser = ContentProvider.maybeAddUserId(uri, sourceUserId);
+                Path newFilePath = getCallComposerPictureDirectory(getContext(), false)
+                        .resolve(uri.getLastPathSegment());
+                try (ParcelFileDescriptor remoteFile = contentResolver.openFile(uriWithUser,
+                        "r", null);
+                     OutputStream localOut =
+                             Files.newOutputStream(newFilePath, StandardOpenOption.CREATE_NEW)) {
+                    FileInputStream input = new FileInputStream(remoteFile.getFileDescriptor());
+                    byte[] buffer = new byte[1 << 14]; // 16kb
+                    while (true) {
+                        int numRead = input.read(buffer);
+                        if (numRead < 0) {
+                            break;
+                        }
+                        localOut.write(buffer, 0, numRead);
+                    }
+                }
+                contentResolver.delete(uriWithUser, null);
+            } catch (IOException e) {
+                Log.e(TAG, "IOException while syncing call composer pics: " + e);
+                // Keep going and get as many as we can.
+            }
+        }
+        
+    }
     /**
      * Un-hides any hidden call log entries that are associated with the specified handle.
      *
diff --git a/src/com/android/providers/contacts/ContactDirectoryManager.java b/src/com/android/providers/contacts/ContactDirectoryManager.java
index 8a1c88b..09e3ff3 100644
--- a/src/com/android/providers/contacts/ContactDirectoryManager.java
+++ b/src/com/android/providers/contacts/ContactDirectoryManager.java
@@ -245,8 +245,7 @@
         Log.i(TAG, "Discovered " + count + " contact directories in " + (end - start) + "ms");
 
         // Announce the change to listeners of the contacts authority
-        mContactsProvider.notifyChange(/* syncToNetwork =*/false,
-                /* syncToMetadataNetwork =*/false);
+        mContactsProvider.notifyChange(/* syncToNetwork =*/false);
 
         // We schedule a rescan if update(DIRECTORIES) is called while we're scanning all packages.
         if (mDirectoriesForceUpdated) {
diff --git a/src/com/android/providers/contacts/ContactMetadataProvider.java b/src/com/android/providers/contacts/ContactMetadataProvider.java
deleted file mode 100644
index 3cf7df2..0000000
--- a/src/com/android/providers/contacts/ContactMetadataProvider.java
+++ /dev/null
@@ -1,443 +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.ContentProvider;
-import android.content.ContentProviderOperation;
-import android.content.ContentProviderResult;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.IContentProvider;
-import android.content.OperationApplicationException;
-import android.content.UriMatcher;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteQueryBuilder;
-import android.net.Uri;
-import android.os.Binder;
-import android.provider.ContactsContract;
-import android.provider.ContactsContract.MetadataSync;
-import android.provider.ContactsContract.MetadataSyncState;
-import android.text.TextUtils;
-import android.util.Log;
-import com.android.common.content.ProjectionMap;
-import com.android.providers.contacts.ContactsDatabaseHelper.MetadataSyncColumns;
-import com.android.providers.contacts.ContactsDatabaseHelper.MetadataSyncStateColumns;
-import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
-import com.android.providers.contacts.ContactsDatabaseHelper.Views;
-import com.android.providers.contacts.MetadataEntryParser.MetadataEntry;
-import com.android.providers.contacts.util.SelectionBuilder;
-import com.android.providers.contacts.util.UserUtils;
-import com.google.common.annotations.VisibleForTesting;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Map;
-
-import static com.android.providers.contacts.ContactsProvider2.getLimit;
-import static com.android.providers.contacts.util.DbQueryUtils.getEqualityClause;
-
-/**
- * Simple content provider to handle directing contact metadata specific calls.
- */
-public class ContactMetadataProvider extends ContentProvider {
-    private static final String TAG = "ContactMetadata";
-    private static final boolean VERBOSE_LOGGING = Log.isLoggable(TAG, Log.VERBOSE);
-    private static final int METADATA_SYNC = 1;
-    private static final int METADATA_SYNC_ID = 2;
-    private static final int SYNC_STATE = 3;
-
-    private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
-
-    static {
-        sURIMatcher.addURI(MetadataSync.METADATA_AUTHORITY, "metadata_sync", METADATA_SYNC);
-        sURIMatcher.addURI(MetadataSync.METADATA_AUTHORITY, "metadata_sync/#", METADATA_SYNC_ID);
-        sURIMatcher.addURI(MetadataSync.METADATA_AUTHORITY, "metadata_sync_state", SYNC_STATE);
-    }
-
-    private static final Map<String, String> sMetadataProjectionMap = ProjectionMap.builder()
-            .add(MetadataSync._ID)
-            .add(MetadataSync.RAW_CONTACT_BACKUP_ID)
-            .add(MetadataSync.ACCOUNT_TYPE)
-            .add(MetadataSync.ACCOUNT_NAME)
-            .add(MetadataSync.DATA_SET)
-            .add(MetadataSync.DATA)
-            .add(MetadataSync.DELETED)
-            .build();
-
-    private static final Map<String, String> sSyncStateProjectionMap =ProjectionMap.builder()
-            .add(MetadataSyncState._ID)
-            .add(MetadataSyncState.ACCOUNT_TYPE)
-            .add(MetadataSyncState.ACCOUNT_NAME)
-            .add(MetadataSyncState.DATA_SET)
-            .add(MetadataSyncState.STATE)
-            .build();
-
-    private ContactsDatabaseHelper mDbHelper;
-    private ContactsProvider2 mContactsProvider;
-
-    private String mAllowedPackage;
-
-    @Override
-    public boolean onCreate() {
-        final Context context = getContext();
-        mDbHelper = getDatabaseHelper(context);
-        final IContentProvider iContentProvider = context.getContentResolver().acquireProvider(
-                ContactsContract.AUTHORITY);
-        final ContentProvider provider = ContentProvider.coerceToLocalContentProvider(
-                iContentProvider);
-        mContactsProvider = (ContactsProvider2) provider;
-
-        mAllowedPackage = getContext().getResources().getString(R.string.metadata_sync_pacakge);
-        return true;
-    }
-
-    protected ContactsDatabaseHelper getDatabaseHelper(final Context context) {
-        return ContactsDatabaseHelper.getInstance(context);
-    }
-
-    @VisibleForTesting
-    protected void setDatabaseHelper(final ContactsDatabaseHelper helper) {
-        mDbHelper = helper;
-    }
-
-    @Override
-    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
-            String sortOrder) {
-
-        ensureCaller();
-
-        if (VERBOSE_LOGGING) {
-            Log.v(TAG, "query: uri=" + uri + "  projection=" + Arrays.toString(projection) +
-                    "  selection=[" + selection + "]  args=" + Arrays.toString(selectionArgs) +
-                    "  order=[" + sortOrder + "] CPID=" + Binder.getCallingPid() +
-                    " User=" + UserUtils.getCurrentUserHandle(getContext()));
-        }
-        final SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
-        String limit = getLimit(uri);
-
-        final SelectionBuilder selectionBuilder = new SelectionBuilder(selection);
-
-        final int match = sURIMatcher.match(uri);
-        switch (match) {
-            case METADATA_SYNC:
-                setTablesAndProjectionMapForMetadata(qb);
-                break;
-
-            case METADATA_SYNC_ID: {
-                setTablesAndProjectionMapForMetadata(qb);
-                selectionBuilder.addClause(getEqualityClause(MetadataSync._ID,
-                        ContentUris.parseId(uri)));
-                break;
-            }
-
-            case SYNC_STATE:
-                setTablesAndProjectionMapForSyncState(qb);
-                break;
-            default:
-                throw new IllegalArgumentException("Unknown URL " + uri);
-        }
-
-        final SQLiteDatabase db = mDbHelper.getReadableDatabase();
-        return qb.query(db, projection, selectionBuilder.build(), selectionArgs, null,
-                null, sortOrder, limit);
-    }
-
-    @Override
-    public String getType(Uri uri) {
-        int match = sURIMatcher.match(uri);
-        switch (match) {
-            case METADATA_SYNC:
-                return MetadataSync.CONTENT_TYPE;
-            case METADATA_SYNC_ID:
-                return MetadataSync.CONTENT_ITEM_TYPE;
-            case SYNC_STATE:
-                return MetadataSyncState.CONTENT_TYPE;
-            default:
-                throw new IllegalArgumentException("Unknown URI: " + uri);
-        }
-    }
-
-    @Override
-    /**
-     * Insert or update if the raw is already existing.
-     */
-    public Uri insert(Uri uri, ContentValues values) {
-
-        ensureCaller();
-
-        final SQLiteDatabase db = mDbHelper.getWritableDatabase();
-        db.beginTransactionNonExclusive();
-        try {
-            final int matchedUriId = sURIMatcher.match(uri);
-            switch (matchedUriId) {
-                case METADATA_SYNC:
-                    // Insert the new entry, and also parse the data column to update related
-                    // tables.
-                    final long metadataSyncId = updateOrInsertDataToMetadataSync(db, uri, values);
-                    db.setTransactionSuccessful();
-                    return ContentUris.withAppendedId(uri, metadataSyncId);
-                case SYNC_STATE:
-                    replaceAccountInfoByAccountId(uri, values);
-                    final Long syncStateId = db.replace(
-                            Tables.METADATA_SYNC_STATE, MetadataSyncColumns.ACCOUNT_ID, values);
-                    db.setTransactionSuccessful();
-                    return ContentUris.withAppendedId(uri, syncStateId);
-                default:
-                    throw new IllegalArgumentException(mDbHelper.exceptionMessage(
-                            "Calling contact metadata insert on an unknown/invalid URI", uri));
-            }
-        } finally {
-            db.endTransaction();
-        }
-    }
-
-    @Override
-    public int delete(Uri uri, String selection, String[] selectionArgs) {
-
-        ensureCaller();
-
-        final SQLiteDatabase db = mDbHelper.getWritableDatabase();
-        db.beginTransactionNonExclusive();
-        try {
-            final int matchedUriId = sURIMatcher.match(uri);
-            int numDeletes = 0;
-            switch (matchedUriId) {
-                case METADATA_SYNC:
-                    Cursor c = db.query(Views.METADATA_SYNC, new String[]{MetadataSync._ID},
-                            selection, selectionArgs, null, null, null);
-                    try {
-                        while (c.moveToNext()) {
-                            final long contactMetadataId = c.getLong(0);
-                            numDeletes += db.delete(Tables.METADATA_SYNC,
-                                    MetadataSync._ID + "=" + contactMetadataId, null);
-                        }
-                    } finally {
-                        c.close();
-                    }
-                    db.setTransactionSuccessful();
-                    return numDeletes;
-                case SYNC_STATE:
-                    c = db.query(Views.METADATA_SYNC_STATE, new String[]{MetadataSyncState._ID},
-                            selection, selectionArgs, null, null, null);
-                    try {
-                        while (c.moveToNext()) {
-                            final long stateId = c.getLong(0);
-                            numDeletes += db.delete(Tables.METADATA_SYNC_STATE,
-                                    MetadataSyncState._ID + "=" + stateId, null);
-                        }
-                    } finally {
-                        c.close();
-                    }
-                    db.setTransactionSuccessful();
-                    return numDeletes;
-                default:
-                    throw new IllegalArgumentException(mDbHelper.exceptionMessage(
-                            "Calling contact metadata delete on an unknown/invalid URI", uri));
-            }
-        } finally {
-            db.endTransaction();
-        }
-    }
-
-    @Override
-    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
-
-        ensureCaller();
-
-        final SQLiteDatabase db = mDbHelper.getWritableDatabase();
-        db.beginTransactionNonExclusive();
-        try {
-            final int matchedUriId = sURIMatcher.match(uri);
-            switch (matchedUriId) {
-                // Do not support update metadata sync by update() method. Please use insert().
-                case SYNC_STATE:
-                    // Only support update by account.
-                    final Long accountId = replaceAccountInfoByAccountId(uri, values);
-                    if (accountId == null) {
-                        throw new IllegalArgumentException(mDbHelper.exceptionMessage(
-                                "Invalid identifier is found for accountId", uri));
-                    }
-                    values.put(MetadataSyncColumns.ACCOUNT_ID, accountId);
-                    // Insert a new row if it doesn't exist.
-                    db.replace(Tables.METADATA_SYNC_STATE, null, values);
-                    db.setTransactionSuccessful();
-                    return 1;
-                default:
-                    throw new IllegalArgumentException(mDbHelper.exceptionMessage(
-                            "Calling contact metadata update on an unknown/invalid URI", uri));
-            }
-        } finally {
-            db.endTransaction();
-        }
-    }
-
-    @Override
-    public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
-            throws OperationApplicationException {
-
-        ensureCaller();
-
-        if (VERBOSE_LOGGING) {
-            Log.v(TAG, "applyBatch: " + operations.size() + " ops");
-        }
-        final SQLiteDatabase db = mDbHelper.getWritableDatabase();
-        db.beginTransactionNonExclusive();
-        try {
-            ContentProviderResult[] results = super.applyBatch(operations);
-            db.setTransactionSuccessful();
-            return results;
-        } finally {
-            db.endTransaction();
-        }
-    }
-
-    @Override
-    public int bulkInsert(Uri uri, ContentValues[] values) {
-
-        ensureCaller();
-
-        if (VERBOSE_LOGGING) {
-            Log.v(TAG, "bulkInsert: " + values.length + " inserts");
-        }
-        final SQLiteDatabase db = mDbHelper.getWritableDatabase();
-        db.beginTransactionNonExclusive();
-        try {
-            final int numValues = super.bulkInsert(uri, values);
-            db.setTransactionSuccessful();
-            return numValues;
-        } finally {
-            db.endTransaction();
-        }
-    }
-
-    private void setTablesAndProjectionMapForMetadata(SQLiteQueryBuilder qb){
-        qb.setTables(Views.METADATA_SYNC);
-        qb.setProjectionMap(sMetadataProjectionMap);
-        qb.setStrict(true);
-    }
-
-    private void setTablesAndProjectionMapForSyncState(SQLiteQueryBuilder qb){
-        qb.setTables(Views.METADATA_SYNC_STATE);
-        qb.setProjectionMap(sSyncStateProjectionMap);
-        qb.setStrict(true);
-    }
-
-    /**
-     * Insert or update a non-deleted entry to MetadataSync table, and also parse the data column
-     * to update related tables for the raw contact.
-     * Returns new upserted metadataSyncId.
-     */
-    private long updateOrInsertDataToMetadataSync(SQLiteDatabase db, Uri uri, ContentValues values) {
-        final int matchUri = sURIMatcher.match(uri);
-        if (matchUri != METADATA_SYNC) {
-            throw new IllegalArgumentException(mDbHelper.exceptionMessage(
-                    "Calling contact metadata insert or update on an unknown/invalid URI", uri));
-        }
-
-        // Don't insert or update a deleted metadata.
-        Integer deleted = values.getAsInteger(MetadataSync.DELETED);
-        if (deleted != null && deleted != 0) {
-            throw new IllegalArgumentException(mDbHelper.exceptionMessage(
-                    "Cannot insert or update deleted metadata:" + values.toString(), uri));
-        }
-
-        // Check if data column is empty or null.
-        final String data = values.getAsString(MetadataSync.DATA);
-        if (TextUtils.isEmpty(data)) {
-            throw new IllegalArgumentException(mDbHelper.exceptionMessage(
-                    "Data column cannot be empty.", uri));
-        }
-
-        // Update or insert for backupId and account info.
-        final Long accountId = replaceAccountInfoByAccountId(uri, values);
-        final String rawContactBackupId = values.getAsString(
-                MetadataSync.RAW_CONTACT_BACKUP_ID);
-        // TODO (tingtingw): Consider a corner case: if there's raw with the same accountId and
-        // backupId, but deleted=1, (Deleted should be synced up to server and hard-deleted, but
-        // may be delayed.) In this case, should we not override it with delete=0? or should this
-        // be prevented by sync adapter side?.
-        deleted = 0; // Only insert or update non-deleted metadata
-        if (accountId == null) {
-            // Do nothing, just return.
-            return 0;
-        }
-        if (rawContactBackupId == null) {
-            throw new IllegalArgumentException(mDbHelper.exceptionMessage(
-                    "Invalid identifier is found: accountId=" + accountId + "; " +
-                            "rawContactBackupId=" + rawContactBackupId, uri));
-        }
-
-        // Update if it exists, otherwise insert.
-        final long metadataSyncId = mDbHelper.upsertMetadataSync(
-                rawContactBackupId, accountId, data, deleted);
-        if (metadataSyncId <= 0) {
-            throw new IllegalArgumentException(mDbHelper.exceptionMessage(
-                    "Metadata upsertion failed. Values= " + values.toString(), uri));
-        }
-
-        // Parse the data column and update other tables.
-        // Data field will never be empty or null, since contacts prefs and usage stats
-        // have default values.
-        final MetadataEntry metadataEntry = MetadataEntryParser.parseDataToMetaDataEntry(data);
-        mContactsProvider.updateFromMetaDataEntry(db, metadataEntry);
-
-        return metadataSyncId;
-    }
-
-    /**
-     *  Replace account_type, account_name and data_set with account_id. If a valid account_id
-     *  cannot be found for this combination, return null.
-     */
-    private Long replaceAccountInfoByAccountId(Uri uri, ContentValues values) {
-        String accountName = values.getAsString(MetadataSync.ACCOUNT_NAME);
-        String accountType = values.getAsString(MetadataSync.ACCOUNT_TYPE);
-        String dataSet = values.getAsString(MetadataSync.DATA_SET);
-        final boolean partialUri = TextUtils.isEmpty(accountName) ^ TextUtils.isEmpty(accountType);
-        if (partialUri) {
-            // Throw when either account is incomplete.
-            throw new IllegalArgumentException(mDbHelper.exceptionMessage(
-                    "Must specify both or neither of ACCOUNT_NAME and ACCOUNT_TYPE", uri));
-        }
-
-        final AccountWithDataSet account = AccountWithDataSet.get(
-                accountName, accountType, dataSet);
-
-        final Long id = mDbHelper.getAccountIdOrNull(account);
-        if (id == null) {
-            return null;
-        }
-
-        values.put(MetadataSyncColumns.ACCOUNT_ID, id);
-        // Only remove the account information once the account ID is extracted (since these
-        // fields are actually used by resolveAccountWithDataSet to extract the relevant ID).
-        values.remove(MetadataSync.ACCOUNT_NAME);
-        values.remove(MetadataSync.ACCOUNT_TYPE);
-        values.remove(MetadataSync.DATA_SET);
-
-        return id;
-    }
-
-    @VisibleForTesting
-    void ensureCaller() {
-        final String caller = getCallingPackage();
-        if (mAllowedPackage.equals(caller)) {
-            return; // Okay.
-        }
-        throw new SecurityException("Caller " + caller + " can't access ContactMetadataProvider");
-    }
-}
diff --git a/src/com/android/providers/contacts/ContactsDatabaseHelper.java b/src/com/android/providers/contacts/ContactsDatabaseHelper.java
index 2e5cdac..7f4188d 100644
--- a/src/com/android/providers/contacts/ContactsDatabaseHelper.java
+++ b/src/com/android/providers/contacts/ContactsDatabaseHelper.java
@@ -16,12 +16,6 @@
 
 package com.android.providers.contacts;
 
-import com.android.internal.R.bool;
-import com.android.providers.contacts.sqlite.DatabaseAnalyzer;
-import com.android.providers.contacts.sqlite.SqlChecker;
-import com.android.providers.contacts.sqlite.SqlChecker.InvalidSqlException;
-import com.android.providers.contacts.util.PropertyUtils;
-
 import android.app.ActivityManager;
 import android.content.ContentResolver;
 import android.content.ContentValues;
@@ -73,14 +67,13 @@
 import android.provider.ContactsContract.DisplayPhoto;
 import android.provider.ContactsContract.FullNameStyle;
 import android.provider.ContactsContract.Groups;
-import android.provider.ContactsContract.MetadataSync;
-import android.provider.ContactsContract.MetadataSyncState;
 import android.provider.ContactsContract.PhoneticNameStyle;
 import android.provider.ContactsContract.PhotoFiles;
 import android.provider.ContactsContract.PinnedPositions;
 import android.provider.ContactsContract.ProviderStatus;
 import android.provider.ContactsContract.RawContacts;
 import android.provider.ContactsContract.Settings;
+import android.provider.ContactsContract.SimAccount;
 import android.provider.ContactsContract.StatusUpdates;
 import android.provider.ContactsContract.StreamItemPhotos;
 import android.provider.ContactsContract.StreamItems;
@@ -98,17 +91,23 @@
 import android.util.Slog;
 
 import com.android.common.content.SyncStateContentProviderHelper;
+import com.android.internal.R.bool;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.providers.contacts.aggregation.util.CommonNicknameCache;
 import com.android.providers.contacts.database.ContactsTableUtil;
 import com.android.providers.contacts.database.DeletedContactsTableUtil;
 import com.android.providers.contacts.database.MoreDatabaseUtils;
+import com.android.providers.contacts.sqlite.DatabaseAnalyzer;
+import com.android.providers.contacts.sqlite.SqlChecker;
+import com.android.providers.contacts.sqlite.SqlChecker.InvalidSqlException;
 import com.android.providers.contacts.util.NeededForTesting;
+import com.android.providers.contacts.util.PropertyUtils;
 
 import java.io.PrintWriter;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.util.ArrayList;
+import java.util.List;
 import java.util.Locale;
 import java.util.Set;
 import java.util.concurrent.Executor;
@@ -143,9 +142,10 @@
      *   1200-1299 O
      *   1300-1399 P
      *   1400-1499 Q
+     *   1500-1599 S
      * </pre>
      */
-    static final int DATABASE_VERSION = 1400;
+    static final int DATABASE_VERSION = 1501;
     private static final int MINIMUM_SUPPORTED_VERSION = 700;
 
     @VisibleForTesting
@@ -189,8 +189,6 @@
         public static final String DIRECTORIES = "directories";
         public static final String DEFAULT_DIRECTORY = "default_directory";
         public static final String SEARCH_INDEX = "search_index";
-        public static final String METADATA_SYNC = "metadata_sync";
-        public static final String METADATA_SYNC_STATE = "metadata_sync_state";
         public static final String PRE_AUTHORIZED_URIS = "pre_authorized_uris";
 
         // This list of tables contains auto-incremented sequences.
@@ -313,15 +311,6 @@
                 + " JOIN " + Tables.ACCOUNTS + " ON ("
                 + AccountsColumns.CONCRETE_ID + "=" + RawContactsColumns.CONCRETE_ACCOUNT_ID
                 + ")";
-
-        public static final String RAW_CONTACTS_JOIN_METADATA_SYNC = Tables.RAW_CONTACTS
-                + " JOIN " + Tables.METADATA_SYNC + " ON ("
-                + RawContactsColumns.CONCRETE_BACKUP_ID + "="
-                + MetadataSyncColumns.CONCRETE_BACKUP_ID
-                + " AND "
-                + RawContactsColumns.CONCRETE_ACCOUNT_ID + "="
-                + MetadataSyncColumns.CONCRETE_ACCOUNT_ID
-                + ")";
     }
 
     public interface Joins {
@@ -712,6 +701,8 @@
         String ACCOUNT_NAME = RawContacts.ACCOUNT_NAME;
         String ACCOUNT_TYPE = RawContacts.ACCOUNT_TYPE;
         String DATA_SET = RawContacts.DATA_SET;
+        String SIM_SLOT_INDEX = "sim_slot_index";
+        String SIM_EF_TYPE = "sim_ef_type";
 
         String CONCRETE_ACCOUNT_NAME = Tables.ACCOUNTS + "." + ACCOUNT_NAME;
         String CONCRETE_ACCOUNT_TYPE = Tables.ACCOUNTS + "." + ACCOUNT_TYPE;
@@ -770,22 +761,6 @@
         public static final int USAGE_TYPE_INT_SHORT_TEXT = 2;
     }
 
-    public interface MetadataSyncColumns {
-        static final String CONCRETE_ID = Tables.METADATA_SYNC + "._id";
-        static final String ACCOUNT_ID = "account_id";
-        static final String CONCRETE_BACKUP_ID = Tables.METADATA_SYNC + "." +
-                MetadataSync.RAW_CONTACT_BACKUP_ID;
-        static final String CONCRETE_ACCOUNT_ID = Tables.METADATA_SYNC + "." + ACCOUNT_ID;
-        static final String CONCRETE_DELETED = Tables.METADATA_SYNC + "." +
-                MetadataSync.DELETED;
-    }
-
-    public interface MetadataSyncStateColumns {
-        static final String CONCRETE_ID = Tables.METADATA_SYNC_STATE + "._id";
-        static final String ACCOUNT_ID = "account_id";
-        static final String CONCRETE_ACCOUNT_ID = Tables.METADATA_SYNC_STATE + "." + ACCOUNT_ID;
-    }
-
     private  interface EmailQuery {
         public static final String TABLE = Tables.DATA;
 
@@ -1247,8 +1222,10 @@
                 AccountsColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
                 AccountsColumns.ACCOUNT_NAME + " TEXT, " +
                 AccountsColumns.ACCOUNT_TYPE + " TEXT, " +
-                AccountsColumns.DATA_SET + " TEXT" +
-        ");");
+                AccountsColumns.DATA_SET + " TEXT, " +
+                AccountsColumns.SIM_SLOT_INDEX + " INTEGER, " +
+                AccountsColumns.SIM_EF_TYPE + " INTEGER" +
+                ");");
 
         // 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
@@ -1622,34 +1599,11 @@
                 DataUsageStatColumns.USAGE_TYPE_INT +
         ");");
 
-        db.execSQL("CREATE TABLE IF NOT EXISTS "
-                + Tables.METADATA_SYNC + " (" +
-                MetadataSync._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
-                MetadataSync.RAW_CONTACT_BACKUP_ID + " TEXT NOT NULL," +
-                MetadataSyncColumns.ACCOUNT_ID + " INTEGER NOT NULL," +
-                MetadataSync.DATA + " TEXT," +
-                MetadataSync.DELETED + " INTEGER NOT NULL DEFAULT 0);");
-
-        db.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS metadata_sync_index ON " +
-                Tables.METADATA_SYNC + " (" +
-                MetadataSync.RAW_CONTACT_BACKUP_ID + ", " +
-                MetadataSyncColumns.ACCOUNT_ID +");");
-
         db.execSQL("CREATE TABLE " + Tables.PRE_AUTHORIZED_URIS + " ("+
                 PreAuthorizedUris._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
                 PreAuthorizedUris.URI + " STRING NOT NULL, " +
                 PreAuthorizedUris.EXPIRATION + " INTEGER NOT NULL DEFAULT 0);");
 
-        db.execSQL("CREATE TABLE IF NOT EXISTS "
-                + Tables.METADATA_SYNC_STATE + " (" +
-                MetadataSyncState._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
-                MetadataSyncStateColumns.ACCOUNT_ID + " INTEGER NOT NULL," +
-                MetadataSyncState.STATE + " BLOB);");
-
-        db.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS metadata_sync_state_index ON " +
-                Tables.METADATA_SYNC_STATE + " (" +
-                MetadataSyncColumns.ACCOUNT_ID +");");
-
         // When adding new tables, be sure to also add size-estimates in updateSqliteStats
         createContactsViews(db);
         createGroupsView(db);
@@ -2242,34 +2196,6 @@
                 + RawContactsColumns.CONCRETE_CONTACT_ID + "=" + ContactsColumns.CONCRETE_ID + ")";
 
         db.execSQL("CREATE VIEW " + Views.STREAM_ITEMS + " AS " + streamItemSelect);
-
-        String metadataSyncSelect = "SELECT " +
-                MetadataSyncColumns.CONCRETE_ID + ", " +
-                MetadataSync.RAW_CONTACT_BACKUP_ID + ", " +
-                AccountsColumns.ACCOUNT_NAME + ", " +
-                AccountsColumns.ACCOUNT_TYPE + ", " +
-                AccountsColumns.DATA_SET + ", " +
-                MetadataSync.DATA + ", " +
-                MetadataSync.DELETED +
-                " FROM " + Tables.METADATA_SYNC
-                + " JOIN " + Tables.ACCOUNTS + " ON ("
-                +   MetadataSyncColumns.CONCRETE_ACCOUNT_ID + "=" + AccountsColumns.CONCRETE_ID
-                + ")";
-
-        db.execSQL("CREATE VIEW " + Views.METADATA_SYNC + " AS " + metadataSyncSelect);
-
-        String metadataSyncStateSelect = "SELECT " +
-                MetadataSyncStateColumns.CONCRETE_ID + ", " +
-                AccountsColumns.ACCOUNT_NAME + ", " +
-                AccountsColumns.ACCOUNT_TYPE + ", " +
-                AccountsColumns.DATA_SET + ", " +
-                MetadataSyncState.STATE +
-                " FROM " + Tables.METADATA_SYNC_STATE
-                + " JOIN " + Tables.ACCOUNTS + " ON ("
-                +   MetadataSyncStateColumns.CONCRETE_ACCOUNT_ID + "=" + AccountsColumns.CONCRETE_ID
-                + ")";
-
-        db.execSQL("CREATE VIEW " + Views.METADATA_SYNC_STATE + " AS " + metadataSyncStateSelect);
     }
 
     private static String buildDisplayPhotoUriAlias(String contactIdColumn, String alias) {
@@ -2655,6 +2581,19 @@
             oldVersion = 1400;
         }
 
+        if (isUpgradeRequired(oldVersion, newVersion, 1500)) {
+            db.execSQL("DROP TABLE IF EXISTS metadata_sync;");
+            db.execSQL("DROP TABLE IF EXISTS metadata_sync_state;");
+            upgradeViewsAndTriggers = true;
+            oldVersion = 1500;
+        }
+
+        if (isUpgradeRequired(oldVersion, newVersion, 1501)) {
+            upgradeToVersion1501(db);
+            upgradeViewsAndTriggers = true;
+            oldVersion = 1501;
+        }
+
         // We extracted "calls" and "voicemail_status" at this point, but we can't remove them here
         // yet, until CallLogDatabaseHelper moves the data.
 
@@ -3317,30 +3256,19 @@
     }
 
     /**
-     * Add new metadata_sync table to cache the meta data on raw contacts level from server before
-     * they are merged into other CP2 tables. The data column is the blob column containing all
-     * the backed up metadata for this raw_contact. This table should only be used by metadata
-     * sync adapter.
+     * Used to add new metadata_sync table to cache the meta data on raw contacts level from server
+     * before they are merged into other CP2 tables. The table is not used any more.
      */
     public void upgradeToVersion1104(SQLiteDatabase db) {
         db.execSQL("DROP TABLE IF EXISTS metadata_sync;");
-        db.execSQL("CREATE TABLE metadata_sync (" +
-                "_id INTEGER PRIMARY KEY AUTOINCREMENT, raw_contact_backup_id TEXT NOT NULL, " +
-                "account_id INTEGER NOT NULL, data TEXT, deleted INTEGER NOT NULL DEFAULT 0);");
-        db.execSQL("CREATE UNIQUE INDEX metadata_sync_index ON metadata_sync (" +
-                "raw_contact_backup_id, account_id);");
     }
 
     /**
-     * Add new metadata_sync_state table to store the metadata sync state for a set of accounts.
+     * Used to add new metadata_sync_state table to store the metadata sync state for a set of
+     * accounts. The table is not used any more.
      */
     public void upgradeToVersion1105(SQLiteDatabase db) {
         db.execSQL("DROP TABLE IF EXISTS metadata_sync_state;");
-        db.execSQL("CREATE TABLE metadata_sync_state (" +
-                "_id INTEGER PRIMARY KEY AUTOINCREMENT, " +
-                "account_id INTEGER NOT NULL, state BLOB);");
-        db.execSQL("CREATE UNIQUE INDEX metadata_sync_state_index ON metadata_sync_state (" +
-                "account_id);");
     }
 
     public void upgradeToVersion1106(SQLiteDatabase db) {
@@ -3439,6 +3367,14 @@
         }
     }
 
+    private void upgradeToVersion1501(SQLiteDatabase db) {
+        try {
+            db.execSQL("ALTER TABLE accounts ADD sim_slot_index INTEGER;");
+            db.execSQL("ALTER TABLE accounts ADD sim_ef_type INTEGER;");
+        } catch (SQLException ignore) {
+        }
+    }
+
     /**
      * 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
@@ -3693,9 +3629,6 @@
             updateIndexStats(db, Tables.DATA_USAGE_STAT,
                     "data_usage_stat_index", "20 2 1");
 
-            updateIndexStats(db, Tables.METADATA_SYNC,
-                    "metadata_sync_index", "10000 1 1");
-
             // Tiny tables
             updateIndexStats(db, Tables.AGGREGATION_EXCEPTIONS,
                     null, "10");
@@ -3716,9 +3649,6 @@
             updateIndexStats(db, "properties",
                     "sqlite_autoindex_properties_1", "4 1");
 
-            updateIndexStats(db, Tables.METADATA_SYNC_STATE,
-                    "metadata_sync_state_index", "2 1 1");
-
             // Search index
             updateIndexStats(db, "search_index_docsize",
                     null, "9000");
@@ -3980,6 +3910,37 @@
     }
 
     /**
+     * Gets all SIM accounts in the accounts table.
+     */
+    public List<SimAccount> getAllSimAccounts() {
+        final List<SimAccount> result = new ArrayList<>();
+        final Cursor c = getReadableDatabase().rawQuery(
+                "SELECT DISTINCT " + AccountsColumns._ID + ","
+                        + AccountsColumns.ACCOUNT_NAME + ","
+                        + AccountsColumns.ACCOUNT_TYPE + ","
+                        + AccountsColumns.SIM_SLOT_INDEX + ","
+                        + AccountsColumns.SIM_EF_TYPE + " FROM " + Tables.ACCOUNTS, null);
+        try {
+            while (c.moveToNext()) {
+                if (c.isNull(3) || c.isNull(4)) {
+                    // Invalid slot index or ef type
+                    continue;
+                }
+                final int simSlot = c.getInt(3);
+                final int efType = c.getInt(4);
+                if (simSlot < 0 || !SimAccount.getValidEfTypes().contains(efType)) {
+                    // Invalid slot index or ef type
+                    continue;
+                }
+                result.add(new SimAccount(c.getString(1), c.getString(2), simSlot, efType));
+            }
+        } finally {
+            c.close();
+        }
+        return result;
+    }
+
+    /**
      * @return ID of the specified account, or null if the account doesn't exist.
      */
     public Long getAccountIdOrNull(AccountWithDataSet accountWithDataSet) {
@@ -4042,6 +4003,69 @@
     }
 
     /**
+     * This method will create a record in the accounts table.
+     *
+     * This must be used in a transaction, so there's no need for synchronization.
+     *
+     * @param simSlot Sim slot index of the account. Must be 0 or greater
+     * @param efType  EF type of the account. Must be a value contained in {@link
+     *                SimAccount#getValidEfTypes()}
+     * @throws IllegalArgumentException if the account name/type pair is already within the table.
+     *                                  SIM accounts should have distinct names and types.
+     *                                  And if simSlot is negative, or efType is not in {@link
+     *                                  SimAccount#getValidEfTypes()}
+     */
+    public long createSimAccountIdInTransaction(AccountWithDataSet accountWithDataSet,
+            int simSlot, int efType) {
+        if (simSlot < 0) {
+            throw new IllegalArgumentException("Sim slot is negative");
+        }
+        if (!SimAccount.getValidEfTypes().contains(efType)) {
+            throw new IllegalArgumentException("Invalid EF type");
+        }
+        if (accountWithDataSet == null || TextUtils.isEmpty(accountWithDataSet.getAccountName())
+                || TextUtils.isEmpty(accountWithDataSet.getAccountType())) {
+            throw new IllegalArgumentException("Account is null or the name/type is empty");
+        }
+
+        Long id = getAccountIdOrNull(accountWithDataSet);
+        if (id != null) {
+            throw new IllegalArgumentException("Account already exists in the table");
+        }
+        final SQLiteStatement insert = getWritableDatabase().compileStatement(
+                "INSERT INTO " + Tables.ACCOUNTS +
+                        " (" + AccountsColumns.ACCOUNT_NAME + ", " +
+                        AccountsColumns.ACCOUNT_TYPE + ", " +
+                        AccountsColumns.DATA_SET + ", " +
+                        AccountsColumns.SIM_SLOT_INDEX + ", " +
+                        AccountsColumns.SIM_EF_TYPE + ") VALUES (?, ?, ?, ?, ?)");
+        try {
+            DatabaseUtils.bindObjectToProgram(insert, 1, accountWithDataSet.getAccountName());
+            DatabaseUtils.bindObjectToProgram(insert, 2, accountWithDataSet.getAccountType());
+            DatabaseUtils.bindObjectToProgram(insert, 3, accountWithDataSet.getDataSet());
+            DatabaseUtils.bindObjectToProgram(insert, 4, simSlot);
+            DatabaseUtils.bindObjectToProgram(insert, 5, efType);
+            id = insert.executeInsert();
+        } finally {
+            insert.close();
+        }
+
+        return id;
+    }
+
+    /**
+     * Deletes all rows in the accounts table with the given sim slot index
+     *
+     * @param simSlot Sim slot to remove accounts
+     * @return how many rows were deleted
+     */
+    public int removeSimAccounts(int simSlot) {
+        final SQLiteDatabase db = getWritableDatabase();
+        return db.delete(Tables.ACCOUNTS, AccountsColumns.SIM_SLOT_INDEX + "=?",
+                new String[]{String.valueOf(simSlot)});
+    }
+
+    /**
      * Update {@link Contacts#IN_VISIBLE_GROUP} for all contacts.
      */
     public void updateAllVisible() {
@@ -4989,22 +5013,6 @@
                 new String[] {String.valueOf(contactId)});
     }
 
-    public long upsertMetadataSync(String backupId, Long accountId, String data, Integer deleted) {
-        final SQLiteStatement metadataSyncInsert = getWritableDatabase().compileStatement(
-                    "INSERT OR REPLACE INTO " + Tables.METADATA_SYNC + "("
-                            + MetadataSync.RAW_CONTACT_BACKUP_ID + ", "
-                            + MetadataSyncColumns.ACCOUNT_ID + ", "
-                            + MetadataSync.DATA + ","
-                            + MetadataSync.DELETED + ")" +
-                            " VALUES (?,?,?,?)");
-        metadataSyncInsert.bindString(1, backupId);
-        metadataSyncInsert.bindLong(2, accountId);
-        data = (data == null) ? "" : data;
-        metadataSyncInsert.bindString(3, data);
-        metadataSyncInsert.bindLong(4, deleted);
-        return metadataSyncInsert.executeInsert();
-    }
-
     public static void notifyProviderStatusChange(Context context) {
         context.getContentResolver().notifyChange(ProviderStatus.CONTENT_URI,
                 /* observer= */ null, /* syncToNetwork= */ false);
diff --git a/src/com/android/providers/contacts/ContactsProvider2.java b/src/com/android/providers/contacts/ContactsProvider2.java
index 5159fb9..0c6e819 100644
--- a/src/com/android/providers/contacts/ContactsProvider2.java
+++ b/src/com/android/providers/contacts/ContactsProvider2.java
@@ -16,9 +16,9 @@
 
 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 static android.content.pm.PackageManager.PERMISSION_GRANTED;
 
 import android.accounts.Account;
 import android.accounts.AccountManager;
@@ -34,6 +34,7 @@
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.IContentService;
+import android.content.Intent;
 import android.content.OperationApplicationException;
 import android.content.SharedPreferences;
 import android.content.SyncAdapterType;
@@ -59,8 +60,11 @@
 import android.net.Uri.Builder;
 import android.os.AsyncTask;
 import android.os.Binder;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.CancellationSignal;
+import android.os.Handler;
+import android.os.Looper;
 import android.os.ParcelFileDescriptor;
 import android.os.ParcelFileDescriptor.AutoCloseInputStream;
 import android.os.RemoteException;
@@ -94,7 +98,6 @@
 import android.provider.ContactsContract.Directory;
 import android.provider.ContactsContract.DisplayPhoto;
 import android.provider.ContactsContract.Groups;
-import android.provider.ContactsContract.MetadataSync;
 import android.provider.ContactsContract.PhoneLookup;
 import android.provider.ContactsContract.PhotoFiles;
 import android.provider.ContactsContract.PinnedPositions;
@@ -104,6 +107,8 @@
 import android.provider.ContactsContract.RawContactsEntity;
 import android.provider.ContactsContract.SearchSnippets;
 import android.provider.ContactsContract.Settings;
+import android.provider.ContactsContract.SimAccount;
+import android.provider.ContactsContract.SimContacts;
 import android.provider.ContactsContract.StatusUpdates;
 import android.provider.ContactsContract.StreamItemPhotos;
 import android.provider.ContactsContract.StreamItems;
@@ -134,8 +139,6 @@
 import com.android.providers.contacts.ContactsDatabaseHelper.DbProperties;
 import com.android.providers.contacts.ContactsDatabaseHelper.GroupsColumns;
 import com.android.providers.contacts.ContactsDatabaseHelper.Joins;
-import com.android.providers.contacts.ContactsDatabaseHelper.MetadataSyncColumns;
-import com.android.providers.contacts.ContactsDatabaseHelper.MetadataSyncStateColumns;
 import com.android.providers.contacts.ContactsDatabaseHelper.NameLookupColumns;
 import com.android.providers.contacts.ContactsDatabaseHelper.NameLookupType;
 import com.android.providers.contacts.ContactsDatabaseHelper.PhoneLookupColumns;
@@ -152,11 +155,6 @@
 import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
 import com.android.providers.contacts.ContactsDatabaseHelper.ViewGroupsColumns;
 import com.android.providers.contacts.ContactsDatabaseHelper.Views;
-import com.android.providers.contacts.MetadataEntryParser.AggregationData;
-import com.android.providers.contacts.MetadataEntryParser.FieldData;
-import com.android.providers.contacts.MetadataEntryParser.MetadataEntry;
-import com.android.providers.contacts.MetadataEntryParser.RawContactInfo;
-import com.android.providers.contacts.MetadataEntryParser.UsageStats;
 import com.android.providers.contacts.SearchIndexManager.FtsQueryBuilder;
 import com.android.providers.contacts.aggregation.AbstractContactAggregator;
 import com.android.providers.contacts.aggregation.AbstractContactAggregator.AggregationSuggestionParameter;
@@ -172,6 +170,8 @@
 import com.android.providers.contacts.util.Clock;
 import com.android.providers.contacts.util.ContactsPermissions;
 import com.android.providers.contacts.util.DbQueryUtils;
+import com.android.providers.contacts.util.LogFields;
+import com.android.providers.contacts.util.LogUtils;
 import com.android.providers.contacts.util.NeededForTesting;
 import com.android.providers.contacts.util.UserUtils;
 import com.android.vcard.VCardComposer;
@@ -218,6 +218,8 @@
 
     private static final String READ_PERMISSION = "android.permission.READ_CONTACTS";
     private static final String WRITE_PERMISSION = "android.permission.WRITE_CONTACTS";
+    private static final String MANAGE_SIM_ACCOUNTS_PERMISSION =
+            "android.contacts.permission.MANAGE_SIM_ACCOUNTS";
 
 
     /* package */ static final String PHONEBOOK_COLLATOR_NAME = "PHONEBOOK";
@@ -258,6 +260,9 @@
     /** Limit for the maximum number of social stream items to store under a raw contact. */
     private static final int MAX_STREAM_ITEMS_PER_RAW_CONTACT = 5;
 
+    /** Rate limit (in milliseconds) for notify change.  Do it as most once every 5 seconds. */
+    private static final int NOTIFY_CHANGE_RATE_LIMIT = 5 * 1000;
+
     /** 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;
 
@@ -1452,13 +1457,14 @@
     private boolean mVisibleTouched = false;
 
     private boolean mSyncToNetwork;
-    private boolean mSyncToMetadataNetWork;
 
     private LocaleSet mCurrentLocales;
     private int mContactsAccountCount;
 
     private ContactsTaskScheduler mTaskScheduler;
 
+    private long mLastNotifyChange = 0;
+
     private long mLastPhotoCleanup = 0;
 
     private FastScrollingIndexCache mFastScrollingIndexCache;
@@ -1468,9 +1474,6 @@
     private int mFastScrollingIndexCacheMissCount;
     private long mTotalTimeFastScrollingIndexGenerate;
 
-    // MetadataSync flag.
-    private boolean mMetadataSyncEnabled;
-
     // Enterprise members
     private EnterprisePolicyGuard mEnterprisePolicyGuard;
 
@@ -1480,6 +1483,13 @@
             Log.v(TAG, "onCreate user="
                     + android.os.Process.myUserHandle().getIdentifier());
         }
+        if (Build.IS_DEBUGGABLE) {
+            StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
+                    .detectLeakedSqlLiteObjects()  // for SqlLiteCursor
+                    .detectLeakedClosableObjects() // for any Cursor
+                    .penaltyLog()
+                    .build());
+        }
 
         if (Log.isLoggable(Constants.PERFORMANCE_TAG, Log.DEBUG)) {
             Log.d(Constants.PERFORMANCE_TAG, "ContactsProvider2.onCreate start");
@@ -1514,9 +1524,6 @@
 
         mFastScrollingIndexCache = FastScrollingIndexCache.getInstance(getContext());
 
-        mMetadataSyncEnabled = android.provider.Settings.Global.getInt(
-                getContext().getContentResolver(), Global.CONTACT_METADATA_SYNC_ENABLED, 0) == 1;
-
         mContactsHelper = getDatabaseHelper();
         mDbHelper.set(mContactsHelper);
 
@@ -1970,8 +1977,7 @@
                         ContentValues updateValues = new ContentValues();
                         updateValues.putNull(Photo.PHOTO_FILE_ID);
                         updateData(ContentUris.withAppendedId(Data.CONTENT_URI, dataId),
-                                updateValues, null, null, /* callerIsSyncAdapter =*/false,
-                                /* callerIsMetadataSyncAdapter =*/false);
+                                updateValues, null, null, /* callerIsSyncAdapter =*/false);
                     }
                     if (photoFileIdToStreamItemPhotoId.containsKey(missingPhotoId)) {
                         // For missing photos that were in stream item photos, just delete the
@@ -2170,49 +2176,106 @@
 
     @Override
     public Uri insert(Uri uri, ContentValues values) {
-        waitForAccess(mWriteAccessLatch);
+        LogFields.Builder logBuilder = LogFields.Builder.aLogFields()
+                .setApiType(LogUtils.ApiType.INSERT)
+                .setUriType(sUriMatcher.match(uri))
+                .setCallerIsSyncAdapter(readBooleanQueryParameter(
+                        uri, ContactsContract.CALLER_IS_SYNCADAPTER, false))
+                .setStartNanos(SystemClock.elapsedRealtimeNanos());
+        Uri resultUri = null;
 
-        mContactsHelper.validateContentValues(getCallingPackage(), values);
+        try {
+            waitForAccess(mWriteAccessLatch);
 
-        if (mapsToProfileDbWithInsertedValues(uri, values)) {
-            switchToProfileMode();
-            return mProfileProvider.insert(uri, values);
+            mContactsHelper.validateContentValues(getCallingPackage(), values);
+
+            if (mapsToProfileDbWithInsertedValues(uri, values)) {
+                switchToProfileMode();
+                resultUri = mProfileProvider.insert(uri, values);
+                return resultUri;
+            }
+            switchToContactMode();
+            resultUri = super.insert(uri, values);
+            return resultUri;
+        } catch (Exception e) {
+            logBuilder.setException(e);
+            throw e;
+        } finally {
+            LogUtils.log(
+                    logBuilder.setResultUri(resultUri).setResultCount(resultUri == null ? 0 : 1)
+                            .build());
         }
-        switchToContactMode();
-        return super.insert(uri, values);
     }
 
     @Override
     public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
-        waitForAccess(mWriteAccessLatch);
+        LogFields.Builder logBuilder = LogFields.Builder.aLogFields()
+                .setApiType(LogUtils.ApiType.UPDATE)
+                .setUriType(sUriMatcher.match(uri))
+                .setCallerIsSyncAdapter(readBooleanQueryParameter(
+                        uri, ContactsContract.CALLER_IS_SYNCADAPTER, false))
+                .setStartNanos(SystemClock.elapsedRealtimeNanos());
+        int updates = 0;
 
-        mContactsHelper.validateContentValues(getCallingPackage(), values);
-        mContactsHelper.validateSql(getCallingPackage(), selection);
+        try {
+            waitForAccess(mWriteAccessLatch);
 
-        if (mapsToProfileDb(uri)) {
-            switchToProfileMode();
-            return mProfileProvider.update(uri, values, selection, selectionArgs);
+            mContactsHelper.validateContentValues(getCallingPackage(), values);
+            mContactsHelper.validateSql(getCallingPackage(), selection);
+
+            if (mapsToProfileDb(uri)) {
+                switchToProfileMode();
+                updates = mProfileProvider.update(uri, values, selection, selectionArgs);
+                return updates;
+            }
+            switchToContactMode();
+            updates = super.update(uri, values, selection, selectionArgs);
+            return updates;
+        } catch (Exception e) {
+            logBuilder.setException(e);
+            throw e;
+        } finally {
+            LogUtils.log(logBuilder.setResultCount(updates).build());
         }
-        switchToContactMode();
-        return super.update(uri, values, selection, selectionArgs);
     }
 
     @Override
     public int delete(Uri uri, String selection, String[] selectionArgs) {
-        waitForAccess(mWriteAccessLatch);
+        LogFields.Builder logBuilder = LogFields.Builder.aLogFields()
+                .setApiType(LogUtils.ApiType.DELETE)
+                .setUriType(sUriMatcher.match(uri))
+                .setCallerIsSyncAdapter(readBooleanQueryParameter(
+                        uri, ContactsContract.CALLER_IS_SYNCADAPTER, false))
+                .setStartNanos(SystemClock.elapsedRealtimeNanos());
+        int deletes = 0;
 
-        mContactsHelper.validateSql(getCallingPackage(), selection);
+        try {
+            waitForAccess(mWriteAccessLatch);
 
-        if (mapsToProfileDb(uri)) {
-            switchToProfileMode();
-            return mProfileProvider.delete(uri, selection, selectionArgs);
+            mContactsHelper.validateSql(getCallingPackage(), selection);
+
+            if (mapsToProfileDb(uri)) {
+                switchToProfileMode();
+                deletes = mProfileProvider.delete(uri, selection, selectionArgs);
+                return deletes;
+            }
+            switchToContactMode();
+            deletes = super.delete(uri, selection, selectionArgs);
+            return deletes;
+        } catch (Exception e) {
+            logBuilder.setException(e);
+            throw e;
+        } finally {
+            LogUtils.log(logBuilder.setResultCount(deletes).build());
         }
-        switchToContactMode();
-        return super.delete(uri, selection, selectionArgs);
     }
 
     @Override
     public Bundle call(String method, String arg, Bundle extras) {
+        LogFields.Builder logBuilder =
+                LogFields.Builder.aLogFields()
+                        .setApiType(LogUtils.ApiType.CALL)
+                        .setStartNanos(SystemClock.elapsedRealtimeNanos());
         waitForAccess(mReadAccessLatch);
         switchToContactMode();
         if (Authorization.AUTHORIZATION_METHOD.equals(method)) {
@@ -2235,6 +2298,95 @@
             }
             undemoteContact(mDbHelper.get().getWritableDatabase(), id);
             return null;
+        } else if (SimContacts.ADD_SIM_ACCOUNT_METHOD.equals(method)) {
+            ContactsPermissions.enforceCallingOrSelfPermission(getContext(),
+                    MANAGE_SIM_ACCOUNTS_PERMISSION);
+
+            final String accountName = extras.getString(SimContacts.KEY_ACCOUNT_NAME);
+            final String accountType = extras.getString(SimContacts.KEY_ACCOUNT_TYPE);
+            final int simSlot = extras.getInt(SimContacts.KEY_SIM_SLOT_INDEX, -1);
+            final int efType = extras.getInt(SimContacts.KEY_SIM_EF_TYPE, -1);
+            if (simSlot < 0) {
+                throw new IllegalArgumentException("Sim slot is negative");
+            }
+            if (!SimAccount.getValidEfTypes().contains(efType)) {
+                throw new IllegalArgumentException("Invalid EF type");
+            }
+            if (TextUtils.isEmpty(accountName) || TextUtils.isEmpty(accountType)) {
+                throw new IllegalArgumentException("Account name or type is empty");
+            }
+
+            long resultId = -1;
+            final Bundle response = new Bundle();
+            final SQLiteDatabase db = mDbHelper.get().getWritableDatabase();
+            db.beginTransaction();
+            try {
+                resultId = mDbHelper.get().createSimAccountIdInTransaction(
+                        AccountWithDataSet.get(accountName, accountType, null), simSlot, efType);
+                db.setTransactionSuccessful();
+            } catch (Exception e) {
+                logBuilder.setException(e);
+                throw e;
+            } finally {
+                LogUtils.log(
+                        logBuilder
+                                .setMethodCall(LogUtils.MethodCall.ADD_SIM_ACCOUNTS)
+                                .setResultCount(resultId > -1 ? 1 : 0)
+                                .build());
+                db.endTransaction();
+            }
+
+            getContext().sendBroadcast(new Intent(SimContacts.ACTION_SIM_ACCOUNTS_CHANGED));
+            return response;
+        } else if (SimContacts.REMOVE_SIM_ACCOUNT_METHOD.equals(method)) {
+            ContactsPermissions.enforceCallingOrSelfPermission(
+                    getContext(), MANAGE_SIM_ACCOUNTS_PERMISSION);
+
+            final int simSlot = extras.getInt(SimContacts.KEY_SIM_SLOT_INDEX, -1);
+            if (simSlot < 0) {
+                throw new IllegalArgumentException("Sim slot is negative");
+            }
+
+            int removedCount = 0;
+            final Bundle response = new Bundle();
+            final SQLiteDatabase db = mDbHelper.get().getWritableDatabase();
+            db.beginTransaction();
+            try {
+                removedCount = mDbHelper.get().removeSimAccounts(simSlot);
+                scheduleBackgroundTask(BACKGROUND_TASK_UPDATE_ACCOUNTS);
+                db.setTransactionSuccessful();
+            } catch (Exception e) {
+                logBuilder.setException(e);
+                throw e;
+            } finally {
+                LogUtils.log(
+                        logBuilder
+                                .setMethodCall(LogUtils.MethodCall.REMOVE_SIM_ACCOUNTS)
+                                .setResultCount(removedCount)
+                                .build());
+                db.endTransaction();
+            }
+            getContext().sendBroadcast(new Intent(SimContacts.ACTION_SIM_ACCOUNTS_CHANGED));
+            return response;
+        } else if (SimContacts.QUERY_SIM_ACCOUNTS_METHOD.equals(method)) {
+            ContactsPermissions.enforceCallingOrSelfPermission(getContext(), READ_PERMISSION);
+            final Bundle response = new Bundle();
+            int accountsCount = 0;
+            try {
+                final List<SimAccount> simAccounts = mDbHelper.get().getAllSimAccounts();
+                response.putParcelableList(SimContacts.KEY_SIM_ACCOUNTS, simAccounts);
+                accountsCount = simAccounts.size();
+                return response;
+            } catch (Exception e) {
+                logBuilder.setException(e);
+                throw e;
+            } finally {
+                LogUtils.log(
+                        logBuilder
+                                .setMethodCall(LogUtils.MethodCall.GET_SIM_ACCOUNTS)
+                                .setResultCount(accountsCount)
+                                .build());
+            }
         }
         return null;
     }
@@ -2454,11 +2606,6 @@
         mTransactionContext.get().clearExceptSearchIndexUpdates();
     }
 
-    @VisibleForTesting
-    void setMetadataSyncForTest(boolean enabled) {
-        mMetadataSyncEnabled = enabled;
-    }
-
     /**
      * Appends comma separated IDs.
      * @param ids Should not be empty
@@ -2473,14 +2620,44 @@
 
     @Override
     protected void notifyChange() {
-        notifyChange(mSyncToNetwork, mSyncToMetadataNetWork);
+        notifyChange(mSyncToNetwork);
         mSyncToNetwork = false;
-        mSyncToMetadataNetWork = false;
     }
 
-    protected void notifyChange(boolean syncToNetwork, boolean syncToMetadataNetwork) {
+    private final Handler mHandler = new Handler(Looper.getMainLooper());
+    private final Runnable mChangeNotifier = () -> {
+        Log.v(TAG, "Scheduled notifyChange started.");
+        mLastNotifyChange = System.currentTimeMillis();
         getContext().getContentResolver().notifyChange(ContactsContract.AUTHORITY_URI, null,
+                false);
+    };
+
+    protected void notifyChange(boolean syncToNetwork) {
+        if (syncToNetwork) {
+            // Changes to sync to network won't be rate limited.
+            getContext().getContentResolver().notifyChange(ContactsContract.AUTHORITY_URI, null,
                 syncToNetwork);
+        } else {
+            // Rate limit the changes which are not to sync to network.
+            long currentTimeMillis = System.currentTimeMillis();
+
+            mHandler.removeCallbacks(mChangeNotifier);
+            if (currentTimeMillis > mLastNotifyChange + NOTIFY_CHANGE_RATE_LIMIT) {
+                // Notify change immediately, since it has been a while since last notify.
+                mLastNotifyChange = currentTimeMillis;
+                getContext().getContentResolver().notifyChange(ContactsContract.AUTHORITY_URI, null,
+                   false);
+            } else {
+                // Schedule a delayed notification, to ensure the very last notifyChange will be
+                // executed.
+                // Delay is set to two-fold of rate limit, and the subsequent notifyChange called
+                // (if ever) between the (NOTIFY_CHANGE_RATE_LIMIT, 2 * NOTIFY_CHANGE_RATE_LIMIT)
+                // time window, will cancel this delayed notification.
+                // The delayed notification is only expected to run if notifyChange is not invoked
+                // between the above time window.
+                mHandler.postDelayed(mChangeNotifier, NOTIFY_CHANGE_RATE_LIMIT * 2);
+            }
+         }
     }
 
     protected void setProviderStatus(int status) {
@@ -3038,8 +3215,8 @@
         Uri dataUri = inProfileMode()
                 ? Uri.withAppendedPath(Profile.CONTENT_URI, RawContacts.Data.CONTENT_DIRECTORY)
                 : Data.CONTENT_URI;
-        Cursor c = query(dataUri, DataRowHandler.DataDeleteQuery.COLUMNS,
-                selection, selectionArgs, null);
+        Cursor c = queryInternal(dataUri, DataRowHandler.DataDeleteQuery.COLUMNS,
+                selection, selectionArgs, null, null);
         try {
             while(c.moveToNext()) {
                 long rawContactId = c.getLong(DataRowHandler.DataDeleteQuery.RAW_CONTACT_ID);
@@ -3066,8 +3243,8 @@
         // Note that the query will return data according to the access restrictions,
         // so we don't need to worry about deleting data we don't have permission to read.
         mSelectionArgs1[0] = String.valueOf(dataId);
-        Cursor c = query(Data.CONTENT_URI, DataRowHandler.DataDeleteQuery.COLUMNS, Data._ID + "=?",
-                mSelectionArgs1, null);
+        Cursor c = queryInternal(Data.CONTENT_URI, DataRowHandler.DataDeleteQuery.COLUMNS,
+                Data._ID + "=?", mSelectionArgs1, null, null);
 
         try {
             if (!c.moveToFirst()) {
@@ -3894,8 +4071,7 @@
         values.put(RawContactsColumns.AGGREGATION_NEEDED, 1);
         values.putNull(RawContacts.CONTACT_ID);
         values.put(RawContacts.DIRTY, 1);
-        return updateRawContact(db, rawContactId, values, callerIsSyncAdapter,
-                /* callerIsMetadataSyncAdapter =*/false);
+        return updateRawContact(db, rawContactId, values, callerIsSyncAdapter);
     }
 
     static int deleteDataUsage(SQLiteDatabase db) {
@@ -3997,8 +4173,7 @@
                 String selectionWithId = (Data.RAW_CONTACT_ID + "=" + rawContactId + " ")
                     + (selection == null ? "" : " AND " + selection);
 
-                count = updateData(uri, values, selectionWithId, selectionArgs, callerIsSyncAdapter,
-                        /* callerIsMetadataSyncAdapter =*/false);
+                count = updateData(uri, values, selectionWithId, selectionArgs, callerIsSyncAdapter);
                 break;
             }
 
@@ -4006,8 +4181,7 @@
             case PROFILE_DATA: {
                 invalidateFastScrollingIndexCache();
                 count = updateData(uri, values, appendAccountToSelection(uri, selection),
-                        selectionArgs, callerIsSyncAdapter,
-                        /* callerIsMetadataSyncAdapter =*/false);
+                        selectionArgs, callerIsSyncAdapter);
                 if (count > 0) {
                     mSyncToNetwork |= !callerIsSyncAdapter;
                 }
@@ -4020,8 +4194,7 @@
             case CALLABLES_ID:
             case POSTALS_ID: {
                 invalidateFastScrollingIndexCache();
-                count = updateData(uri, values, selection, selectionArgs, callerIsSyncAdapter,
-                        /* callerIsMetadataSyncAdapter =*/false);
+                count = updateData(uri, values, selection, selectionArgs, callerIsSyncAdapter);
                 if (count > 0) {
                     mSyncToNetwork |= !callerIsSyncAdapter;
                 }
@@ -4074,8 +4247,7 @@
             }
 
             case AGGREGATION_EXCEPTIONS: {
-                count = updateAggregationException(db, values,
-                        /* callerIsMetadataSyncAdapter =*/false);
+                count = updateAggregationException(db, values);
                 invalidateFastScrollingIndexCache();
                 break;
             }
@@ -4401,8 +4573,7 @@
         try {
             while (cursor.moveToNext()) {
                 long rawContactId = cursor.getLong(0);
-                updateRawContact(db, rawContactId, values, callerIsSyncAdapter,
-                        /* callerIsMetadataSyncAdapter =*/false);
+                updateRawContact(db, rawContactId, values, callerIsSyncAdapter);
                 count++;
             }
         } finally {
@@ -4435,7 +4606,7 @@
     }
 
     private int updateRawContact(SQLiteDatabase db, long rawContactId, ContentValues values,
-            boolean callerIsSyncAdapter, boolean callerIsMetadataSyncAdapter) {
+            boolean callerIsSyncAdapter) {
         final String selection = RawContactsColumns.CONCRETE_ID + " = ?";
         mSelectionArgs1[0] = Long.toString(rawContactId);
 
@@ -4566,8 +4737,7 @@
     }
 
     private int updateData(Uri uri, ContentValues inputValues, String selection,
-            String[] selectionArgs, boolean callerIsSyncAdapter,
-            boolean callerIsMetadataSyncAdapter) {
+            String[] selectionArgs, boolean callerIsSyncAdapter) {
 
         final ContentValues values = new ContentValues(inputValues);
         values.remove(Data._ID);
@@ -4593,7 +4763,7 @@
                 selection, selectionArgs, null, -1 /* directory ID */, null);
         try {
             while(c.moveToNext()) {
-                count += updateData(values, c, callerIsSyncAdapter, callerIsMetadataSyncAdapter);
+                count += updateData(values, c, callerIsSyncAdapter);
             }
         } finally {
             c.close();
@@ -4609,8 +4779,7 @@
         }
     }
 
-    private int updateData(ContentValues values, Cursor c, boolean callerIsSyncAdapter,
-            boolean callerIsMetadataSyncAdapter) {
+    private int updateData(ContentValues values, Cursor c, boolean callerIsSyncAdapter) {
         if (values.size() == 0) {
             return 0;
         }
@@ -4625,7 +4794,7 @@
         DataRowHandler rowHandler = getDataRowHandler(mimeType);
         boolean updated =
                 rowHandler.update(db, mTransactionContext.get(), values, c,
-                        callerIsSyncAdapter, callerIsMetadataSyncAdapter);
+                        callerIsSyncAdapter);
         if (Photo.CONTENT_ITEM_TYPE.equals(mimeType)) {
             scheduleBackgroundTask(BACKGROUND_TASK_CLEANUP_PHOTOS);
         }
@@ -4743,8 +4912,7 @@
         return rslt;
     }
 
-    private int updateAggregationException(SQLiteDatabase db, ContentValues values,
-            boolean callerIsMetadataSyncAdapter) {
+    private int updateAggregationException(SQLiteDatabase db, ContentValues values) {
         Integer exceptionType = values.getAsInteger(AggregationExceptions.TYPE);
         Long rcId1 = values.getAsLong(AggregationExceptions.RAW_CONTACT_ID1);
         Long rcId2 = values.getAsLong(AggregationExceptions.RAW_CONTACT_ID2);
@@ -4867,27 +5035,6 @@
         return result;
     }
 
-    private long searchRawContactIdForRawContactInfo(SQLiteDatabase db,
-            RawContactInfo rawContactInfo) {
-        if (rawContactInfo == null) {
-            return 0;
-        }
-        final String backupId = rawContactInfo.mBackupId;
-        final String accountType = rawContactInfo.mAccountType;
-        final String accountName = rawContactInfo.mAccountName;
-        final String dataSet = rawContactInfo.mDataSet;
-        ContentValues values = new ContentValues();
-        values.put(AccountsColumns.ACCOUNT_TYPE, accountType);
-        values.put(AccountsColumns.ACCOUNT_NAME, accountName);
-        if (dataSet != null) {
-            values.put(AccountsColumns.DATA_SET, dataSet);
-        }
-
-        final long accountId = replaceAccountInfoByAccountId(RawContacts.CONTENT_URI, values);
-        final long rawContactId = queryRawContactId(db, backupId, accountId);
-        return rawContactId;
-    }
-
     interface AggregationExceptionQuery {
         String TABLE = Tables.AGGREGATION_EXCEPTIONS;
         String[] COLUMNS = new String[] {
@@ -4924,80 +5071,6 @@
         return aggregationRawContactIds;
     }
 
-    /**
-     * Update RawContact, Data, DataUsageStats, AggregationException tables from MetadataEntry.
-     */
-    @NeededForTesting
-    void updateFromMetaDataEntry(SQLiteDatabase db, MetadataEntry metadataEntry) {
-        final RawContactInfo rawContactInfo =  metadataEntry.mRawContactInfo;
-        final long rawContactId = searchRawContactIdForRawContactInfo(db, rawContactInfo);
-        if (rawContactId == 0) {
-            return;
-        }
-
-        ContentValues rawContactValues = new ContentValues();
-        rawContactValues.put(RawContacts.SEND_TO_VOICEMAIL, metadataEntry.mSendToVoicemail);
-        rawContactValues.put(RawContacts.STARRED, metadataEntry.mStarred);
-        rawContactValues.put(RawContacts.PINNED, metadataEntry.mPinned);
-        updateRawContact(db, rawContactId, rawContactValues, /* callerIsSyncAdapter =*/true,
-                /* callerIsMetadataSyncAdapter =*/true);
-
-        // Update Data and DataUsageStats table.
-        for (int i = 0; i < metadataEntry.mFieldDatas.size(); i++) {
-            final FieldData fieldData = metadataEntry.mFieldDatas.get(i);
-            final String dataHashId = fieldData.mDataHashId;
-            final ArrayList<Long> dataIds = queryDataId(db, rawContactId, dataHashId);
-
-            for (long dataId : dataIds) {
-                // Update is_primary and is_super_primary.
-                ContentValues dataValues = new ContentValues();
-                dataValues.put(Data.IS_PRIMARY, fieldData.mIsPrimary ? 1 : 0);
-                dataValues.put(Data.IS_SUPER_PRIMARY, fieldData.mIsSuperPrimary ? 1 : 0);
-                updateData(ContentUris.withAppendedId(Data.CONTENT_URI, dataId),
-                        dataValues, null, null, /* callerIsSyncAdapter =*/true,
-                        /* callerIsMetadataSyncAdapter =*/true);
-
-            }
-        }
-
-        // Update AggregationException table.
-        final Set<Long> aggregationRawContactIdsInServer = new ArraySet<>();
-        for (int i = 0; i < metadataEntry.mAggregationDatas.size(); i++) {
-            final AggregationData aggregationData = metadataEntry.mAggregationDatas.get(i);
-            final int typeInt = getAggregationType(aggregationData.mType, null);
-            final RawContactInfo aggregationContact1 = aggregationData.mRawContactInfo1;
-            final RawContactInfo aggregationContact2 = aggregationData.mRawContactInfo2;
-            final long rawContactId1 = searchRawContactIdForRawContactInfo(db, aggregationContact1);
-            final long rawContactId2 = searchRawContactIdForRawContactInfo(db, aggregationContact2);
-            if (rawContactId1 == 0 || rawContactId2 == 0) {
-                continue;
-            }
-            ContentValues values = new ContentValues();
-            values.put(AggregationExceptions.RAW_CONTACT_ID1, rawContactId1);
-            values.put(AggregationExceptions.RAW_CONTACT_ID2, rawContactId2);
-            values.put(AggregationExceptions.TYPE, typeInt);
-            updateAggregationException(db, values, /* callerIsMetadataSyncAdapter =*/true);
-            if (rawContactId1 != rawContactId) {
-                aggregationRawContactIdsInServer.add(rawContactId1);
-            }
-            if (rawContactId2 != rawContactId) {
-                aggregationRawContactIdsInServer.add(rawContactId2);
-            }
-        }
-
-        // Delete AggregationExceptions from CP2 if it doesn't exist in server side.
-        Set<Long> aggregationRawContactIdsInLocal = queryAggregationRawContactIds(db, rawContactId);
-        Set<Long> rawContactIdsToBeDeleted = com.google.common.collect.Sets.difference(
-                aggregationRawContactIdsInLocal, aggregationRawContactIdsInServer);
-        for (Long deleteRawContactId : rawContactIdsToBeDeleted) {
-            ContentValues values = new ContentValues();
-            values.put(AggregationExceptions.RAW_CONTACT_ID1, rawContactId);
-            values.put(AggregationExceptions.RAW_CONTACT_ID2, deleteRawContactId);
-            values.put(AggregationExceptions.TYPE, AggregationExceptions.TYPE_AUTOMATIC);
-            updateAggregationException(db, values, /* callerIsMetadataSyncAdapter =*/true);
-        }
-    }
-
     /** return serialized version of {@code accounts} */
     @VisibleForTesting
     static String accountsToString(Set<Account> accounts) {
@@ -5092,12 +5165,14 @@
             // All accounts that are used in raw_contacts and/or groups.
             final Set<AccountWithDataSet> knownAccountsWithDataSets
                     = dbHelper.getAllAccountsWithDataSets();
-
+            // All known SIM accounts
+            final List<SimAccount> simAccounts = getDatabaseHelper().getAllSimAccounts();
             // Find the accounts that have been removed.
             final List<AccountWithDataSet> accountsWithDataSetsToDelete = Lists.newArrayList();
             for (AccountWithDataSet knownAccountWithDataSet : knownAccountsWithDataSets) {
                 if (knownAccountWithDataSet.isLocalAccount()
-                        || knownAccountWithDataSet.inSystemAccounts(systemAccounts)) {
+                        || knownAccountWithDataSet.inSystemAccounts(systemAccounts)
+                        || knownAccountWithDataSet.inSimAccounts(simAccounts)) {
                     continue;
                 }
                 accountsWithDataSetsToDelete.add(knownAccountWithDataSet);
@@ -5107,7 +5182,6 @@
                 for (AccountWithDataSet accountWithDataSet : accountsWithDataSetsToDelete) {
                     final Long accountIdOrNull = dbHelper.getAccountIdOrNull(accountWithDataSet);
 
-                    // getAccountIdOrNull() really shouldn't return null here, but just in case...
                     if (accountIdOrNull != null) {
                         final String accountId = Long.toString(accountIdOrNull);
                         final String[] accountIdParams =
@@ -5140,14 +5214,6 @@
                                         " FROM " + Tables.RAW_CONTACTS +
                                         " WHERE " + RawContactsColumns.ACCOUNT_ID + " = ?)",
                                         accountIdParams);
-                        db.execSQL(
-                                "DELETE FROM " + Tables.METADATA_SYNC +
-                                        " WHERE " + MetadataSyncColumns.ACCOUNT_ID + " = ?",
-                                accountIdParams);
-                        db.execSQL(
-                                "DELETE FROM " + Tables.METADATA_SYNC_STATE +
-                                        " WHERE " + MetadataSyncStateColumns.ACCOUNT_ID + " = ?",
-                                accountIdParams);
 
                         // Delta API is only needed for regular contacts.
                         if (!inProfileMode()) {
@@ -5348,6 +5414,29 @@
     @Override
     public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
             String sortOrder, CancellationSignal cancellationSignal) {
+        LogFields.Builder logBuilder = LogFields.Builder.aLogFields()
+                .setApiType(LogUtils.ApiType.QUERY)
+                .setUriType(sUriMatcher.match(uri))
+                .setCallerIsSyncAdapter(readBooleanQueryParameter(
+                        uri, ContactsContract.CALLER_IS_SYNCADAPTER, false))
+                .setStartNanos(SystemClock.elapsedRealtimeNanos());
+
+        Cursor cursor = null;
+        try {
+            cursor = queryInternal(uri, projection, selection, selectionArgs, sortOrder,
+                    cancellationSignal);
+            return cursor;
+        } catch (Exception e) {
+            logBuilder.setException(e);
+            throw e;
+        } finally {
+            LogUtils.log(
+                    logBuilder.setResultCount(cursor == null ? 0 : cursor.getCount()).build());
+        }
+    }
+
+    private Cursor queryInternal(Uri uri, String[] projection, String selection,
+            String[] selectionArgs, String sortOrder, CancellationSignal cancellationSignal) {
         if (VERBOSE_LOGGING) {
             Log.v(TAG, "query: uri=" + uri + "  projection=" + Arrays.toString(projection) +
                     "  selection=[" + selection + "]  args=" + Arrays.toString(selectionArgs) +
@@ -5548,6 +5637,17 @@
             return null;
         }
 
+        if (cursor.getCount() > 0) {
+            final int callingUid = Binder.getCallingUid();
+            final String directoryAuthority = directoryInfo.authority;
+            if (VERBOSE_LOGGING) {
+                Log.v(TAG, "Making authority " + directoryAuthority
+                        + " visible to UID " + callingUid);
+            }
+            getContext().getPackageManager().grantImplicitAccess(
+                    callingUid, directoryAuthority);
+        }
+
         // Load the cursor contents into a memory cursor (backed by a cursor window) and close the
         // underlying cursor.
         try {
@@ -6696,11 +6796,17 @@
                     boolean foundResult = false;
                     Cursor cursor = doQuery(db, qb, projectionWithNumber, selection, selectionArgs,
                             sortOrder, groupBy, null, limit, cancellationSignal);
+
                     try {
                         if (cursor.getCount() > 0) {
                             foundResult = true;
-                            return PhoneLookupWithStarPrefix
+                            cursor = PhoneLookupWithStarPrefix
                                     .removeNonStarMatchesFromCursor(number, cursor);
+                            if (!mDbHelper.get().getUseStrictPhoneNumberComparisonForTest()) {
+                                cursor = PhoneLookupWithStarPrefix.removeNoMatchPhoneNumber(number,
+                                        cursor, mDbHelper.get().getCurrentCountryIso());
+                            }
+                            return cursor;
                         }
 
                         // Use the fall-back lookup method.
@@ -6713,11 +6819,13 @@
                         // numbers
                         mDbHelper.get().buildFallbackPhoneLookupAndContactQuery(qb, number);
 
-                        final Cursor fallbackCursor = doQuery(db, qb, projectionWithNumber,
+                        Cursor fallbackCursor = doQuery(db, qb, projectionWithNumber,
                                 selection, selectionArgs, sortOrder, groupBy, having, limit,
                                 cancellationSignal);
-                        return PhoneLookupWithStarPrefix.removeNonStarMatchesFromCursor(
+                        fallbackCursor = PhoneLookupWithStarPrefix.removeNonStarMatchesFromCursor(
                                 number, fallbackCursor);
+                        return PhoneLookupWithStarPrefix.removeNoMatchPhoneNumber(number,
+                                fallbackCursor, mDbHelper.get().getCurrentCountryIso());
                     } finally {
                         if (!foundResult) {
                             // We'll be returning a different cursor, so close this one.
@@ -8628,7 +8736,7 @@
 
             case RAW_CONTACTS_ID_DISPLAY_PHOTO: {
                 long rawContactId = Long.parseLong(uri.getPathSegments().get(1));
-                boolean writeable = !mode.equals("r");
+                boolean writeable = mode.contains("w");
 
                 // Find the primary photo data record for this raw contact.
                 SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
diff --git a/src/com/android/providers/contacts/DataRowHandler.java b/src/com/android/providers/contacts/DataRowHandler.java
index 235edfe..a82ce34 100644
--- a/src/com/android/providers/contacts/DataRowHandler.java
+++ b/src/com/android/providers/contacts/DataRowHandler.java
@@ -121,7 +121,6 @@
         if ((primary != null && primary != 0) || (superPrimary != null && superPrimary != 0)) {
             final long mimeTypeId = getMimeTypeId();
             mDbHelper.setIsPrimary(rawContactId, dataId, mimeTypeId);
-            txContext.markRawContactMetadataDirty(rawContactId, /* isMetadataSyncAdapter =*/false);
 
             // We also have to make sure that no other data item on this raw_contact is
             // configured super primary
@@ -154,13 +153,11 @@
      * @return true if update changed something
      */
     public boolean update(SQLiteDatabase db, TransactionContext txContext,
-            ContentValues values, Cursor c, boolean callerIsSyncAdapter,
-            boolean callerIsMetadataSyncAdapter) {
+            ContentValues values, Cursor c, boolean callerIsSyncAdapter) {
         long dataId = c.getLong(DataUpdateQuery._ID);
         long rawContactId = c.getLong(DataUpdateQuery.RAW_CONTACT_ID);
 
-        handlePrimaryAndSuperPrimary(txContext, values, dataId, rawContactId,
-                callerIsMetadataSyncAdapter);
+        handlePrimaryAndSuperPrimary(txContext, values, dataId, rawContactId);
         handleHashIdForUpdate(values, dataId);
 
         if (values.size() > 0) {
@@ -251,15 +248,13 @@
      * configured correctly
      */
     private void handlePrimaryAndSuperPrimary(TransactionContext txContext, ContentValues values,
-            long dataId, long rawContactId, boolean callerIsMetadataSyncAdapter) {
+            long dataId, long rawContactId) {
         final boolean hasPrimary = values.getAsInteger(Data.IS_PRIMARY) != null;
         final boolean hasSuperPrimary = values.getAsInteger(Data.IS_SUPER_PRIMARY) != null;
 
         // Nothing to do? Bail out early
         if (!hasPrimary && !hasSuperPrimary) return;
 
-        txContext.markRawContactMetadataDirty(rawContactId, callerIsMetadataSyncAdapter);
-
         final long mimeTypeId = getMimeTypeId();
 
         // Check if we want to clear values
@@ -325,7 +320,6 @@
         db.delete(Tables.PRESENCE, PresenceColumns.RAW_CONTACT_ID + "=?", mSelectionArgs1);
         if (count != 0 && primary) {
             fixPrimary(db, rawContactId);
-            txContext.markRawContactMetadataDirty(rawContactId, /* isMetadataSyncAdapter =*/false);
         }
 
         if (hasSearchableData()) {
diff --git a/src/com/android/providers/contacts/DataRowHandlerForCommonDataKind.java b/src/com/android/providers/contacts/DataRowHandlerForCommonDataKind.java
index 5ae3a01..063fcdb 100644
--- a/src/com/android/providers/contacts/DataRowHandlerForCommonDataKind.java
+++ b/src/com/android/providers/contacts/DataRowHandlerForCommonDataKind.java
@@ -49,15 +49,14 @@
 
     @Override
     public boolean update(SQLiteDatabase db, TransactionContext txContext, ContentValues values,
-            Cursor c, boolean callerIsSyncAdapter, boolean callerIsMetadataSyncAdapter) {
+            Cursor c, boolean callerIsSyncAdapter) {
         final long dataId = c.getLong(DataUpdateQuery._ID);
         final ContentValues augmented = getAugmentedValues(db, dataId, values);
         if (augmented == null) {        // No change
             return false;
         }
         enforceTypeAndLabel(augmented);
-        return super.update(db, txContext, values, c, callerIsSyncAdapter,
-                callerIsMetadataSyncAdapter);
+        return super.update(db, txContext, values, c, callerIsSyncAdapter);
     }
 
     /**
diff --git a/src/com/android/providers/contacts/DataRowHandlerForEmail.java b/src/com/android/providers/contacts/DataRowHandlerForEmail.java
index 3c7311f..539c959 100644
--- a/src/com/android/providers/contacts/DataRowHandlerForEmail.java
+++ b/src/com/android/providers/contacts/DataRowHandlerForEmail.java
@@ -50,8 +50,8 @@
 
     @Override
     public boolean update(SQLiteDatabase db, TransactionContext txContext, ContentValues values,
-            Cursor c, boolean callerIsSyncAdapter, boolean callerIsMetadataSyncAdapter) {
-        if (!super.update(db, txContext, values, c, callerIsSyncAdapter, callerIsMetadataSyncAdapter)) {
+            Cursor c, boolean callerIsSyncAdapter) {
+        if (!super.update(db, txContext, values, c, callerIsSyncAdapter)) {
             return false;
         }
 
diff --git a/src/com/android/providers/contacts/DataRowHandlerForGroupMembership.java b/src/com/android/providers/contacts/DataRowHandlerForGroupMembership.java
index b1c4049..3f310b1 100644
--- a/src/com/android/providers/contacts/DataRowHandlerForGroupMembership.java
+++ b/src/com/android/providers/contacts/DataRowHandlerForGroupMembership.java
@@ -86,11 +86,11 @@
 
     @Override
     public boolean update(SQLiteDatabase db, TransactionContext txContext, ContentValues values,
-            Cursor c, boolean callerIsSyncAdapter, boolean callerIsMetadataSyncAdapter) {
+            Cursor c, boolean callerIsSyncAdapter) {
         long rawContactId = c.getLong(DataUpdateQuery.RAW_CONTACT_ID);
         boolean wasStarred = hasFavoritesGroupMembership(db, rawContactId);
         resolveGroupSourceIdInValues(txContext, rawContactId, db, values, false);
-        if (!super.update(db, txContext, values, c, callerIsSyncAdapter, callerIsMetadataSyncAdapter)) {
+        if (!super.update(db, txContext, values, c, callerIsSyncAdapter)) {
             return false;
         }
         boolean isStarred = hasFavoritesGroupMembership(db, rawContactId);
diff --git a/src/com/android/providers/contacts/DataRowHandlerForIdentity.java b/src/com/android/providers/contacts/DataRowHandlerForIdentity.java
index 4d4a33f..32e9757 100644
--- a/src/com/android/providers/contacts/DataRowHandlerForIdentity.java
+++ b/src/com/android/providers/contacts/DataRowHandlerForIdentity.java
@@ -46,9 +46,9 @@
 
     @Override
     public boolean update(SQLiteDatabase db, TransactionContext txContext, ContentValues values,
-            Cursor c, boolean callerIsSyncAdapter, boolean callerIsMetadataSyncAdapter) {
+            Cursor c, boolean callerIsSyncAdapter) {
 
-        super.update(db, txContext, values, c, callerIsSyncAdapter, callerIsMetadataSyncAdapter);
+        super.update(db, txContext, values, c, callerIsSyncAdapter);
 
         // Identity affects aggregation.
         final long rawContactId = c.getLong(DataUpdateQuery.RAW_CONTACT_ID);
diff --git a/src/com/android/providers/contacts/DataRowHandlerForNickname.java b/src/com/android/providers/contacts/DataRowHandlerForNickname.java
index cc85c2b..03b96a3 100644
--- a/src/com/android/providers/contacts/DataRowHandlerForNickname.java
+++ b/src/com/android/providers/contacts/DataRowHandlerForNickname.java
@@ -52,11 +52,11 @@
 
     @Override
     public boolean update(SQLiteDatabase db, TransactionContext txContext, ContentValues values,
-            Cursor c, boolean callerIsSyncAdapter, boolean callerIsMetadataSyncAdapter) {
+            Cursor c, boolean callerIsSyncAdapter) {
         long dataId = c.getLong(DataUpdateQuery._ID);
         long rawContactId = c.getLong(DataUpdateQuery.RAW_CONTACT_ID);
 
-        if (!super.update(db, txContext, values, c, callerIsSyncAdapter, callerIsMetadataSyncAdapter)) {
+        if (!super.update(db, txContext, values, c, callerIsSyncAdapter)) {
             return false;
         }
 
diff --git a/src/com/android/providers/contacts/DataRowHandlerForOrganization.java b/src/com/android/providers/contacts/DataRowHandlerForOrganization.java
index 5b69fe3..66a3b1b 100644
--- a/src/com/android/providers/contacts/DataRowHandlerForOrganization.java
+++ b/src/com/android/providers/contacts/DataRowHandlerForOrganization.java
@@ -51,8 +51,8 @@
 
     @Override
     public boolean update(SQLiteDatabase db, TransactionContext txContext, ContentValues values,
-            Cursor c, boolean callerIsSyncAdapter, boolean callerIsMetadataSyncAdapter) {
-        if (!super.update(db, txContext, values, c, callerIsSyncAdapter, callerIsMetadataSyncAdapter)) {
+            Cursor c, boolean callerIsSyncAdapter) {
+        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 7bbac68..052252e 100644
--- a/src/com/android/providers/contacts/DataRowHandlerForPhoneNumber.java
+++ b/src/com/android/providers/contacts/DataRowHandlerForPhoneNumber.java
@@ -58,10 +58,10 @@
 
     @Override
     public boolean update(SQLiteDatabase db, TransactionContext txContext, ContentValues values,
-            Cursor c, boolean callerIsSyncAdapter, boolean callerIsMetadataSyncAdapter) {
+            Cursor c, boolean callerIsSyncAdapter) {
         fillNormalizedNumber(values);
 
-        if (!super.update(db, txContext, values, c, callerIsSyncAdapter, callerIsMetadataSyncAdapter)) {
+        if (!super.update(db, txContext, values, c, callerIsSyncAdapter)) {
             return false;
         }
 
diff --git a/src/com/android/providers/contacts/DataRowHandlerForPhoto.java b/src/com/android/providers/contacts/DataRowHandlerForPhoto.java
index 3d28b05..532a852 100644
--- a/src/com/android/providers/contacts/DataRowHandlerForPhoto.java
+++ b/src/com/android/providers/contacts/DataRowHandlerForPhoto.java
@@ -76,7 +76,7 @@
 
     @Override
     public boolean update(SQLiteDatabase db, TransactionContext txContext, ContentValues values,
-            Cursor c, boolean callerIsSyncAdapter, boolean callerIsMetadataSyncAdapter) {
+            Cursor c, boolean callerIsSyncAdapter) {
         long rawContactId = c.getLong(DataUpdateQuery.RAW_CONTACT_ID);
 
         if (values.containsKey(SKIP_PROCESSING_KEY)) {
@@ -89,7 +89,7 @@
         }
 
         // Do the actual update.
-        if (!super.update(db, txContext, values, c, callerIsSyncAdapter, callerIsMetadataSyncAdapter)) {
+        if (!super.update(db, txContext, values, c, callerIsSyncAdapter)) {
             return false;
         }
 
diff --git a/src/com/android/providers/contacts/DataRowHandlerForStructuredName.java b/src/com/android/providers/contacts/DataRowHandlerForStructuredName.java
index b80f759..044e972 100644
--- a/src/com/android/providers/contacts/DataRowHandlerForStructuredName.java
+++ b/src/com/android/providers/contacts/DataRowHandlerForStructuredName.java
@@ -63,7 +63,7 @@
 
     @Override
     public boolean update(SQLiteDatabase db, TransactionContext txContext, ContentValues values,
-            Cursor c, boolean callerIsSyncAdapter, boolean callerIsMetadataSyncAdapter) {
+            Cursor c, boolean callerIsSyncAdapter) {
         final long dataId = c.getLong(DataUpdateQuery._ID);
         final long rawContactId = c.getLong(DataUpdateQuery.RAW_CONTACT_ID);
 
@@ -74,7 +74,7 @@
 
         fixStructuredNameComponents(augmented, values);
 
-        super.update(db, txContext, values, c, callerIsSyncAdapter, callerIsMetadataSyncAdapter);
+        super.update(db, txContext, values, c, callerIsSyncAdapter);
         if (values.containsKey(StructuredName.DISPLAY_NAME)) {
             augmented.putAll(values);
             String name = augmented.getAsString(StructuredName.DISPLAY_NAME);
diff --git a/src/com/android/providers/contacts/DataRowHandlerForStructuredPostal.java b/src/com/android/providers/contacts/DataRowHandlerForStructuredPostal.java
index 235bbd7..7fc97b7 100644
--- a/src/com/android/providers/contacts/DataRowHandlerForStructuredPostal.java
+++ b/src/com/android/providers/contacts/DataRowHandlerForStructuredPostal.java
@@ -60,7 +60,7 @@
 
     @Override
     public boolean update(SQLiteDatabase db, TransactionContext txContext, ContentValues values,
-            Cursor c, boolean callerIsSyncAdapter, boolean callerIsMetadataSyncAdapter) {
+            Cursor c, boolean callerIsSyncAdapter) {
         final long dataId = c.getLong(DataUpdateQuery._ID);
         final ContentValues augmented = getAugmentedValues(db, dataId, values);
         if (augmented == null) {    // No change
@@ -68,7 +68,7 @@
         }
 
         fixStructuredPostalComponents(augmented, values);
-        super.update(db, txContext, values, c, callerIsSyncAdapter, callerIsMetadataSyncAdapter);
+        super.update(db, txContext, values, c, callerIsSyncAdapter);
         return true;
     }
 
diff --git a/src/com/android/providers/contacts/DbModifierWithNotification.java b/src/com/android/providers/contacts/DbModifierWithNotification.java
index 852301d..dc74c0a 100644
--- a/src/com/android/providers/contacts/DbModifierWithNotification.java
+++ b/src/com/android/providers/contacts/DbModifierWithNotification.java
@@ -27,6 +27,7 @@
 import android.database.Cursor;
 import android.database.DatabaseUtils.InsertHelper;
 import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteQueryBuilder;
 import android.net.Uri;
 import android.os.Binder;
 import android.provider.CallLog.Calls;
@@ -65,6 +66,7 @@
             Voicemails.DELETED + " == 0";
     private final String mTableName;
     private final SQLiteDatabase mDb;
+    private final boolean mHasReadVoicemailPermission;
     private final InsertHelper mInsertHelper;
     private final Context mContext;
     private final Uri mBaseUri;
@@ -86,8 +88,14 @@
 
     private DbModifierWithNotification(String tableName, SQLiteDatabase db,
             InsertHelper insertHelper, Context context) {
+        this(tableName, db, insertHelper, true /* hasReadVoicemail */, context);
+    }
+
+    public DbModifierWithNotification(String tableName, SQLiteDatabase db,
+            InsertHelper insertHelper, boolean hasReadVoicemailPermission, Context context) {
         mTableName = tableName;
         mDb = db;
+        mHasReadVoicemailPermission = hasReadVoicemailPermission;
         mInsertHelper = insertHelper;
         mContext = context;
         mBaseUri = mTableName.equals(Tables.VOICEMAIL_STATUS) ?
@@ -109,7 +117,7 @@
                     packagesModified);
         }
         if (rowId > 0 && mIsCallsTable) {
-            notifyCallLogChange();
+            notifyCallLogChange(mContext);
         }
         return rowId;
     }
@@ -126,20 +134,20 @@
                     ContentUris.withAppendedId(mBaseUri, rowId), packagesModified);
         }
         if (rowId > 0 && mIsCallsTable) {
-            notifyCallLogChange();
+            notifyCallLogChange(mContext);
         }
         return rowId;
     }
 
-    private void notifyCallLogChange() {
-        mContext.getContentResolver().notifyChange(Calls.CONTENT_URI, null, false);
+    public static void notifyCallLogChange(Context context) {
+        context.getContentResolver().notifyChange(Calls.CONTENT_URI, null, false);
 
         Intent intent = new Intent("com.android.internal.action.CALL_LOG_CHANGE");
         intent.setComponent(new ComponentName("com.android.calllogbackup",
                 "com.android.calllogbackup.CallLogChangeReceiver"));
 
-        if (!mContext.getPackageManager().queryBroadcastReceivers(intent, 0).isEmpty()) {
-            mContext.sendBroadcast(intent);
+        if (!context.getPackageManager().queryBroadcastReceivers(intent, 0).isEmpty()) {
+            context.sendBroadcast(intent);
         }
     }
 
@@ -196,12 +204,21 @@
         if (values.isEmpty()) {
             return 0;
         }
-        int count = mDb.update(table, values, whereClause, whereArgs);
+
+        final SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
+        qb.setTables(mTableName);
+        qb.setProjectionMap(CallLogProvider.sCallsProjectionMap);
+        qb.setStrict(true);
+        if (!mHasReadVoicemailPermission) {
+            qb.setStrictGrammar(true);
+        }
+        int count = qb.update(mDb, values, whereClause, whereArgs);
+
         if (count > 0 && isVoicemailContent || Tables.VOICEMAIL_STATUS.equals(table)) {
             notifyVoicemailChange(mBaseUri, packagesModified);
         }
         if (count > 0 && mIsCallsTable) {
-            notifyCallLogChange();
+            notifyCallLogChange(mContext);
         }
         if (hasMarkedRead) {
             // A "New" voicemail has been marked as read by the server. This voicemail is no longer
@@ -269,21 +286,30 @@
         // If the deletion is being made by the package that inserted the voicemail or by
         // CP2 (cleanup after uninstall), then we don't need to wait for sync, so just delete it.
         final int count;
+
+        final SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
+        qb.setTables(mTableName);
+        qb.setProjectionMap(CallLogProvider.sCallsProjectionMap);
+        qb.setStrict(true);
+        if (!mHasReadVoicemailPermission) {
+            qb.setStrictGrammar(true);
+        }
+
         if (mIsCallsTable && isVoicemail && !isSelfModifyingOrInternal(packagesModified)) {
             ContentValues values = new ContentValues();
             values.put(VoicemailContract.Voicemails.DIRTY, 1);
             values.put(VoicemailContract.Voicemails.DELETED, 1);
             values.put(VoicemailContract.Voicemails.LAST_MODIFIED, getTimeMillis());
-            count = mDb.update(table, values, whereClause, whereArgs);
+            count = qb.update(mDb, values, whereClause, whereArgs);
         } else {
-            count = mDb.delete(table, whereClause, whereArgs);
+            count = qb.delete(mDb, whereClause, whereArgs);
         }
 
         if (count > 0 && isVoicemail) {
             notifyVoicemailChange(mBaseUri, packagesModified);
         }
         if (count > 0 && mIsCallsTable) {
-            notifyCallLogChange();
+            notifyCallLogChange(mContext);
         }
         return count;
     }
diff --git a/src/com/android/providers/contacts/MetadataEntryParser.java b/src/com/android/providers/contacts/MetadataEntryParser.java
deleted file mode 100644
index 2fe423a..0000000
--- a/src/com/android/providers/contacts/MetadataEntryParser.java
+++ /dev/null
@@ -1,296 +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.text.TextUtils;
-import com.android.providers.contacts.util.NeededForTesting;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import java.util.ArrayList;
-
-@NeededForTesting
-public class MetadataEntryParser {
-
-    private final static String UNIQUE_CONTACT_ID = "unique_contact_id";
-    private final static String ACCOUNT_TYPE = "account_type";
-    private final static String CUSTOM_ACCOUNT_TYPE = "custom_account_type";
-    private final static String ENUM_VALUE_FOR_GOOGLE_ACCOUNT = "GOOGLE_ACCOUNT";
-    private final static String GOOGLE_ACCOUNT_TYPE = "com.google";
-    private final static String ENUM_VALUE_FOR_CUSTOM_ACCOUNT = "CUSTOM_ACCOUNT";
-    private final static String ACCOUNT_NAME = "account_name";
-    private final static String DATA_SET = "data_set";
-    private final static String ENUM_FOR_PLUS_DATA_SET = "GOOGLE_PLUS";
-    private final static String ENUM_FOR_CUSTOM_DATA_SET = "CUSTOM";
-    private final static String PLUS_DATA_SET_TYPE = "plus";
-    private final static String CUSTOM_DATA_SET = "custom_data_set";
-    private final static String CONTACT_ID = "contact_id";
-    private final static String CONTACT_PREFS = "contact_prefs";
-    private final static String SEND_TO_VOICEMAIL = "send_to_voicemail";
-    private final static String STARRED = "starred";
-    private final static String PINNED = "pinned";
-    private final static String AGGREGATION_DATA = "aggregation_data";
-    private final static String CONTACT_IDS = "contact_ids";
-    private final static String TYPE = "type";
-    private final static String FIELD_DATA = "field_data";
-    private final static String FIELD_DATA_ID = "field_data_id";
-    private final static String FIELD_DATA_PREFS = "field_data_prefs";
-    private final static String IS_PRIMARY = "is_primary";
-    private final static String IS_SUPER_PRIMARY = "is_super_primary";
-    private final static String USAGE_STATS = "usage_stats";
-    private final static String USAGE_TYPE = "usage_type";
-    private final static String LAST_TIME_USED = "last_time_used";
-    private final static String USAGE_COUNT = "usage_count";
-
-    @NeededForTesting
-    public static class UsageStats {
-        @NeededForTesting
-        final String mUsageType;
-        @NeededForTesting
-        final long mLastTimeUsed;
-        @NeededForTesting
-        final int mTimesUsed;
-
-        @NeededForTesting
-        public UsageStats(String usageType, long lastTimeUsed, int timesUsed) {
-            this.mUsageType = usageType;
-            this.mLastTimeUsed = lastTimeUsed;
-            this.mTimesUsed = timesUsed;
-        }
-    }
-
-    @NeededForTesting
-    public static class FieldData {
-        @NeededForTesting
-        final String mDataHashId;
-        @NeededForTesting
-        final boolean mIsPrimary;
-        @NeededForTesting
-        final boolean mIsSuperPrimary;
-        @NeededForTesting
-        final ArrayList<UsageStats> mUsageStatsList;
-
-        @NeededForTesting
-        public FieldData(String dataHashId, boolean isPrimary, boolean isSuperPrimary,
-                ArrayList<UsageStats> usageStatsList) {
-            this.mDataHashId = dataHashId;
-            this.mIsPrimary = isPrimary;
-            this.mIsSuperPrimary = isSuperPrimary;
-            this.mUsageStatsList = usageStatsList;
-        }
-    }
-
-    @NeededForTesting
-    public static class RawContactInfo {
-        @NeededForTesting
-        final String mBackupId;
-        @NeededForTesting
-        final String mAccountType;
-        @NeededForTesting
-        final String mAccountName;
-        @NeededForTesting
-        final String mDataSet;
-
-        @NeededForTesting
-        public RawContactInfo(String backupId, String accountType, String accountName,
-                String dataSet) {
-            this.mBackupId = backupId;
-            this.mAccountType = accountType;
-            this.mAccountName = accountName;
-            mDataSet = dataSet;
-        }
-    }
-
-    @NeededForTesting
-    public static class AggregationData {
-        @NeededForTesting
-        final RawContactInfo mRawContactInfo1;
-        @NeededForTesting
-        final RawContactInfo mRawContactInfo2;
-        @NeededForTesting
-        final String mType;
-
-        @NeededForTesting
-        public AggregationData(RawContactInfo rawContactInfo1, RawContactInfo rawContactInfo2,
-                String type) {
-            this.mRawContactInfo1 = rawContactInfo1;
-            this.mRawContactInfo2 = rawContactInfo2;
-            this.mType = type;
-        }
-    }
-
-    @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
-        public MetadataEntry(RawContactInfo rawContactInfo,
-                int sendToVoicemail, int starred, int pinned,
-                ArrayList<FieldData> fieldDatas,
-                ArrayList<AggregationData> aggregationDatas) {
-            this.mRawContactInfo = rawContactInfo;
-            this.mSendToVoicemail = sendToVoicemail;
-            this.mStarred = starred;
-            this.mPinned = pinned;
-            this.mFieldDatas = fieldDatas;
-            this.mAggregationDatas = aggregationDatas;
-        }
-    }
-
-    @NeededForTesting
-    static MetadataEntry parseDataToMetaDataEntry(String inputData) {
-        if (TextUtils.isEmpty(inputData)) {
-            throw new IllegalArgumentException("Input cannot be empty.");
-        }
-
-        try {
-            final JSONObject root = new JSONObject(inputData);
-            // Parse to get rawContactId and account info.
-            final JSONObject uniqueContactJSON = root.getJSONObject(UNIQUE_CONTACT_ID);
-            final RawContactInfo rawContactInfo = parseUniqueContact(uniqueContactJSON);
-
-            // Parse contactPrefs to get sendToVoicemail, starred, pinned.
-            final JSONObject contactPrefs = root.getJSONObject(CONTACT_PREFS);
-            final boolean sendToVoicemail = contactPrefs.has(SEND_TO_VOICEMAIL)
-                    ? contactPrefs.getBoolean(SEND_TO_VOICEMAIL) : false;
-            final boolean starred = contactPrefs.has(STARRED)
-                    ? contactPrefs.getBoolean(STARRED) : false;
-            final int pinned = contactPrefs.has(PINNED) ? contactPrefs.getInt(PINNED) : 0;
-
-            // Parse aggregationDatas
-            final ArrayList<AggregationData> aggregationsList = new ArrayList<AggregationData>();
-            if (root.has(AGGREGATION_DATA)) {
-                final JSONArray aggregationDatas = root.getJSONArray(AGGREGATION_DATA);
-
-                for (int i = 0; i < aggregationDatas.length(); i++) {
-                    final JSONObject aggregationData = aggregationDatas.getJSONObject(i);
-                    final JSONArray contacts = aggregationData.getJSONArray(CONTACT_IDS);
-
-                    if (contacts.length() != 2) {
-                        throw new IllegalArgumentException(
-                                "There should be two contacts for each aggregation.");
-                    }
-                    final JSONObject rawContact1 = contacts.getJSONObject(0);
-                    final RawContactInfo aggregationContact1 = parseUniqueContact(rawContact1);
-                    final JSONObject rawContact2 = contacts.getJSONObject(1);
-                    final RawContactInfo aggregationContact2 = parseUniqueContact(rawContact2);
-                    final String type = aggregationData.getString(TYPE);
-                    if (TextUtils.isEmpty(type)) {
-                        throw new IllegalArgumentException("Aggregation type cannot be empty.");
-                    }
-
-                    final AggregationData aggregation = new AggregationData(
-                            aggregationContact1, aggregationContact2, type);
-                    aggregationsList.add(aggregation);
-                }
-            }
-
-            // Parse fieldDatas
-            final ArrayList<FieldData> fieldDatasList = new ArrayList<FieldData>();
-            if (root.has(FIELD_DATA)) {
-                final JSONArray fieldDatas = root.getJSONArray(FIELD_DATA);
-
-                for (int i = 0; i < fieldDatas.length(); i++) {
-                    final JSONObject fieldData = fieldDatas.getJSONObject(i);
-                    final String dataHashId = fieldData.getString(FIELD_DATA_ID);
-                    if (TextUtils.isEmpty(dataHashId)) {
-                        throw new IllegalArgumentException("Field data hash id cannot be empty.");
-                    }
-                    final JSONObject fieldDataPrefs = fieldData.getJSONObject(FIELD_DATA_PREFS);
-                    final boolean isPrimary = fieldDataPrefs.getBoolean(IS_PRIMARY);
-                    final boolean isSuperPrimary = fieldDataPrefs.getBoolean(IS_SUPER_PRIMARY);
-
-                    final ArrayList<UsageStats> usageStatsList = new ArrayList<UsageStats>();
-                    if (fieldData.has(USAGE_STATS)) {
-                        final JSONArray usageStats = fieldData.getJSONArray(USAGE_STATS);
-                        for (int j = 0; j < usageStats.length(); j++) {
-                            final JSONObject usageStat = usageStats.getJSONObject(j);
-                            final String usageType = usageStat.getString(USAGE_TYPE);
-                            if (TextUtils.isEmpty(usageType)) {
-                                throw new IllegalArgumentException("Usage type cannot be empty.");
-                            }
-                            final long lastTimeUsed = usageStat.getLong(LAST_TIME_USED);
-                            final int usageCount = usageStat.getInt(USAGE_COUNT);
-
-                            final UsageStats usageStatsParsed = new UsageStats(
-                                    usageType, lastTimeUsed, usageCount);
-                            usageStatsList.add(usageStatsParsed);
-                        }
-                    }
-
-                    final FieldData fieldDataParse = new FieldData(dataHashId, isPrimary,
-                            isSuperPrimary, usageStatsList);
-                    fieldDatasList.add(fieldDataParse);
-                }
-            }
-            final MetadataEntry metaDataEntry = new MetadataEntry(rawContactInfo,
-                    sendToVoicemail ? 1 : 0, starred ? 1 : 0, pinned,
-                    fieldDatasList, aggregationsList);
-            return metaDataEntry;
-        } catch (JSONException e) {
-            throw new IllegalArgumentException("JSON Exception.", e);
-        }
-    }
-
-    private static RawContactInfo parseUniqueContact(JSONObject uniqueContactJSON) {
-        try {
-            final String backupId = uniqueContactJSON.getString(CONTACT_ID);
-            final String accountName = uniqueContactJSON.getString(ACCOUNT_NAME);
-            String accountType = uniqueContactJSON.getString(ACCOUNT_TYPE);
-            if (ENUM_VALUE_FOR_GOOGLE_ACCOUNT.equals(accountType)) {
-                accountType = GOOGLE_ACCOUNT_TYPE;
-            } else if (ENUM_VALUE_FOR_CUSTOM_ACCOUNT.equals(accountType)) {
-                accountType = uniqueContactJSON.getString(CUSTOM_ACCOUNT_TYPE);
-            } else {
-                throw new IllegalArgumentException("Unknown account type.");
-            }
-
-            String dataSet = null;
-            switch (uniqueContactJSON.getString(DATA_SET)) {
-                case ENUM_FOR_PLUS_DATA_SET:
-                    dataSet = PLUS_DATA_SET_TYPE;
-                    break;
-                case ENUM_FOR_CUSTOM_DATA_SET:
-                    dataSet = uniqueContactJSON.getString(CUSTOM_DATA_SET);
-                    break;
-            }
-            if (TextUtils.isEmpty(backupId) || TextUtils.isEmpty(accountType)
-                    || TextUtils.isEmpty(accountName)) {
-                throw new IllegalArgumentException(
-                        "Contact backup id, account type, account name cannot be empty.");
-            }
-            final RawContactInfo rawContactInfo = new RawContactInfo(
-                    backupId, accountType, accountName, dataSet);
-            return rawContactInfo;
-        } catch (JSONException e) {
-            throw new IllegalArgumentException("JSON Exception.", e);
-        }
-    }
-}
diff --git a/src/com/android/providers/contacts/PhoneLookupWithStarPrefix.java b/src/com/android/providers/contacts/PhoneLookupWithStarPrefix.java
index 7efc891..ca29edc 100644
--- a/src/com/android/providers/contacts/PhoneLookupWithStarPrefix.java
+++ b/src/com/android/providers/contacts/PhoneLookupWithStarPrefix.java
@@ -15,6 +15,7 @@
  */
 package com.android.providers.contacts;
 
+import com.android.i18n.phonenumbers.Phonenumber;
 import com.android.internal.annotations.VisibleForTesting;
 
 import android.database.Cursor;
@@ -148,4 +149,36 @@
                 return null;
         }
     }
+
+    /**
+     * Check each phone number in the given cursor to detemine if it's a match with the given phone
+     * number. Return the matching ones in a new cursor.
+     * @param number phone number to be match
+     * @param cursor contains a series of number s to be match
+     * @param defaultCountryIso The lowercase two letter ISO 3166-1 country code. It is recommended
+     *                         to pass in {@link TelephonyManager#getNetworkCountryIso()}.
+     * @return A new cursor with all matching phone numbers.
+     */
+    public static Cursor removeNoMatchPhoneNumber(String number, Cursor cursor,
+            String defaultCountryIso) {
+        if (number == null) {
+            return cursor;
+        }
+
+        final MatrixCursor matrixCursor = new MatrixCursor(cursor.getColumnNames());
+
+        cursor.moveToPosition(-1);
+        while (cursor.moveToNext()) {
+            final int numberIndex = cursor.getColumnIndex(PhoneLookup.NUMBER);
+            final String numberToMatch = cursor.getString(numberIndex);
+            if (PhoneNumberUtils.areSamePhoneNumber(number, numberToMatch, defaultCountryIso)) {
+                final MatrixCursor.RowBuilder b = matrixCursor.newRow();
+                for (int column = 0; column < cursor.getColumnCount(); column++) {
+                    b.add(cursor.getColumnName(column), cursorValue(cursor, column));
+                }
+            }
+        }
+
+        return matrixCursor;
+    }
 }
diff --git a/src/com/android/providers/contacts/ProfileProvider.java b/src/com/android/providers/contacts/ProfileProvider.java
index f3b6daf..d9dc784 100644
--- a/src/com/android/providers/contacts/ProfileProvider.java
+++ b/src/com/android/providers/contacts/ProfileProvider.java
@@ -116,8 +116,8 @@
         mDelegate.notifyChange();
     }
 
-    protected void notifyChange(boolean syncToNetwork, boolean syncToMetadataNetWork) {
-        mDelegate.notifyChange(syncToNetwork, syncToMetadataNetWork);
+    protected void notifyChange(boolean syncToNetwork) {
+        mDelegate.notifyChange(syncToNetwork);
     }
 
     protected Locale getLocale() {
diff --git a/src/com/android/providers/contacts/SearchIndexManager.java b/src/com/android/providers/contacts/SearchIndexManager.java
index e421654..aeaa0e7 100644
--- a/src/com/android/providers/contacts/SearchIndexManager.java
+++ b/src/com/android/providers/contacts/SearchIndexManager.java
@@ -55,7 +55,8 @@
     private static final int MAX_STRING_BUILDER_SIZE = 1024 * 10;
 
     public static final String PROPERTY_SEARCH_INDEX_VERSION = "search_index";
-    private static final int SEARCH_INDEX_VERSION = 1;
+    private static final String ROW_ID_KEY = "rowid";
+    private static final int SEARCH_INDEX_VERSION = 2;
 
     private static final class ContactIndexQuery {
         public static final String[] COLUMNS = {
@@ -327,7 +328,7 @@
         // Remove affected search_index rows.
         final SQLiteDatabase db = mDbHelper.getWritableDatabase();
         final int deleted = db.delete(Tables.SEARCH_INDEX,
-                SearchIndexColumns.CONTACT_ID + " IN (SELECT " +
+                ROW_ID_KEY + " IN (SELECT " +
                     RawContacts.CONTACT_ID +
                     " FROM " + Tables.RAW_CONTACTS +
                     " WHERE " + rawContactsSelection +
@@ -400,6 +401,7 @@
         mValues.put(SearchIndexColumns.NAME, builder.getName());
         mValues.put(SearchIndexColumns.TOKENS, builder.getTokens());
         mValues.put(SearchIndexColumns.CONTACT_ID, contactId);
+        mValues.put(ROW_ID_KEY, contactId);
         db.insert(Tables.SEARCH_INDEX, null, mValues);
     }
     private int getSearchIndexVersion() {
diff --git a/src/com/android/providers/contacts/TransactionContext.java b/src/com/android/providers/contacts/TransactionContext.java
index dfb6d69..86dae01 100644
--- a/src/com/android/providers/contacts/TransactionContext.java
+++ b/src/com/android/providers/contacts/TransactionContext.java
@@ -35,7 +35,6 @@
     /** Map from raw contact id to account Id */
     private ArrayMap<Long, Long> mInsertedRawContactsAccounts;
     private ArraySet<Long> mUpdatedRawContacts;
-    private ArraySet<Long> mMetadataDirtyRawContacts;
     private ArraySet<Long> mBackupIdChangedRawContacts;
     private ArraySet<Long> mDirtyRawContacts;
     // Set used to track what has been changed and deleted. This is needed so we can update the
@@ -78,22 +77,6 @@
         markRawContactChangedOrDeletedOrInserted(rawContactId);
     }
 
-    public void markRawContactMetadataDirty(long rawContactId, boolean isMetadataSyncAdapter) {
-        if (!isMetadataSyncAdapter) {
-            if (mMetadataDirtyRawContacts == null) {
-                mMetadataDirtyRawContacts = new ArraySet<>();
-            }
-            mMetadataDirtyRawContacts.add(rawContactId);
-        }
-    }
-
-    public void markBackupIdChangedRawContact(long rawContactId) {
-        if (mBackupIdChangedRawContacts == null) {
-            mBackupIdChangedRawContacts = new ArraySet<>();
-        }
-        mBackupIdChangedRawContacts.add(rawContactId);
-    }
-
     public void markRawContactChangedOrDeletedOrInserted(long rawContactId) {
         if (mChangedRawContacts == null) {
             mChangedRawContacts = new ArraySet<>();
@@ -131,16 +114,6 @@
         return mDirtyRawContacts;
     }
 
-    public Set<Long> getMetadataDirtyRawContactIds() {
-        if (mMetadataDirtyRawContacts == null) mMetadataDirtyRawContacts = new ArraySet<>();
-        return mMetadataDirtyRawContacts;
-    }
-
-    public Set<Long> getBackupIdChangedRawContacts() {
-        if (mBackupIdChangedRawContacts == null) mBackupIdChangedRawContacts = new ArraySet<>();
-        return mBackupIdChangedRawContacts;
-    }
-
     public Set<Long> getChangedRawContactIds() {
         if (mChangedRawContacts == null) mChangedRawContacts = new ArraySet<>();
         return mChangedRawContacts;
@@ -176,7 +149,6 @@
         mUpdatedRawContacts = null;
         mUpdatedSyncStates = null;
         mDirtyRawContacts = null;
-        mMetadataDirtyRawContacts = null;
         mChangedRawContacts = null;
         mBackupIdChangedRawContacts = null;
     }
diff --git a/src/com/android/providers/contacts/VoicemailContentProvider.java b/src/com/android/providers/contacts/VoicemailContentProvider.java
index daecd97..ef7a375 100644
--- a/src/com/android/providers/contacts/VoicemailContentProvider.java
+++ b/src/com/android/providers/contacts/VoicemailContentProvider.java
@@ -24,6 +24,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.AppOpsManager;
+import android.content.AttributionSource;
 import android.content.ContentProvider;
 import android.content.ContentValues;
 import android.content.Context;
@@ -119,24 +120,23 @@
     }
 
     @Override
-    protected int enforceReadPermissionInner(Uri uri, String callingPkg,
-            @Nullable String featureId, IBinder callerToken) throws SecurityException {
+    protected int enforceReadPermissionInner(Uri uri,
+            @NonNull AttributionSource attributionSource) throws SecurityException {
         // Permit carrier-privileged apps regardless of ADD_VOICEMAIL permission state.
         if (mVoicemailPermissions.callerHasCarrierPrivileges()) {
             return MODE_ALLOWED;
         }
-        return super.enforceReadPermissionInner(uri, callingPkg, featureId, callerToken);
+        return super.enforceReadPermissionInner(uri, attributionSource);
     }
 
-
     @Override
-    protected int enforceWritePermissionInner(Uri uri, String callingPkg,
-            @Nullable String featureId, IBinder callerToken) throws SecurityException {
+    protected int enforceWritePermissionInner(Uri uri,
+            @NonNull AttributionSource attributionSource) throws SecurityException {
         // Permit carrier-privileged apps regardless of ADD_VOICEMAIL permission state.
         if (mVoicemailPermissions.callerHasCarrierPrivileges()) {
             return MODE_ALLOWED;
         }
-        return super.enforceWritePermissionInner(uri, callingPkg, featureId, callerToken);
+        return super.enforceWritePermissionInner(uri, attributionSource);
     }
 
     @VisibleForTesting
diff --git a/src/com/android/providers/contacts/VoicemailNotifier.java b/src/com/android/providers/contacts/VoicemailNotifier.java
index 159cec7..b4033ea 100644
--- a/src/com/android/providers/contacts/VoicemailNotifier.java
+++ b/src/com/android/providers/contacts/VoicemailNotifier.java
@@ -1,5 +1,6 @@
 package com.android.providers.contacts;
 
+import android.app.BroadcastOptions;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -27,6 +28,12 @@
 
     private final String TAG = "VoicemailNotifier";
 
+    /**
+     * Grant recipients of new voicemail broadcasts a 10sec allowlist so they can start a background
+     * service to do VVM processing.
+     */
+    private final long VOICEMAIL_ALLOW_LIST_DURATION_MILLIS = 10000;
+
     private final Context mContext;
     private final Uri mBaseUri;
 
@@ -85,7 +92,17 @@
                     intent.putExtra(VoicemailContract.EXTRA_SELF_CHANGE,
                             callingPackages.contains(component.getPackageName()));
                 }
-                mContext.sendBroadcast(intent);
+                if (intentAction.equals(VoicemailContract.ACTION_NEW_VOICEMAIL)) {
+                    BroadcastOptions bopts = BroadcastOptions.makeBasic();
+                    bopts.setTemporaryAppWhitelistDuration(VOICEMAIL_ALLOW_LIST_DURATION_MILLIS);
+                    Log.i(TAG, String.format("sendNotification: allowMillis=%d, pkg=%s",
+                            VOICEMAIL_ALLOW_LIST_DURATION_MILLIS, component.getPackageName()));
+                    mContext.sendBroadcast(intent, android.Manifest.permission.READ_VOICEMAIL,
+                            bopts.toBundle());
+                } else {
+                    mContext.sendBroadcast(intent);
+                }
+
                 Log.v(TAG, String.format("Sent intent. act:%s, url:%s, comp:%s," +
                                 " self_change:%s", intent.getAction(), intent.getData(),
                         component.getClassName(),
diff --git a/src/com/android/providers/contacts/VoicemailPermissions.java b/src/com/android/providers/contacts/VoicemailPermissions.java
index 58e7a14..b38f7f5 100644
--- a/src/com/android/providers/contacts/VoicemailPermissions.java
+++ b/src/com/android/providers/contacts/VoicemailPermissions.java
@@ -151,7 +151,12 @@
     }
 
     private static boolean packageHasCarrierPrivileges(TelephonyManager tm, String packageName) {
-        return tm.checkCarrierPrivilegesForPackageAnyPhone(packageName)
-                == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
+        final long token = Binder.clearCallingIdentity();
+        try {
+            return tm.checkCarrierPrivilegesForPackageAnyPhone(packageName)
+                    == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
     }
 }
diff --git a/src/com/android/providers/contacts/debug/ContactsDumpActivity.java b/src/com/android/providers/contacts/debug/ContactsDumpActivity.java
index 359f3f8..725b5bf 100644
--- a/src/com/android/providers/contacts/debug/ContactsDumpActivity.java
+++ b/src/com/android/providers/contacts/debug/ContactsDumpActivity.java
@@ -66,6 +66,13 @@
     }
 
     @Override
+    protected void onStart() {
+        super.onStart();
+        getWindow().addSystemFlags(android.view.WindowManager.LayoutParams
+                .SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
+    }
+
+    @Override
     public void onClick(View v) {
         switch (v.getId()) {
             case R.id.confirm:
diff --git a/src/com/android/providers/contacts/util/LogFields.java b/src/com/android/providers/contacts/util/LogFields.java
new file mode 100644
index 0000000..4d07ca4
--- /dev/null
+++ b/src/com/android/providers/contacts/util/LogFields.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2020 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 android.net.Uri;
+
+public final class LogFields {
+
+    private final int apiType;
+
+    private final int uriType;
+
+    private final boolean callerIsSyncAdapter;
+
+    private final long startNanos;
+
+    private Exception exception;
+
+    private Uri resultUri;
+
+    private int resultCount;
+
+    private int methodCall;
+
+    public LogFields(int apiType, int uriType, boolean callerIsSyncAdapter, long startNanos) {
+        this.apiType = apiType;
+        this.uriType = uriType;
+        this.callerIsSyncAdapter = callerIsSyncAdapter;
+        this.startNanos = startNanos;
+    }
+
+    public int getApiType() {
+        return apiType;
+    }
+
+    public int getUriType() {
+        return uriType;
+    }
+
+    public boolean isCallerIsSyncAdapter() {
+        return callerIsSyncAdapter;
+    }
+
+    public long getStartNanos() {
+        return startNanos;
+    }
+
+    public Exception getException() {
+        return exception;
+    }
+
+    public Uri getResultUri() {
+        return resultUri;
+    }
+
+    public int getResultCount() {
+        return resultCount;
+    }
+
+    public int getMethodCall() {
+        return methodCall;
+    }
+
+    public static final class Builder {
+        private int apiType;
+        private int uriType;
+        private boolean callerIsSyncAdapter;
+        private long startNanos;
+        private Exception exception;
+        private Uri resultUri;
+        private int resultCount;
+        private int methodCall;
+
+        private Builder() {
+        }
+
+        public static Builder aLogFields() {
+            return new Builder();
+        }
+
+        public Builder setApiType(int apiType) {
+            this.apiType = apiType;
+            return this;
+        }
+
+        public Builder setUriType(int uriType) {
+            this.uriType = uriType;
+            return this;
+        }
+
+        public Builder setCallerIsSyncAdapter(boolean callerIsSyncAdapter) {
+            this.callerIsSyncAdapter = callerIsSyncAdapter;
+            return this;
+        }
+
+        public Builder setStartNanos(long startNanos) {
+            this.startNanos = startNanos;
+            return this;
+        }
+
+        public Builder setException(Exception exception) {
+            this.exception = exception;
+            return this;
+        }
+
+        public Builder setResultUri(Uri resultUri) {
+            this.resultUri = resultUri;
+            return this;
+        }
+
+        public Builder setResultCount(int resultCount) {
+            this.resultCount = resultCount;
+            return this;
+        }
+
+        public Builder setMethodCall(int methodCall) {
+            this.methodCall = methodCall;
+            return this;
+        }
+
+        public LogFields build() {
+            LogFields logFields = new LogFields(apiType, uriType, callerIsSyncAdapter, startNanos);
+            logFields.resultCount = this.resultCount;
+            logFields.exception = this.exception;
+            logFields.resultUri = this.resultUri;
+            logFields.methodCall = this.methodCall;
+            return logFields;
+        }
+    }
+}
diff --git a/src/com/android/providers/contacts/util/LogUtils.java b/src/com/android/providers/contacts/util/LogUtils.java
new file mode 100644
index 0000000..a564a35
--- /dev/null
+++ b/src/com/android/providers/contacts/util/LogUtils.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2020 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 android.os.SystemClock;
+import android.util.StatsEvent;
+import android.util.StatsLog;
+
+public class LogUtils {
+    // Keep in sync with ContactsProviderStatus#ResultType in
+    // frameworks/proto_logging/stats/atoms.proto file.
+    public interface ResultType {
+        int SUCCESS = 1;
+        int FAIL = 2;
+        int ILLEGAL_ARGUMENT = 3;
+        int UNSUPPORTED_OPERATION = 4;
+    }
+
+    // Keep in sync with ContactsProviderStatus#ApiType in
+    // frameworks/proto_logging/stats/atoms.proto file.
+    public interface ApiType {
+        int QUERY = 1;
+        int INSERT = 2;
+        int UPDATE = 3;
+        int DELETE = 4;
+        int CALL = 5;
+    }
+
+    // Keep in sync with ContactsProviderStatus#CallerType in
+    // frameworks/proto_logging/stats/atoms.proto file.
+    public interface CallerType {
+        int CALLER_IS_SYNC_ADAPTER = 1;
+        int CALLER_IS_NOT_SYNC_ADAPTER = 2;
+    }
+
+    // Keep in sync with ContactsProviderStatus#MethodCall in
+    // frameworks/proto_logging/stats/atoms.proto file.
+    public interface MethodCall {
+        int ADD_SIM_ACCOUNTS = 1;
+        int REMOVE_SIM_ACCOUNTS = 2;
+        int GET_SIM_ACCOUNTS = 3;
+    }
+
+    private static final int STATSD_LOG_ATOM_ID = 301;
+
+    public static void log(LogFields logFields) {
+        StatsLog.write(StatsEvent.newBuilder()
+                .setAtomId(STATSD_LOG_ATOM_ID)
+                .writeInt(logFields.getApiType())
+                .writeInt(logFields.getUriType())
+                .writeInt(getCallerType(logFields.isCallerIsSyncAdapter()))
+                .writeInt(getResultType(logFields.getException()))
+                .writeInt(logFields.getResultCount())
+                .writeLong(getLatencyMicros(logFields.getStartNanos()))
+                .writeInt(0) // Empty value for TaskType
+                .writeInt(logFields.getMethodCall())
+                .usePooledBuffer()
+                .build());
+    }
+
+    private static int getCallerType(boolean callerIsSyncAdapter) {
+        return callerIsSyncAdapter
+                ? CallerType.CALLER_IS_SYNC_ADAPTER : CallerType.CALLER_IS_NOT_SYNC_ADAPTER;
+    }
+
+    private static int getResultType(Exception exception) {
+        if (exception == null) {
+            return ResultType.SUCCESS;
+        } else if (exception instanceof IllegalArgumentException) {
+            return ResultType.ILLEGAL_ARGUMENT;
+        } else if (exception instanceof UnsupportedOperationException) {
+            return ResultType.UNSUPPORTED_OPERATION;
+        } else {
+            return ResultType.FAIL;
+        }
+    }
+
+    private static long getLatencyMicros(long startNanos) {
+        return (SystemClock.elapsedRealtimeNanos() - startNanos) / 1000;
+    }
+}
+
+
diff --git a/test_common/Android.bp b/test_common/Android.bp
index b30e2e3..207b1db 100644
--- a/test_common/Android.bp
+++ b/test_common/Android.bp
@@ -12,6 +12,17 @@
 // 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",
+    ],
+}
+
 java_library {
     name: "ContactsProviderTestUtils",
     srcs: ["src/**/*.java"],
diff --git a/tests/Android.bp b/tests/Android.bp
index b15ded4..6fc1d17 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -1,3 +1,14 @@
+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: "ContactsProviderTests",
     static_libs: [
diff --git a/tests/assets/test1/testFileDeviceContactMetadataJSON.txt b/tests/assets/test1/testFileDeviceContactMetadataJSON.txt
deleted file mode 100644
index 65e624d..0000000
--- a/tests/assets/test1/testFileDeviceContactMetadataJSON.txt
+++ /dev/null
@@ -1,69 +0,0 @@
-{
-  "unique_contact_id": {
-    "account_type": "CUSTOM_ACCOUNT",
-    "custom_account_type": "facebook",
-    "account_name": "android-test",
-    "contact_id": "1111111",
-    "data_set": "FOCUS"
-  },
-  "contact_prefs": {
-    "send_to_voicemail": true,
-    "starred": false,
-    "pinned": 2
-  },
-  "aggregation_data": [
-    {
-      "type": "TOGETHER",
-      "contact_ids": [
-        {
-          "account_type": "GOOGLE_ACCOUNT",
-          "account_name": "android-test2",
-          "contact_id": "2222222",
-          "data_set": "GOOGLE_PLUS"
-        },
-        {
-          "account_type": "GOOGLE_ACCOUNT",
-          "account_name": "android-test3",
-          "contact_id": "3333333",
-          "data_set": "CUSTOM",
-          "custom_data_set": "custom type"
-        }
-      ]
-    }
-  ],
-  "field_data": [
-    {
-      "field_data_id": "1001",
-      "field_data_prefs": {
-        "is_primary": true,
-        "is_super_primary": true
-      },
-      "usage_stats": [
-        {
-          "usage_type": "CALL",
-          "last_time_used": 10000001,
-          "usage_count": 10
-        },
-        {
-          "usage_type": "SHORT_TEXT",
-          "last_time_used": 20000002,
-          "usage_count": 20
-        }
-      ]
-    },
-    {
-      "field_data_id": "1002",
-      "field_data_prefs": {
-        "is_primary": false,
-        "is_super_primary": false
-      },
-      "usage_stats": [
-        {
-          "usage_type": "LONG_TEXT",
-          "last_time_used": 30000003,
-          "usage_count": 30
-        }
-      ]
-    }
-  ]
-}
\ No newline at end of file
diff --git a/tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java b/tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java
index 91a76a3..816d10d 100644
--- a/tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java
+++ b/tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java
@@ -1331,10 +1331,6 @@
         assertEquals(expected, (getContactsProvider()).isNetworkNotified());
     }
 
-    protected void assertMetadataNetworkNotified(boolean expected) {
-        assertEquals(expected, (getContactsProvider()).isMetadataNetworkNotified());
-    }
-
     protected void assertProjection(Uri uri, String[] expectedProjection) {
         Cursor cursor = mResolver.query(uri, null, "0", null, null);
         String[] actualProjection = cursor.getColumnNames();
diff --git a/tests/src/com/android/providers/contacts/CallLogProviderTest.java b/tests/src/com/android/providers/contacts/CallLogProviderTest.java
index 9baf1e4..92b4b17 100644
--- a/tests/src/com/android/providers/contacts/CallLogProviderTest.java
+++ b/tests/src/com/android/providers/contacts/CallLogProviderTest.java
@@ -16,6 +16,8 @@
 
 package com.android.providers.contacts;
 
+import static android.provider.CallLog.Calls.MISSED_REASON_NOT_MISSED;
+
 import android.telecom.CallerInfo;
 import com.android.providers.contacts.testutil.CommonDatabaseUtils;
 import com.android.providers.contacts.util.ContactsPermissions;
@@ -60,7 +62,7 @@
             Voicemails.DIRTY,
             Voicemails.DELETED};
     /** Total number of columns exposed by call_log provider. */
-    private static final int NUM_CALLLOG_FIELDS = 35;
+    private static final int NUM_CALLLOG_FIELDS = 40;
 
     private static final int MIN_MATCH = 7;
 
@@ -199,7 +201,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);
+                40, null, MISSED_REASON_NOT_MISSED);
         ContactsPermissions.ALLOW_SELF_CALL = false;
         assertNotNull(uri);
         assertEquals("0@" + CallLog.AUTHORITY, uri.getAuthority());
@@ -222,6 +224,7 @@
         // Casting null to Long as there are many forms of "put" which have nullable second
         // 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);
         assertStoredValues(uri, values);
     }
 
@@ -495,6 +498,7 @@
                 values.put(Calls.DATA_USAGE, 1000);
                 values.put(Calls.PHONE_ACCOUNT_COMPONENT_NAME, (String) null);
                 values.put(Calls.PHONE_ACCOUNT_ID, (Long) null);
+                values.put(Calls.PRIORITY, Calls.PRIORITY_NORMAL);
                 break;
             case 1:
                 values.put(Calls.NUMBER, "654321");
@@ -506,6 +510,7 @@
                 values.put(Calls.DATA_USAGE, 0);
                 values.put(Calls.PHONE_ACCOUNT_COMPONENT_NAME, (String) null);
                 values.put(Calls.PHONE_ACCOUNT_ID, (Long) null);
+                values.put(Calls.PRIORITY, Calls.PRIORITY_NORMAL);
                 break;
             case 2:
                 values.put(Calls.NUMBER, "123456");
@@ -517,6 +522,7 @@
                 values.put(Calls.DATA_USAGE, 2000);
                 values.put(Calls.PHONE_ACCOUNT_COMPONENT_NAME, (String) null);
                 values.put(Calls.PHONE_ACCOUNT_ID, (Long) null);
+                values.put(Calls.PRIORITY, Calls.PRIORITY_URGENT);
                 break;
         }
         return values;
diff --git a/tests/src/com/android/providers/contacts/ContactMetadataProviderTest.java b/tests/src/com/android/providers/contacts/ContactMetadataProviderTest.java
deleted file mode 100644
index 3d8b8eb..0000000
--- a/tests/src/com/android/providers/contacts/ContactMetadataProviderTest.java
+++ /dev/null
@@ -1,586 +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.ContentProviderOperation;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.database.Cursor;
-import android.net.Uri;
-import android.provider.ContactsContract.MetadataSync;
-import android.provider.ContactsContract.MetadataSyncState;
-import android.provider.ContactsContract.RawContacts;
-import android.test.MoreAsserts;
-import android.test.suitebuilder.annotation.MediumTest;
-import com.android.providers.contacts.ContactsDatabaseHelper.MetadataSyncColumns;
-import com.android.providers.contacts.testutil.RawContactUtil;
-import com.google.android.collect.Lists;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * Unit tests for {@link com.android.providers.contacts.ContactMetadataProvider}.
- * <p/>
- * Run the test like this:
- * <code>
- * adb shell am instrument -e class com.android.providers.contacts.ContactMetadataProviderTest -w \
- * com.android.providers.contacts.tests/android.test.InstrumentationTestRunner
- * </code>
- */
-@MediumTest
-public class ContactMetadataProviderTest extends BaseContactsProvider2Test {
-    private static String TEST_ACCOUNT_TYPE1 = "test_account_type1";
-    private static String TEST_ACCOUNT_NAME1 = "test_account_name1";
-    private static String TEST_DATA_SET1 = "plus";
-    private static String TEST_BACKUP_ID1 = "1001";
-    private static String TEST_DATA1 = "{\n" +
-            "  \"unique_contact_id\": {\n" +
-            "    \"account_type\": \"CUSTOM_ACCOUNT\",\n" +
-            "    \"custom_account_type\": " + TEST_ACCOUNT_TYPE1 + ",\n" +
-            "    \"account_name\": " + TEST_ACCOUNT_NAME1 + ",\n" +
-            "    \"contact_id\": " + TEST_BACKUP_ID1 + ",\n" +
-            "    \"data_set\": \"GOOGLE_PLUS\"\n" +
-            "  },\n" +
-            "  \"contact_prefs\": {\n" +
-            "    \"send_to_voicemail\": true,\n" +
-            "    \"starred\": true,\n" +
-            "    \"pinned\": 2\n" +
-            "  }\n" +
-            "  }";
-    private static byte[] TEST_SYNC_STATE1 = "sync state1".getBytes();
-    private static String TEST_ACCOUNT_TYPE2 = "test_account_type2";
-    private static String TEST_ACCOUNT_NAME2 = "test_account_name2";
-    private static String TEST_DATA_SET2 = null;
-    private static String TEST_BACKUP_ID2 = "1002";
-    private static String TEST_DATA2 =  "{\n" +
-            "  \"unique_contact_id\": {\n" +
-            "    \"account_type\": \"CUSTOM_ACCOUNT\",\n" +
-            "    \"custom_account_type\": " + TEST_ACCOUNT_TYPE2 + ",\n" +
-            "    \"account_name\": " + TEST_ACCOUNT_NAME2 + ",\n" +
-            "    \"contact_id\": " + TEST_BACKUP_ID2 + ",\n" +
-            "    \"data_set\": \"GOOGLE_PLUS\"\n" +
-            "  },\n" +
-            "  \"contact_prefs\": {\n" +
-            "    \"send_to_voicemail\": true,\n" +
-            "    \"starred\": true,\n" +
-            "    \"pinned\": 2\n" +
-            "  }\n" +
-            "  }";
-    private static byte[] TEST_SYNC_STATE2 = "sync state2".getBytes();
-    private static String SELECTION_BY_TEST_ACCOUNT1 = MetadataSync.ACCOUNT_NAME + "='" +
-            TEST_ACCOUNT_NAME1 + "' AND " + MetadataSync.ACCOUNT_TYPE + "='" + TEST_ACCOUNT_TYPE1 +
-            "' AND " + MetadataSync.DATA_SET + "='" + TEST_DATA_SET1 + "'";
-
-    private static String SELECTION_BY_TEST_ACCOUNT2 = MetadataSync.ACCOUNT_NAME + "='" +
-            TEST_ACCOUNT_NAME2 + "' AND " + MetadataSync.ACCOUNT_TYPE + "='" + TEST_ACCOUNT_TYPE2 +
-            "' AND " + MetadataSync.DATA_SET + "='" + TEST_DATA_SET2 + "'";
-
-    private ContactMetadataProvider mContactMetadataProvider;
-    private ContentValues defaultValues;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mContactMetadataProvider = addProvider(
-                ContactMetadataProviderTestable.class, MetadataSync.METADATA_AUTHORITY);
-        // Reset the dbHelper to be the one ContactsProvider2 is using. Before this, two providers
-        // are using different dbHelpers.
-        mContactMetadataProvider.setDatabaseHelper(((SynchronousContactsProvider2)
-                mActor.provider).getDatabaseHelper());
-        setupData();
-    }
-
-    public void testInsertWithInvalidUri() {
-        try {
-            mResolver.insert(Uri.withAppendedPath(MetadataSync.METADATA_AUTHORITY_URI,
-                    "metadata"), getDefaultValues());
-            fail("the insert was expected to fail, but it succeeded");
-        } catch (IllegalArgumentException e) {
-            // this was expected
-        }
-    }
-
-    public void testUpdateWithInvalidUri() {
-        try {
-            mResolver.update(Uri.withAppendedPath(MetadataSync.METADATA_AUTHORITY_URI,
-                    "metadata"), getDefaultValues(), null, null);
-            fail("the update was expected to fail, but it succeeded");
-        } catch (IllegalArgumentException e) {
-            // this was expected
-        }
-    }
-
-    public void testGetMetadataByAccount() {
-        Cursor c = mResolver.query(MetadataSync.CONTENT_URI, null, SELECTION_BY_TEST_ACCOUNT1,
-                null, null);
-        assertEquals(1, c.getCount());
-
-        ContentValues expectedValues = defaultValues;
-        expectedValues.remove(MetadataSyncColumns.ACCOUNT_ID);
-        c.moveToFirst();
-        assertCursorValues(c, expectedValues);
-        c.close();
-    }
-
-    public void testFailOnInsertMetadataForSameAccountIdAndBackupId() {
-        // Insert a new metadata with same account and backupId as defaultValues should fail.
-        String newData = "{\n" +
-                "  \"unique_contact_id\": {\n" +
-                "    \"account_type\": \"CUSTOM_ACCOUNT\",\n" +
-                "    \"custom_account_type\": " + TEST_ACCOUNT_TYPE1 + ",\n" +
-                "    \"account_name\": " + TEST_ACCOUNT_NAME1 + ",\n" +
-                "    \"contact_id\": " + TEST_BACKUP_ID1 + ",\n" +
-                "    \"data_set\": \"GOOGLE_PLUS\"\n" +
-                "  },\n" +
-                "  \"contact_prefs\": {\n" +
-                "    \"send_to_voicemail\": false,\n" +
-                "    \"starred\": false,\n" +
-                "    \"pinned\": 1\n" +
-                "  }\n" +
-                "  }";
-
-        ContentValues  newValues =  new ContentValues();
-        newValues.put(MetadataSync.ACCOUNT_NAME, TEST_ACCOUNT_NAME1);
-        newValues.put(MetadataSync.ACCOUNT_TYPE, TEST_ACCOUNT_TYPE1);
-        newValues.put(MetadataSync.DATA_SET, TEST_DATA_SET1);
-        newValues.put(MetadataSync.RAW_CONTACT_BACKUP_ID, TEST_BACKUP_ID1);
-        newValues.put(MetadataSync.DATA, newData);
-        newValues.put(MetadataSync.DELETED, 0);
-        try {
-            mResolver.insert(MetadataSync.CONTENT_URI, newValues);
-        } catch (Exception e) {
-            // Expected.
-        }
-    }
-
-    public void testInsertAndUpdateMetadataSync() {
-        // Create a raw contact with backupId.
-        String backupId = "backupId10001";
-        long rawContactId = RawContactUtil.createRawContactWithAccountDataSet(
-                mResolver, TEST_ACCOUNT_NAME1, TEST_ACCOUNT_TYPE1, TEST_DATA_SET1);
-        Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
-        ContentValues values = new ContentValues();
-        values.put(RawContacts.BACKUP_ID, backupId);
-        assertEquals(1, mResolver.update(rawContactUri, values, null, null));
-
-        assertStoredValue(rawContactUri, RawContacts._ID, rawContactId);
-        assertStoredValue(rawContactUri, RawContacts.ACCOUNT_TYPE, TEST_ACCOUNT_TYPE1);
-        assertStoredValue(rawContactUri, RawContacts.ACCOUNT_NAME, TEST_ACCOUNT_NAME1);
-        assertStoredValue(rawContactUri, RawContacts.BACKUP_ID, backupId);
-        assertStoredValue(rawContactUri, RawContacts.DATA_SET, TEST_DATA_SET1);
-
-        String deleted = "0";
-        String insertJson = "{\n" +
-                "  \"unique_contact_id\": {\n" +
-                "    \"account_type\": \"CUSTOM_ACCOUNT\",\n" +
-                "    \"custom_account_type\": " + TEST_ACCOUNT_TYPE1 + ",\n" +
-                "    \"account_name\": " + TEST_ACCOUNT_NAME1 + ",\n" +
-                "    \"contact_id\": " + backupId + ",\n" +
-                "    \"data_set\": \"GOOGLE_PLUS\"\n" +
-                "  },\n" +
-                "  \"contact_prefs\": {\n" +
-                "    \"send_to_voicemail\": true,\n" +
-                "    \"starred\": true,\n" +
-                "    \"pinned\": 2\n" +
-                "  }\n" +
-                "  }";
-
-        // Insert to MetadataSync table.
-        ContentValues insertedValues = new ContentValues();
-        insertedValues.put(MetadataSync.RAW_CONTACT_BACKUP_ID, backupId);
-        insertedValues.put(MetadataSync.ACCOUNT_TYPE, TEST_ACCOUNT_TYPE1);
-        insertedValues.put(MetadataSync.ACCOUNT_NAME, TEST_ACCOUNT_NAME1);
-        insertedValues.put(MetadataSync.DATA_SET, TEST_DATA_SET1);
-        insertedValues.put(MetadataSync.DATA, insertJson);
-        insertedValues.put(MetadataSync.DELETED, deleted);
-        Uri metadataUri = mResolver.insert(MetadataSync.CONTENT_URI, insertedValues);
-
-        long metadataId = ContentUris.parseId(metadataUri);
-        assertEquals(true, metadataId > 0);
-
-        // Check if RawContact table is updated  after inserting metadata.
-        assertStoredValue(rawContactUri, RawContacts.ACCOUNT_TYPE, TEST_ACCOUNT_TYPE1);
-        assertStoredValue(rawContactUri, RawContacts.ACCOUNT_NAME, TEST_ACCOUNT_NAME1);
-        assertStoredValue(rawContactUri, RawContacts.BACKUP_ID, backupId);
-        assertStoredValue(rawContactUri, RawContacts.DATA_SET, TEST_DATA_SET1);
-        assertStoredValue(rawContactUri, RawContacts.SEND_TO_VOICEMAIL, "1");
-        assertStoredValue(rawContactUri, RawContacts.STARRED, "1");
-        assertStoredValue(rawContactUri, RawContacts.PINNED, "2");
-
-        // Update the MetadataSync table.
-        String updatedJson = "{\n" +
-                "  \"unique_contact_id\": {\n" +
-                "    \"account_type\": \"CUSTOM_ACCOUNT\",\n" +
-                "    \"custom_account_type\": " + TEST_ACCOUNT_TYPE1 + ",\n" +
-                "    \"account_name\": " + TEST_ACCOUNT_NAME1 + ",\n" +
-                "    \"contact_id\": " + backupId + ",\n" +
-                "    \"data_set\": \"GOOGLE_PLUS\"\n" +
-                "  },\n" +
-                "  \"contact_prefs\": {\n" +
-                "    \"send_to_voicemail\": false,\n" +
-                "    \"starred\": false,\n" +
-                "    \"pinned\": 1\n" +
-                "  }\n" +
-                "  }";
-        ContentValues updatedValues = new ContentValues();
-        updatedValues.put(MetadataSync.RAW_CONTACT_BACKUP_ID, backupId);
-        updatedValues.put(MetadataSync.ACCOUNT_TYPE, TEST_ACCOUNT_TYPE1);
-        updatedValues.put(MetadataSync.ACCOUNT_NAME, TEST_ACCOUNT_NAME1);
-        updatedValues.put(MetadataSync.DATA_SET, TEST_DATA_SET1);
-        updatedValues.put(MetadataSync.DATA, updatedJson);
-        updatedValues.put(MetadataSync.DELETED, deleted);
-        mResolver.insert(MetadataSync.CONTENT_URI, updatedValues);
-
-        // Check if the insert (actually update) is correct.
-        assertStoredValue(rawContactUri, RawContacts.ACCOUNT_TYPE, TEST_ACCOUNT_TYPE1);
-        assertStoredValue(rawContactUri, RawContacts.ACCOUNT_NAME, TEST_ACCOUNT_NAME1);
-        assertStoredValue(rawContactUri, RawContacts.DATA_SET, TEST_DATA_SET1);
-        assertStoredValue(rawContactUri, RawContacts.SEND_TO_VOICEMAIL, "0");
-        assertStoredValue(rawContactUri, RawContacts.STARRED, "0");
-        assertStoredValue(rawContactUri, RawContacts.PINNED, "1");
-    }
-
-    public void testInsertMetadata() {
-        String backupId = "newBackupId";
-        String deleted = "0";
-        String insertJson = "{\n" +
-                "  \"unique_contact_id\": {\n" +
-                "    \"account_type\": \"CUSTOM_ACCOUNT\",\n" +
-                "    \"custom_account_type\": " + TEST_ACCOUNT_TYPE1 + ",\n" +
-                "    \"account_name\": " + TEST_ACCOUNT_NAME1 + ",\n" +
-                "    \"contact_id\": " + backupId + ",\n" +
-                "    \"data_set\": \"GOOGLE_PLUS\"\n" +
-                "  },\n" +
-                "  \"contact_prefs\": {\n" +
-                "    \"send_to_voicemail\": true,\n" +
-                "    \"starred\": true,\n" +
-                "    \"pinned\": 2\n" +
-                "  }\n" +
-                "  }";
-
-        // Insert to MetadataSync table.
-        ContentValues insertedValues = new ContentValues();
-        insertedValues.put(MetadataSync.RAW_CONTACT_BACKUP_ID, backupId);
-        insertedValues.put(MetadataSync.ACCOUNT_TYPE, TEST_ACCOUNT_TYPE1);
-        insertedValues.put(MetadataSync.ACCOUNT_NAME, TEST_ACCOUNT_NAME1);
-        insertedValues.put(MetadataSync.DATA_SET, TEST_DATA_SET1);
-        insertedValues.put(MetadataSync.DATA, insertJson);
-        insertedValues.put(MetadataSync.DELETED, deleted);
-        Uri metadataUri = mResolver.insert(MetadataSync.CONTENT_URI, insertedValues);
-
-        long metadataId = ContentUris.parseId(metadataUri);
-        assertEquals(true, metadataId > 0);
-    }
-
-    public void testFailUpdateDeletedMetadata() {
-        String backupId = "backupId001";
-        String newData = "{\n" +
-                "  \"unique_contact_id\": {\n" +
-                "    \"account_type\": \"CUSTOM_ACCOUNT\",\n" +
-                "    \"custom_account_type\": " + TEST_ACCOUNT_TYPE1 + ",\n" +
-                "    \"account_name\": " + TEST_ACCOUNT_NAME1 + ",\n" +
-                "    \"contact_id\": " + backupId + ",\n" +
-                "    \"data_set\": \"GOOGLE_PLUS\"\n" +
-                "  },\n" +
-                "  \"contact_prefs\": {\n" +
-                "    \"send_to_voicemail\": false,\n" +
-                "    \"starred\": false,\n" +
-                "    \"pinned\": 1\n" +
-                "  }\n" +
-                "  }";
-
-        ContentValues  newValues =  new ContentValues();
-        newValues.put(MetadataSync.ACCOUNT_NAME, TEST_ACCOUNT_NAME1);
-        newValues.put(MetadataSync.ACCOUNT_TYPE, TEST_ACCOUNT_TYPE1);
-        newValues.put(MetadataSync.DATA_SET, TEST_DATA_SET1);
-        newValues.put(MetadataSync.RAW_CONTACT_BACKUP_ID, backupId);
-        newValues.put(MetadataSync.DATA, newData);
-        newValues.put(MetadataSync.DELETED, 1);
-
-        try {
-            mResolver.insert(MetadataSync.CONTENT_URI, newValues);
-            fail("the update was expected to fail, but it succeeded");
-        } catch (IllegalArgumentException e) {
-            // Expected
-        }
-    }
-
-    public void testInsertWithNullData() {
-        ContentValues  newValues =  new ContentValues();
-        String data = null;
-        String backupId = "backupId002";
-        newValues.put(MetadataSync.ACCOUNT_NAME, TEST_ACCOUNT_NAME1);
-        newValues.put(MetadataSync.ACCOUNT_TYPE, TEST_ACCOUNT_TYPE1);
-        newValues.put(MetadataSync.DATA_SET, TEST_DATA_SET1);
-        newValues.put(MetadataSync.RAW_CONTACT_BACKUP_ID, backupId);
-        newValues.put(MetadataSync.DATA, data);
-        newValues.put(MetadataSync.DELETED, 0);
-
-        try {
-            mResolver.insert(MetadataSync.CONTENT_URI, newValues);
-        } catch (IllegalArgumentException e) {
-            // Expected.
-        }
-    }
-
-    public void testDeleteMetadata() {
-        //insert another metadata for TEST_ACCOUNT
-        insertMetadata(TEST_ACCOUNT_NAME1, TEST_ACCOUNT_TYPE1, TEST_DATA_SET1, "2", TEST_DATA1, 0);
-        Cursor c = mResolver.query(MetadataSync.CONTENT_URI, null, SELECTION_BY_TEST_ACCOUNT1,
-                null, null);
-        assertEquals(2, c.getCount());
-        int numOfDeletion = mResolver.delete(MetadataSync.CONTENT_URI, SELECTION_BY_TEST_ACCOUNT1,
-                null);
-        assertEquals(2, numOfDeletion);
-        c = mResolver.query(MetadataSync.CONTENT_URI, null, SELECTION_BY_TEST_ACCOUNT1,
-                null, null);
-        assertEquals(0, c.getCount());
-    }
-
-    public void testBulkInsert() {
-        Cursor c = mResolver.query(MetadataSync.CONTENT_URI, new String[]{MetadataSync._ID},
-                SELECTION_BY_TEST_ACCOUNT1, null, null);
-        assertEquals(1, c.getCount());
-
-        ContentValues values1 = getMetadataContentValues(
-                TEST_ACCOUNT_NAME1, TEST_ACCOUNT_TYPE1, TEST_DATA_SET1, "123", TEST_DATA1, 0);
-        ContentValues values2 = getMetadataContentValues(
-                TEST_ACCOUNT_NAME1, TEST_ACCOUNT_TYPE1, TEST_DATA_SET1, "456", TEST_DATA1, 0);
-        ContentValues[] values = new ContentValues[] {values1, values2};
-
-        mResolver.bulkInsert(MetadataSync.CONTENT_URI, values);
-        c = mResolver.query(MetadataSync.CONTENT_URI, new String[] {MetadataSync._ID},
-                SELECTION_BY_TEST_ACCOUNT1, null, null);
-        assertEquals(3, c.getCount());
-    }
-
-    public void testBatchOperations() throws Exception {
-        // Two mentadata_sync entries in the beginning, one for TEST_ACCOUNT1 and another for
-        // TEST_ACCOUNT2
-        Cursor c = mResolver.query(MetadataSync.CONTENT_URI, new String[] {MetadataSync._ID},
-                null, null, null);
-        assertEquals(2, c.getCount());
-
-        String updatedData = "{\n" +
-                "  \"unique_contact_id\": {\n" +
-                "    \"account_type\": \"CUSTOM_ACCOUNT\",\n" +
-                "    \"custom_account_type\": " + TEST_ACCOUNT_TYPE1 + ",\n" +
-                "    \"account_name\": " + TEST_ACCOUNT_NAME1 + ",\n" +
-                "    \"contact_id\": " + TEST_BACKUP_ID1 + ",\n" +
-                "    \"data_set\": \"GOOGLE_PLUS\"\n" +
-                "  },\n" +
-                "  \"contact_prefs\": {\n" +
-                "    \"send_to_voicemail\": true,\n" +
-                "    \"starred\": false,\n" +
-                "    \"pinned\": 5\n" +
-                "  }\n" +
-                "  }";
-
-        String newBackupId = "2222";
-        String newData = "{\n" +
-                "  \"unique_contact_id\": {\n" +
-                "    \"account_type\": \"CUSTOM_ACCOUNT\",\n" +
-                "    \"custom_account_type\": " + TEST_ACCOUNT_TYPE1 + ",\n" +
-                "    \"account_name\": " + TEST_ACCOUNT_NAME1 + ",\n" +
-                "    \"contact_id\": " + newBackupId + ",\n" +
-                "    \"data_set\": \"GOOGLE_PLUS\"\n" +
-                "  },\n" +
-                "  \"contact_prefs\": {\n" +
-                "    \"send_to_voicemail\": true,\n" +
-                "    \"starred\": false,\n" +
-                "    \"pinned\": 5\n" +
-                "  }\n" +
-                "  }";
-
-        ArrayList<ContentProviderOperation> ops = Lists.newArrayList();
-        ops.add(ContentProviderOperation.newInsert(MetadataSync.CONTENT_URI)
-                .withValue(MetadataSync.ACCOUNT_NAME, TEST_ACCOUNT_NAME1)
-                .withValue(MetadataSync.ACCOUNT_TYPE, TEST_ACCOUNT_TYPE1)
-                .withValue(MetadataSync.DATA_SET, TEST_DATA_SET1)
-                .withValue(MetadataSync.RAW_CONTACT_BACKUP_ID, TEST_BACKUP_ID1)
-                .withValue(MetadataSync.DATA, updatedData)
-                .withValue(MetadataSync.DELETED, 0)
-                .build());
-
-        ops.add(ContentProviderOperation.newInsert(MetadataSync.CONTENT_URI)
-                .withValue(MetadataSync.ACCOUNT_NAME, TEST_ACCOUNT_NAME1)
-                .withValue(MetadataSync.ACCOUNT_TYPE, TEST_ACCOUNT_TYPE1)
-                .withValue(MetadataSync.DATA_SET, TEST_DATA_SET1)
-                .withValue(MetadataSync.RAW_CONTACT_BACKUP_ID, newBackupId)
-                .withValue(MetadataSync.DATA, newData)
-                .withValue(MetadataSync.DELETED, 0)
-                .build());
-
-        ops.add(ContentProviderOperation.newDelete(MetadataSync.CONTENT_URI)
-                .withSelection(SELECTION_BY_TEST_ACCOUNT2, null)
-                .build());
-
-        // Batch three operations: update the metadata_entry of TEST_ACCOUNT1; insert one new
-        // metadata_entry for TEST_ACCOUNT1; delete metadata_entry of TEST_ACCOUNT2
-        mResolver.applyBatch(MetadataSync.METADATA_AUTHORITY, ops);
-
-        // After the batch operations, there should be two metadata_entry for TEST_ACCOUNT1 with
-        // new data value and no metadata_entry for TEST_ACCOUNT2.
-        c = mResolver.query(MetadataSync.CONTENT_URI, new String[] {MetadataSync.DATA},
-                SELECTION_BY_TEST_ACCOUNT1, null, null);
-        assertEquals(2, c.getCount());
-        Set<String> actualData = new HashSet<>();
-        while (c.moveToNext()) {
-            actualData.add(c.getString(0));
-        }
-        c.close();
-        MoreAsserts.assertContentsInAnyOrder(actualData, updatedData, newData);
-
-        c = mResolver.query(MetadataSync.CONTENT_URI, new String[] {MetadataSync._ID},
-                SELECTION_BY_TEST_ACCOUNT2, null, null);
-        assertEquals(0, c.getCount());
-    }
-
-    public void testQueryMetadataSyncState() {
-        String selection = MetadataSyncState.ACCOUNT_NAME + "=?1 AND " +
-                MetadataSyncState.ACCOUNT_TYPE + "=?2 AND " + MetadataSyncState.DATA_SET + "=?3";
-        final String[] args = new String[]{TEST_ACCOUNT_NAME1, TEST_ACCOUNT_TYPE1, TEST_DATA_SET1};
-        final String[] projection = new String[]{MetadataSyncState.STATE};
-        Cursor c = mResolver.query(MetadataSyncState.CONTENT_URI, projection, selection, args,
-                null);
-        assertEquals(1, c.getCount());
-        c.moveToFirst();
-        assertTrue(Arrays.equals(TEST_SYNC_STATE1, c.getBlob(0)));
-        c.close();
-    }
-
-    public void testUpdateMetadataSyncState() {
-        mResolver.update(MetadataSyncState.CONTENT_URI, getSyncStateValues(TEST_ACCOUNT_NAME1,
-                TEST_ACCOUNT_TYPE1, TEST_DATA_SET1, TEST_SYNC_STATE2), null, null);
-        String selection = MetadataSyncState.ACCOUNT_NAME + "=?1 AND " +
-                MetadataSyncState.ACCOUNT_TYPE + "=?2 AND " + MetadataSyncState.DATA_SET + "=?3";
-        final String[] args = new String[]{TEST_ACCOUNT_NAME1, TEST_ACCOUNT_TYPE1, TEST_DATA_SET1};
-        final String[] projection =  new String[] {MetadataSyncState.STATE};
-        Cursor c = mResolver.query(MetadataSyncState.CONTENT_URI, projection, selection, args,
-                null);
-
-        assertEquals(1, c.getCount());
-        c.moveToFirst();
-        assertTrue(Arrays.equals(TEST_SYNC_STATE2, c.getBlob(0)));
-        c.close();
-    }
-
-    public void testUpdateMetadataSyncState_NonExisting() {
-        // Delete the existing one first.
-        String selection = MetadataSyncState.ACCOUNT_NAME + "=?1 AND " +
-                MetadataSyncState.ACCOUNT_TYPE + "=?2 AND " + MetadataSyncState.DATA_SET + "=?3";
-        final String[] args = new String[]{TEST_ACCOUNT_NAME1, TEST_ACCOUNT_TYPE1, TEST_DATA_SET1};
-
-        mResolver.delete(MetadataSyncState.CONTENT_URI, selection, args);
-
-        mResolver.update(MetadataSyncState.CONTENT_URI, getSyncStateValues(TEST_ACCOUNT_NAME1,
-                TEST_ACCOUNT_TYPE1, TEST_DATA_SET1, TEST_SYNC_STATE2), null, null);
-        final String[] projection =  new String[] {MetadataSyncState.STATE};
-        Cursor c = mResolver.query(MetadataSyncState.CONTENT_URI, projection, selection, args,
-                null);
-
-        assertEquals(1, c.getCount());
-        c.moveToFirst();
-        assertTrue(Arrays.equals(TEST_SYNC_STATE2, c.getBlob(0)));
-        c.close();
-    }
-
-    public void testDeleteMetadataSyncState() {
-        String selection = MetadataSyncState.ACCOUNT_NAME + "=?1 AND " +
-                MetadataSyncState.ACCOUNT_TYPE + "=?2 AND " + MetadataSyncState.DATA_SET + "=?3";
-        final String[] args = new String[]{TEST_ACCOUNT_NAME1, TEST_ACCOUNT_TYPE1, TEST_DATA_SET1};
-        final String[] projection = new String[]{MetadataSyncState.STATE};
-        Cursor c = mResolver.query(MetadataSyncState.CONTENT_URI, projection, selection, args,
-                null);
-        assertEquals(1, c.getCount());
-        c.close();
-
-        mResolver.delete(MetadataSyncState.CONTENT_URI, selection, args);
-        c = mResolver.query(MetadataSyncState.CONTENT_URI, projection, selection, args,
-                null);
-        assertEquals(0, c.getCount());
-        c.close();
-    }
-
-    private void setupData() {
-        long rawContactId1 = RawContactUtil.createRawContactWithAccountDataSet(
-                mResolver, TEST_ACCOUNT_NAME1, TEST_ACCOUNT_TYPE1, TEST_DATA_SET1);
-        createAccount(TEST_ACCOUNT_NAME1, TEST_ACCOUNT_TYPE1, TEST_DATA_SET1);
-        insertMetadata(getDefaultValues());
-        insertMetadataSyncState(TEST_ACCOUNT_NAME1, TEST_ACCOUNT_TYPE1, TEST_DATA_SET1,
-                TEST_SYNC_STATE1);
-
-        // Insert another entry for another account
-        createAccount(TEST_ACCOUNT_NAME2, TEST_ACCOUNT_TYPE2, TEST_DATA_SET2);
-        insertMetadata(TEST_ACCOUNT_NAME2, TEST_ACCOUNT_TYPE2, TEST_DATA_SET2, TEST_BACKUP_ID2,
-                TEST_DATA2, 0);
-        insertMetadataSyncState(TEST_ACCOUNT_NAME2, TEST_ACCOUNT_TYPE2, TEST_DATA_SET2,
-                TEST_SYNC_STATE2);
-    }
-
-    private ContentValues getDefaultValues() {
-        defaultValues = new ContentValues();
-        defaultValues.put(MetadataSync.ACCOUNT_NAME, TEST_ACCOUNT_NAME1);
-        defaultValues.put(MetadataSync.ACCOUNT_TYPE, TEST_ACCOUNT_TYPE1);
-        defaultValues.put(MetadataSync.DATA_SET, TEST_DATA_SET1);
-        defaultValues.put(MetadataSync.RAW_CONTACT_BACKUP_ID, TEST_BACKUP_ID1);
-        defaultValues.put(MetadataSync.DATA, TEST_DATA1);
-        defaultValues.put(MetadataSync.DELETED, 0);
-        return defaultValues;
-    }
-
-    private long insertMetadata(String accountName, String accountType, String dataSet,
-            String backupId, String data, int deleted) {
-        return insertMetadata(getMetadataContentValues(
-                accountName, accountType, dataSet, backupId, data, deleted));
-    }
-
-    private ContentValues getMetadataContentValues(String accountName, String accountType,
-            String dataSet, String backupId, String data, int deleted) {
-        ContentValues values = new ContentValues();
-        values.put(MetadataSync.ACCOUNT_NAME, accountName);
-        values.put(MetadataSync.ACCOUNT_TYPE, accountType);
-        values.put(MetadataSync.DATA_SET, dataSet);
-        values.put(MetadataSync.RAW_CONTACT_BACKUP_ID, backupId);
-        values.put(MetadataSync.DATA, data);
-        values.put(MetadataSync.DELETED, deleted);
-        return values;
-    }
-
-    private long insertMetadata(ContentValues values) {
-        return ContentUris.parseId(mResolver.insert(MetadataSync.CONTENT_URI, values));
-    }
-
-    private long insertMetadataSyncState(String accountName, String accountType,
-            String dataSet, byte[] state) {
-        return ContentUris.parseId(mResolver.insert(MetadataSyncState.CONTENT_URI,
-                getSyncStateValues(accountName, accountType, dataSet, state)));
-    }
-
-    private ContentValues getSyncStateValues(String accountName, String accountType,
-            String dataSet, byte[] state) {
-        ContentValues values = new ContentValues();
-        values.put(MetadataSyncState.ACCOUNT_NAME, accountName);
-        values.put(MetadataSyncState.ACCOUNT_TYPE, accountType);
-        values.put(MetadataSyncState.DATA_SET, dataSet);
-        values.put(MetadataSyncState.STATE, state);
-        return values;
-    }
-}
diff --git a/tests/src/com/android/providers/contacts/ContactMetadataProviderTestable.java b/tests/src/com/android/providers/contacts/ContactMetadataProviderTestable.java
deleted file mode 100644
index c46643f..0000000
--- a/tests/src/com/android/providers/contacts/ContactMetadataProviderTestable.java
+++ /dev/null
@@ -1,23 +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;
-
-public class ContactMetadataProviderTestable extends ContactMetadataProvider {
-    @Override
-    void ensureCaller() {
-        // Not testable, skip.
-    }
-}
diff --git a/tests/src/com/android/providers/contacts/ContactsActor.java b/tests/src/com/android/providers/contacts/ContactsActor.java
index 804e79a..56a4fc4 100644
--- a/tests/src/com/android/providers/contacts/ContactsActor.java
+++ b/tests/src/com/android/providers/contacts/ContactsActor.java
@@ -248,6 +248,11 @@
         public boolean isUserUnlocked(int userId) {
             return true; // Just make it always unlocked for now.
         }
+
+        @Override
+        public boolean isUserRunning(int userId) {
+            return true;
+        }
     }
 
     private MockTelephonyManager mMockTelephonyManager;
diff --git a/tests/src/com/android/providers/contacts/ContactsDatabaseHelperUpgradeTest.java b/tests/src/com/android/providers/contacts/ContactsDatabaseHelperUpgradeTest.java
index 1832b4e..d50a292 100644
--- a/tests/src/com/android/providers/contacts/ContactsDatabaseHelperUpgradeTest.java
+++ b/tests/src/com/android/providers/contacts/ContactsDatabaseHelperUpgradeTest.java
@@ -30,8 +30,6 @@
 import android.provider.ContactsContract.Directory;
 import android.provider.ContactsContract.DisplayNameSources;
 import android.provider.ContactsContract.Groups;
-import android.provider.ContactsContract.MetadataSync;
-import android.provider.ContactsContract.MetadataSyncState;
 import android.provider.ContactsContract.PhotoFiles;
 import android.provider.ContactsContract.PinnedPositions;
 import android.provider.ContactsContract.RawContacts;
@@ -51,8 +49,6 @@
 import com.android.providers.contacts.ContactsDatabaseHelper.DataUsageStatColumns;
 import com.android.providers.contacts.ContactsDatabaseHelper.DirectoryColumns;
 import com.android.providers.contacts.ContactsDatabaseHelper.GroupsColumns;
-import com.android.providers.contacts.ContactsDatabaseHelper.MetadataSyncColumns;
-import com.android.providers.contacts.ContactsDatabaseHelper.MetadataSyncStateColumns;
 import com.android.providers.contacts.ContactsDatabaseHelper.MimetypesColumns;
 import com.android.providers.contacts.ContactsDatabaseHelper.NameLookupColumns;
 import com.android.providers.contacts.ContactsDatabaseHelper.NicknameLookupColumns;
@@ -63,7 +59,6 @@
 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.testutil.TestUtil;
 import com.android.providers.contacts.util.PropertyUtils;
 
 /**
@@ -225,6 +220,8 @@
             new TableColumn(AccountsColumns.ACCOUNT_NAME, TEXT, false, null),
             new TableColumn(AccountsColumns.ACCOUNT_TYPE, TEXT, false, null),
             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),
     };
 
     private static final TableColumn[] CONTACTS_COLUMNS = new TableColumn[] {
@@ -529,26 +526,12 @@
             new TableColumn(DataUsageStatColumns.LR_LAST_TIME_USED, INTEGER, true, "0"),
     };
 
-    private static final TableColumn[] METADATA_SYNC_COLUMNS = new TableColumn[] {
-            new TableColumn(MetadataSync._ID, INTEGER, false, null),
-            new TableColumn(MetadataSync.RAW_CONTACT_BACKUP_ID, TEXT, true, null),
-            new TableColumn(MetadataSyncColumns.ACCOUNT_ID, INTEGER, true, null),
-            new TableColumn(MetadataSync.DATA, TEXT, false, null),
-            new TableColumn(MetadataSync.DELETED, INTEGER, true, "0"),
-    };
-
     private static final TableColumn[] PRE_AUTHORIZED_URIS_COLUMNS = new TableColumn[] {
             new TableColumn(PreAuthorizedUris._ID, INTEGER, false, null),
             new TableColumn(PreAuthorizedUris.URI, STRING, true, null),
             new TableColumn(PreAuthorizedUris.EXPIRATION, INTEGER, true, "0"),
     };
 
-    private static final TableColumn[] METADATA_SYNC_STATE_COLUMNS = new TableColumn[] {
-            new TableColumn(MetadataSyncState._ID, INTEGER, false, null),
-            new TableColumn(MetadataSyncStateColumns.ACCOUNT_ID, INTEGER, true, null),
-            new TableColumn(MetadataSyncState.STATE, BLOB, false, null),
-    };
-
     private static final TableColumn[] PRESENCE_COLUMNS = new TableColumn[] {
             new TableColumn(StatusUpdates.DATA_ID, INTEGER, false, null),
             new TableColumn(StatusUpdates.PROTOCOL, INTEGER, true, null),
@@ -592,9 +575,7 @@
             new TableListEntry(Tables.STATUS_UPDATES, STATUS_UPDATES_COLUMNS),
             new TableListEntry(Tables.DIRECTORIES, DIRECTORIES_COLUMNS),
             new TableListEntry(Tables.DATA_USAGE_STAT, DATA_USAGE_STAT_COLUMNS),
-            new TableListEntry(Tables.METADATA_SYNC, METADATA_SYNC_COLUMNS),
             new TableListEntry(Tables.PRE_AUTHORIZED_URIS, PRE_AUTHORIZED_URIS_COLUMNS),
-            new TableListEntry(Tables.METADATA_SYNC_STATE, METADATA_SYNC_STATE_COLUMNS),
             new TableListEntry(Tables.PRESENCE, PRESENCE_COLUMNS),
             new TableListEntry(Tables.AGGREGATED_PRESENCE, AGGREGATED_PRESENCE_COLUMNS)
     };
diff --git a/tests/src/com/android/providers/contacts/ContactsProvider2Test.java b/tests/src/com/android/providers/contacts/ContactsProvider2Test.java
index d5643d2..7efc2f4 100644
--- a/tests/src/com/android/providers/contacts/ContactsProvider2Test.java
+++ b/tests/src/com/android/providers/contacts/ContactsProvider2Test.java
@@ -55,8 +55,6 @@
 import android.provider.ContactsContract.DisplayPhoto;
 import android.provider.ContactsContract.FullNameStyle;
 import android.provider.ContactsContract.Groups;
-import android.provider.ContactsContract.MetadataSync;
-import android.provider.ContactsContract.MetadataSyncState;
 import android.provider.ContactsContract.PhoneLookup;
 import android.provider.ContactsContract.PhoneticNameStyle;
 import android.provider.ContactsContract.PinnedPositions;
@@ -86,11 +84,6 @@
 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.MetadataEntryParser.AggregationData;
-import com.android.providers.contacts.MetadataEntryParser.FieldData;
-import com.android.providers.contacts.MetadataEntryParser.MetadataEntry;
-import com.android.providers.contacts.MetadataEntryParser.RawContactInfo;
-import com.android.providers.contacts.MetadataEntryParser.UsageStats;
 import com.android.providers.contacts.testutil.CommonDatabaseUtils;
 import com.android.providers.contacts.testutil.ContactUtil;
 import com.android.providers.contacts.testutil.DataUtil;
@@ -1685,9 +1678,9 @@
         assertStoredValues(lookupUri1, null, null, new ContentValues[] {values, values});
 
         // In the context that 8004664411 is a valid number, "4664411" as a
-        // call id should  match to both "8004664411" and "+18004664411".
+        // call id should not match to either "8004664411" or "+18004664411".
         Uri lookupUri2 = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, "4664411");
-        assertEquals(2, getCount(lookupUri2, null, null));
+        assertEquals(0, getCount(lookupUri2, null, null));
 
         // A wrong area code 799 vs 800 should not be matched
         lookupUri2 = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, "7994664411");
@@ -1918,13 +1911,13 @@
 
         values.clear();
 
-        // match with international format
+        // No match with international format
         lookupUri2 = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, "+1 650 861 0002");
-        assertEquals(1, getCount(lookupUri2, null, null));
+        assertEquals(0, getCount(lookupUri2, null, null));
 
-        // match with national format
+        // No match with national format
         lookupUri2 = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, "650 861 0002");
-        assertEquals(1, getCount(lookupUri2, null, null));
+        assertEquals(0, getCount(lookupUri2, null, null));
     }
 
     public void testIntlPhoneLookupUseCases() {
@@ -1943,20 +1936,20 @@
         assertEquals(2, getCount(Uri.withAppendedPath(
                 PhoneLookup.CONTENT_FILTER_URI, fullNumber), null, null));
 
-        // Shorter (local) number with 0 prefix should also match.
-        assertEquals(2, getCount(Uri.withAppendedPath(
+        // Shorter (local) number with 0 prefix should not match.
+        assertEquals(0, getCount(Uri.withAppendedPath(
                 PhoneLookup.CONTENT_FILTER_URI, "097427289"), null, null));
 
         // Number with international (+972) prefix should also match.
         assertEquals(1, getCount(Uri.withAppendedPath(
                 PhoneLookup.CONTENT_FILTER_URI, "+97297427289"), null, null));
 
-        // Same shorter number with dashes should match.
-        assertEquals(2, getCount(Uri.withAppendedPath(
+        // Same shorter number with dashes should not match.
+        assertEquals(0, getCount(Uri.withAppendedPath(
                 PhoneLookup.CONTENT_FILTER_URI, "09-742-7289"), null, null));
 
-        // Same shorter number with spaces should match.
-        assertEquals(2, getCount(Uri.withAppendedPath(
+        // Same shorter number with spaces should not match.
+        assertEquals(0, getCount(Uri.withAppendedPath(
                 PhoneLookup.CONTENT_FILTER_URI, "09 742 7289"), null, null));
 
         // Some other number should not match.
@@ -1978,16 +1971,16 @@
         assertEquals(1, getCount(Uri.withAppendedPath(
                 PhoneLookup.CONTENT_FILTER_URI, "0796010101"), null, null));
 
-        assertEquals(1, getCount(Uri.withAppendedPath(
+        assertEquals(0, getCount(Uri.withAppendedPath(
                 PhoneLookup.CONTENT_FILTER_URI, "+48796010101"), null, null));
 
-        assertEquals(1, getCount(Uri.withAppendedPath(
+        assertEquals(0, getCount(Uri.withAppendedPath(
                 PhoneLookup.CONTENT_FILTER_URI, "48796010101"), null, null));
 
-        assertEquals(1, getCount(Uri.withAppendedPath(
+        assertEquals(0, getCount(Uri.withAppendedPath(
                 PhoneLookup.CONTENT_FILTER_URI, "4-879-601-0101"), null, null));
 
-        assertEquals(1, getCount(Uri.withAppendedPath(
+        assertEquals(0, getCount(Uri.withAppendedPath(
                 PhoneLookup.CONTENT_FILTER_URI, "4 879 601 0101"), null, null));
     }
 
@@ -2894,333 +2887,6 @@
         }
     }
 
-    public void testUpdateFromMetadataEntry() {
-        String accountType1 = "accountType1";
-        String accountName1 = "accountName1";
-        String dataSet1 = "plus";
-        Account account1 = new Account(accountName1, accountType1);
-        long rawContactId = RawContactUtil.createRawContactWithName(mResolver, account1);
-        Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
-        // Add backup_id for the raw contact.
-        String backupId = "backupId100001";
-        ContentValues values = new ContentValues();
-        values.put(RawContacts.BACKUP_ID, backupId);
-        assertEquals(1, mResolver.update(rawContactUri, values, null, null));
-
-        String emailAddress = "address@email.com";
-        Uri dataUri = insertEmail(rawContactId, emailAddress);
-        String hashId = getStoredValue(dataUri, Data.HASH_ID);
-
-        // Another data that should not be updated.
-        String phoneNumber = "111-111-1111";
-        Uri dataUri2 = insertPhoneNumber(rawContactId, phoneNumber);
-
-        // Aggregation should be deleted from local since it doesn't exist in server.
-        long toBeDeletedAggRawContactId = RawContactUtil.createRawContactWithName(
-                mResolver, account1);
-        setAggregationException(AggregationExceptions.TYPE_KEEP_SEPARATE,
-                rawContactId, toBeDeletedAggRawContactId);
-
-        // Check if AggregationException table has one value.
-        assertStoredValue(AggregationExceptions.CONTENT_URI, AggregationExceptions.RAW_CONTACT_ID1,
-                rawContactId);
-        assertStoredValue(AggregationExceptions.CONTENT_URI, AggregationExceptions.RAW_CONTACT_ID2,
-                toBeDeletedAggRawContactId);
-        assertStoredValue(AggregationExceptions.CONTENT_URI, AggregationExceptions.TYPE,
-                AggregationExceptions.TYPE_KEEP_SEPARATE);
-
-        String accountType2 = "accountType2";
-        String accountName2 = "accountName2";
-        Account account2 = new Account(accountName2, accountType2);
-        long rawContactId2 = RawContactUtil.createRawContactWithName(mResolver, account2);
-        Uri rawContactUri2 = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId2);
-        String backupId2 = "backupId100003";
-        ContentValues values2 = new ContentValues();
-        values2.put(RawContacts.BACKUP_ID, backupId2);
-        assertEquals(1, mResolver.update(rawContactUri2, values2, null, null));
-
-        String usageTypeString = "CALL";
-        int lastTimeUsed = 1111111;
-        int timesUsed = 5;
-        String aggregationTypeString = "TOGETHER";
-        int aggregationType = AggregationExceptions.TYPE_KEEP_TOGETHER;
-
-        RawContactInfo rawContactInfo = new RawContactInfo(
-                backupId, accountType1, accountName1, null);
-        UsageStats usageStats = new UsageStats(usageTypeString, lastTimeUsed, timesUsed);
-        ArrayList<UsageStats> usageStatsList = new ArrayList<>();
-        usageStatsList.add(usageStats);
-        FieldData fieldData = new FieldData(hashId, true, true, usageStatsList);
-        ArrayList<FieldData> fieldDataList = new ArrayList<>();
-        fieldDataList.add(fieldData);
-        ArrayList<AggregationData> aggregationDataList = new ArrayList<>();
-        MetadataEntry metadataEntry = new MetadataEntry(rawContactInfo,
-                1, 1, 1, fieldDataList, aggregationDataList);
-
-        ContactsProvider2 provider = (ContactsProvider2) getProvider();
-        final ContactsDatabaseHelper helper =
-                ((ContactsDatabaseHelper) provider.getDatabaseHelper());
-        SQLiteDatabase db = helper.getWritableDatabase();
-
-        // Before updating tables from MetadataEntry.
-        assertStoredValue(rawContactUri, RawContacts.ACCOUNT_TYPE, accountType1);
-        assertStoredValue(rawContactUri, RawContacts.ACCOUNT_NAME, accountName1);
-        assertStoredValue(rawContactUri, RawContacts.SEND_TO_VOICEMAIL, "0");
-        assertStoredValue(rawContactUri, RawContacts.STARRED, "0");
-        assertStoredValue(rawContactUri, RawContacts.PINNED, "0");
-        assertStoredValue(dataUri, Data.IS_PRIMARY, 0);
-        assertStoredValue(dataUri, Data.IS_SUPER_PRIMARY, 0);
-
-        // Update tables without aggregation first, since aggregator will affect pinned value.
-        provider.updateFromMetaDataEntry(db, metadataEntry);
-
-        // After updating tables from MetadataEntry.
-        assertStoredValue(rawContactUri, RawContacts.ACCOUNT_TYPE, accountType1);
-        assertStoredValue(rawContactUri, RawContacts.ACCOUNT_NAME, accountName1);
-        assertStoredValue(rawContactUri, RawContacts.SEND_TO_VOICEMAIL, "1");
-        assertStoredValue(rawContactUri, RawContacts.STARRED, "1");
-        assertStoredValue(rawContactUri, RawContacts.PINNED, "1");
-        assertStoredValue(dataUri, Data.IS_PRIMARY, 1);
-        assertStoredValue(dataUri, Data.IS_SUPER_PRIMARY, 1);
-        assertStoredValue(dataUri2, Data.IS_PRIMARY, 0);
-        assertStoredValue(dataUri2, Data.IS_SUPER_PRIMARY, 0);
-        final Uri dataUriWithUsageType = Data.CONTENT_URI.buildUpon().appendQueryParameter(
-                DataUsageFeedback.USAGE_TYPE, usageTypeString).build();
-        assertDataUsageZero(dataUriWithUsageType, emailAddress);
-
-        // Update AggregationException table.
-        RawContactInfo aggregationContact = new RawContactInfo(
-                backupId2, accountType2, accountName2, null);
-        AggregationData aggregationData = new AggregationData(
-                rawContactInfo, aggregationContact, aggregationTypeString);
-        aggregationDataList.add(aggregationData);
-        metadataEntry = new MetadataEntry(rawContactInfo,
-                1, 1, 1, fieldDataList, aggregationDataList);
-        provider.updateFromMetaDataEntry(db, metadataEntry);
-
-        // Check if AggregationException table is updated.
-        assertStoredValue(AggregationExceptions.CONTENT_URI, AggregationExceptions.RAW_CONTACT_ID1,
-                rawContactId);
-        assertStoredValue(AggregationExceptions.CONTENT_URI, AggregationExceptions.RAW_CONTACT_ID2,
-                rawContactId2);
-        assertStoredValue(AggregationExceptions.CONTENT_URI, AggregationExceptions.TYPE,
-                aggregationType);
-
-        // After aggregation, check if rawContacts.starred/send_to_voicemail
-        // were copied to contacts table.
-        final long contactId = queryContactId(rawContactId);
-        Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
-        // The merged contact should be starred if any of the rawcontact is starred.
-        assertStoredValue(contactUri, Contacts.STARRED, 1);
-        // The merged contact should be send_to_voicemail
-        // if all of the rawcontact is send_to_voicemail.
-        assertStoredValue(contactUri, Contacts.SEND_TO_VOICEMAIL, 0);
-    }
-
-    public void testUpdateMetadataOnRawContactInsert() throws Exception {
-        ContactMetadataProvider contactMetadataProvider = addProvider(
-                ContactMetadataProviderTestable.class, MetadataSync.METADATA_AUTHORITY);
-        // Reset the dbHelper to be the one ContactsProvider2 is using. Before this, two providers
-        // are using different dbHelpers.
-        contactMetadataProvider.setDatabaseHelper(((SynchronousContactsProvider2)
-                mActor.provider).getDatabaseHelper());
-        // Create an account first.
-        String backupId = "backupId001";
-        String accountType = "accountType";
-        String accountName = "accountName";
-        Account account = new Account(accountName, accountType);
-        createAccount(accountName, accountType, null);
-
-        // Insert a metadata to MetadataSync table.
-        String data = "{\n" +
-                "  \"unique_contact_id\": {\n" +
-                "    \"account_type\": \"CUSTOM_ACCOUNT\",\n" +
-                "    \"custom_account_type\": " + accountType + ",\n" +
-                "    \"account_name\": " + accountName + ",\n" +
-                "    \"contact_id\": " + backupId + ",\n" +
-                "    \"data_set\": \"FOCUS\"\n" +
-                "  },\n" +
-                "  \"contact_prefs\": {\n" +
-                "    \"send_to_voicemail\": true,\n" +
-                "    \"starred\": true,\n" +
-                "    \"pinned\": 1\n" +
-                "  }\n" +
-                "  }";
-
-        ContentValues insertedValues = new ContentValues();
-        insertedValues.put(MetadataSync.RAW_CONTACT_BACKUP_ID, backupId);
-        insertedValues.put(MetadataSync.ACCOUNT_TYPE, accountType);
-        insertedValues.put(MetadataSync.ACCOUNT_NAME, accountName);
-        insertedValues.put(MetadataSync.DATA, data);
-        mResolver.insert(MetadataSync.CONTENT_URI, insertedValues);
-
-        // Enable metadataSync flag.
-        final ContactsProvider2 cp = (ContactsProvider2) getProvider();
-        cp.setMetadataSyncForTest(true);
-        // Insert a raw contact.
-        long rawContactId = RawContactUtil.createRawContactWithBackupId(mResolver, backupId,
-                account);
-        Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
-        // 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, "0");
-        assertStoredValue(rawContactUri, RawContacts.STARRED, "0");
-        assertStoredValue(rawContactUri, RawContacts.PINNED, "0");
-        // No metadata network notify.
-        assertMetadataNetworkNotified(false);
-    }
-
-    public void testUpdateMetadataOnRawContactBackupIdChange() throws Exception {
-        ContactMetadataProvider contactMetadataProvider = addProvider(
-                ContactMetadataProviderTestable.class, MetadataSync.METADATA_AUTHORITY);
-        // Reset the dbHelper to be the one ContactsProvider2 is using. Before this, two providers
-        // are using different dbHelpers.
-        contactMetadataProvider.setDatabaseHelper(((SynchronousContactsProvider2)
-                mActor.provider).getDatabaseHelper());
-        // Create an account first.
-        String backupId = "backupId001";
-        String accountType = "accountType";
-        String accountName = "accountName";
-        Account account = new Account(accountName, accountType);
-        createAccount(accountName, accountType, null);
-
-        // Insert a metadata to MetadataSync table.
-        String data = "{\n" +
-                "  \"unique_contact_id\": {\n" +
-                "    \"account_type\": \"CUSTOM_ACCOUNT\",\n" +
-                "    \"custom_account_type\": " + accountType + ",\n" +
-                "    \"account_name\": " + accountName + ",\n" +
-                "    \"contact_id\": " + backupId + ",\n" +
-                "    \"data_set\": \"FOCUS\"\n" +
-                "  },\n" +
-                "  \"contact_prefs\": {\n" +
-                "    \"send_to_voicemail\": true,\n" +
-                "    \"starred\": true,\n" +
-                "    \"pinned\": 1\n" +
-                "  }\n" +
-                "  }";
-
-        ContentValues insertedValues = new ContentValues();
-        insertedValues.put(MetadataSync.RAW_CONTACT_BACKUP_ID, backupId);
-        insertedValues.put(MetadataSync.ACCOUNT_TYPE, accountType);
-        insertedValues.put(MetadataSync.ACCOUNT_NAME, accountName);
-        insertedValues.put(MetadataSync.DATA, data);
-        mResolver.insert(MetadataSync.CONTENT_URI, insertedValues);
-
-        // Enable metadataSync flag.
-        final ContactsProvider2 cp = (ContactsProvider2) getProvider();
-        cp.setMetadataSyncForTest(true);
-        // Insert a raw contact without backup_id.
-        long rawContactId = RawContactUtil.createRawContact(mResolver, account);
-        Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
-        // Check if the raw contact is not updated because of no backup_id.
-        assertStoredValue(rawContactUri, RawContacts._ID, rawContactId);
-        assertStoredValue(rawContactUri, RawContacts.ACCOUNT_TYPE, accountType);
-        assertStoredValue(rawContactUri, RawContacts.ACCOUNT_NAME, accountName);
-        assertStoredValue(rawContactUri, RawContacts.SEND_TO_VOICEMAIL, "0");
-        assertStoredValue(rawContactUri, RawContacts.STARRED, "0");
-        assertStoredValue(rawContactUri, RawContacts.PINNED, "0");
-
-        // Update the raw contact with backup_id.
-        ContentValues updatedValues = new ContentValues();
-        updatedValues.put(RawContacts.BACKUP_ID, backupId);
-        mResolver.update(RawContacts.CONTENT_URI, updatedValues, null, null);
-        // 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, "0");
-        assertStoredValue(rawContactUri, RawContacts.STARRED, "0");
-        assertStoredValue(rawContactUri, RawContacts.PINNED, "0");
-        // No metadata network notify.
-        assertMetadataNetworkNotified(false);
-    }
-
-    public void testDeleteMetadataOnRawContactDelete() throws Exception {
-        ContactMetadataProvider contactMetadataProvider = addProvider(
-                ContactMetadataProviderTestable.class, MetadataSync.METADATA_AUTHORITY);
-        // Reset the dbHelper to be the one ContactsProvider2 is using. Before this, two providers
-        // are using different dbHelpers.
-        contactMetadataProvider.setDatabaseHelper(((SynchronousContactsProvider2)
-                mActor.provider).getDatabaseHelper());
-        // Enable metadataSync flag.
-        final ContactsProvider2 cp = (ContactsProvider2) getProvider();
-        cp.setMetadataSyncForTest(true);
-        // Create an account first.
-        String backupId = "backupId001";
-        String accountType = "accountType";
-        String accountName = "accountName";
-        Account account = new Account(accountName, accountType);
-        createAccount(accountName, accountType, null);
-
-        // Insert a raw contact.
-        long rawContactId = RawContactUtil.createRawContactWithBackupId(mResolver, backupId,
-                account);
-        Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
-
-        // Insert a metadata to MetadataSync table.
-        String data = "{\n" +
-                "  \"unique_contact_id\": {\n" +
-                "    \"account_type\": \"CUSTOM_ACCOUNT\",\n" +
-                "    \"custom_account_type\": " + accountType + ",\n" +
-                "    \"account_name\": " + accountName + ",\n" +
-                "    \"contact_id\": " + backupId + ",\n" +
-                "    \"data_set\": \"FOCUS\"\n" +
-                "  },\n" +
-                "  \"contact_prefs\": {\n" +
-                "    \"send_to_voicemail\": true,\n" +
-                "    \"starred\": true,\n" +
-                "    \"pinned\": 1\n" +
-                "  }\n" +
-                "  }";
-
-        ContentValues insertedValues = new ContentValues();
-        insertedValues.put(MetadataSync.RAW_CONTACT_BACKUP_ID, backupId);
-        insertedValues.put(MetadataSync.ACCOUNT_TYPE, accountType);
-        insertedValues.put(MetadataSync.ACCOUNT_NAME, accountName);
-        insertedValues.put(MetadataSync.DATA, data);
-        Uri metadataUri = mResolver.insert(MetadataSync.CONTENT_URI, insertedValues);
-
-        // Delete raw contact.
-        mResolver.delete(rawContactUri, null, null);
-        // 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);
-        // Lychee removed. Will not notify it.
-        assertMetadataNetworkNotified(false);
-
-        // Add another rawcontact and metadata, and don't delete them.
-        // Insert a raw contact.
-        String backupId2 = "newBackupId";
-        long rawContactId2 = RawContactUtil.createRawContactWithBackupId(mResolver, backupId2,
-                account);
-        Uri rawContactUri2 = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
-
-        // Insert a metadata to MetadataSync table.
-        ContentValues insertedValues2 = new ContentValues();
-        insertedValues2.put(MetadataSync.RAW_CONTACT_BACKUP_ID, backupId2);
-        insertedValues2.put(MetadataSync.ACCOUNT_TYPE, accountType);
-        insertedValues2.put(MetadataSync.ACCOUNT_NAME, accountName);
-        insertedValues2.put(MetadataSync.DATA, data);
-        Uri metadataUri2 = mResolver.insert(MetadataSync.CONTENT_URI, insertedValues2);
-
-        // Update raw contact but not delete.
-        ContentValues values = new ContentValues();
-        values.put(RawContacts.STARRED, "1");
-        mResolver.update(rawContactUri2, values, null, null);
-
-        // Check if the metadata is not marked as deleted.
-        assertStoredValue(metadataUri2, MetadataSync.DELETED, "0");
-        // 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() {
         long rawContactId = RawContactUtil.createRawContactWithName(mResolver, "Alice", "Nextore");
         Uri dataUri = insertPostalAddress(rawContactId, "1600 Amphiteatre Ave, Mountain View");
@@ -5197,10 +4863,6 @@
     }
 
     public void testSetSendToVoicemailAndRingtone() {
-        // Enable metadataSync flag.
-        final ContactsProvider2 cp = (ContactsProvider2) getProvider();
-        cp.setMetadataSyncForTest(true);
-
         long rawContactId = RawContactUtil.createRawContactWithName(mResolver);
         Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
         assertDirty(rawContactUri, true);
@@ -5210,14 +4872,12 @@
         updateSendToVoicemailAndRingtone(contactId, true, "foo");
         assertSendToVoicemailAndRingtone(contactId, true, "foo");
         assertNetworkNotified(false);
-        assertMetadataNetworkNotified(false);
         assertDirty(rawContactUri, false);
         assertMetadataDirty(rawContactUri, false);
 
         updateSendToVoicemailAndRingtoneWithSelection(contactId, false, "bar");
         assertSendToVoicemailAndRingtone(contactId, false, "bar");
         assertNetworkNotified(false);
-        assertMetadataNetworkNotified(false);
         assertDirty(rawContactUri, false);
         assertMetadataDirty(rawContactUri, false);
     }
@@ -5278,10 +4938,6 @@
     }
 
     public void testMarkMetadataDirtyAfterAggregation() {
-        // Enable metadataSync flag.
-        final ContactsProvider2 cp = (ContactsProvider2) getProvider();
-        cp.setMetadataSyncForTest(true);
-
         long rawContactId1 = RawContactUtil.createRawContactWithName(mResolver, "i", "j");
         long rawContactId2 = RawContactUtil.createRawContactWithName(mResolver, "k", "l");
         Uri rawContactUri1 = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId1);
@@ -5300,7 +4956,6 @@
         assertMetadataDirty(rawContactUri1, false);
         assertMetadataDirty(rawContactUri2, false);
         assertNetworkNotified(false);
-        assertMetadataNetworkNotified(false);
     }
 
     public void testStatusUpdateInsert() {
@@ -6766,98 +6421,6 @@
         assertStoredValue(safeStreamItemPhotoUri, StreamItemPhotos._ID, safeStreamItemPhotoId);
     }
 
-    public void testMetadataSyncCleanedUpOnAccountRemoval() throws Exception {
-        Account doomedAccount = new Account("doom", "doom");
-        createAccount(doomedAccount.name, doomedAccount.type, null);
-        Account safeAccount = new Account("safe", "safe");
-        createAccount(safeAccount.name, safeAccount.type, null);
-        ContactsProvider2 cp = (ContactsProvider2) getProvider();
-        mActor.setAccounts(new Account[]{doomedAccount, safeAccount});
-        cp.onAccountsUpdated(new Account[]{doomedAccount, safeAccount});
-
-        ContactMetadataProvider contactMetadataProvider = addProvider(
-                ContactMetadataProviderTestable.class, MetadataSync.METADATA_AUTHORITY);
-        // Reset the dbHelper to be the one ContactsProvider2 is using. Before this, two providers
-        // are using different dbHelpers.
-        contactMetadataProvider.setDatabaseHelper(((SynchronousContactsProvider2)
-                mActor.provider).getDatabaseHelper());
-
-        // Create a doomed metadata.
-        String backupId = "backupIdForDoomed";
-        ContentValues metadataValues = new ContentValues();
-        metadataValues.put(MetadataSync.RAW_CONTACT_BACKUP_ID, backupId);
-        metadataValues.put(MetadataSync.ACCOUNT_TYPE, doomedAccount.type);
-        metadataValues.put(MetadataSync.ACCOUNT_NAME, doomedAccount.name);
-        metadataValues.put(MetadataSync.DATA,
-                getDefaultMetadataJSONString(doomedAccount.type, doomedAccount.name, backupId));
-        Uri doomedMetadataUri = mResolver.insert(MetadataSync.CONTENT_URI, metadataValues);
-        // Create a doomed metadata sync state.
-        ContentValues syncStateValues = new ContentValues();
-        syncStateValues.put(MetadataSyncState.ACCOUNT_TYPE, doomedAccount.type);
-        syncStateValues.put(MetadataSyncState.ACCOUNT_NAME, doomedAccount.name);
-        syncStateValues.put(MetadataSyncState.STATE, "syncState");
-        mResolver.insert(MetadataSyncState.CONTENT_URI, syncStateValues);
-
-        // Create a safe metadata.
-        String backupId2 = "backupIdForSafe";
-        ContentValues insertedValues2 = new ContentValues();
-        insertedValues2.put(MetadataSync.RAW_CONTACT_BACKUP_ID, backupId2);
-        insertedValues2.put(MetadataSync.ACCOUNT_TYPE, safeAccount.type);
-        insertedValues2.put(MetadataSync.ACCOUNT_NAME, safeAccount.name);
-        insertedValues2.put(MetadataSync.DATA,
-                getDefaultMetadataJSONString(safeAccount.type, safeAccount.name, backupId2));
-        Uri safeMetadataUri = mResolver.insert(MetadataSync.CONTENT_URI, insertedValues2);
-        // Create a safe metadata sync state.
-        ContentValues syncStateValues2 = new ContentValues();
-        syncStateValues2.put(MetadataSyncState.ACCOUNT_TYPE, safeAccount.type);
-        syncStateValues2.put(MetadataSyncState.ACCOUNT_NAME, safeAccount.name);
-        syncStateValues2.put(MetadataSyncState.STATE, "syncState2");
-        mResolver.insert(MetadataSyncState.CONTENT_URI, syncStateValues2);
-
-        // Remove the doomed account.
-        mActor.setAccounts(new Account[]{safeAccount});
-        cp.onAccountsUpdated(new Account[]{safeAccount});
-
-        // Check that the doomed stuff has all been nuked.
-        ContentValues[] noValues = new ContentValues[0];
-        assertStoredValues(doomedMetadataUri, noValues);
-        String selection = MetadataSyncState.ACCOUNT_NAME + "=?1 AND "
-                + MetadataSyncState.ACCOUNT_TYPE + "=?2";
-        String[] args = new String[]{doomedAccount.name, doomedAccount.type};
-        final String[] projection = new String[]{MetadataSyncState.STATE};
-        Cursor c = mResolver.query(MetadataSyncState.CONTENT_URI, projection, selection, args,
-                null);
-        assertEquals(0, c.getCount());
-
-        // Check that the safe stuff lives on.
-        assertStoredValue(safeMetadataUri, MetadataSync.RAW_CONTACT_BACKUP_ID, backupId2);
-        args = new String[]{safeAccount.name, safeAccount.type};
-        c = mResolver.query(MetadataSyncState.CONTENT_URI, projection, selection, args,
-                null);
-        assertEquals(1, c.getCount());
-        c.moveToNext();
-        assertEquals("syncState2", c.getString(0));
-        c.close();
-    }
-
-    private String getDefaultMetadataJSONString(
-            String accountType, String accountName, String backupId) {
-        return "{\n" +
-                "  \"unique_contact_id\": {\n" +
-                "    \"account_type\": \"CUSTOM_ACCOUNT\",\n" +
-                "    \"custom_account_type\": " + accountType + ",\n" +
-                "    \"account_name\": " + accountName + ",\n" +
-                "    \"contact_id\": " + backupId + ",\n" +
-                "    \"data_set\": \"FOCUS\"\n" +
-                "  },\n" +
-                "  \"contact_prefs\": {\n" +
-                "    \"send_to_voicemail\": true,\n" +
-                "    \"starred\": true,\n" +
-                "    \"pinned\": 1\n" +
-                "  }\n" +
-                "  }";
-    }
-
     public void testContactDeletion() {
         long rawContactId1 = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe",
                 TestUtil.ACCOUNT_1);
@@ -6891,35 +6454,15 @@
     }
 
     public void testDirtyWhenRawContactInsert() {
-        // Enable metadataSync flag.
-        final ContactsProvider2 cp = (ContactsProvider2) getProvider();
-        cp.setMetadataSyncForTest(true);
-
-        // When inserting a rawcontact without metadata.
+        // When inserting a rawcontact.
         long rawContactId = RawContactUtil.createRawContact(mResolver, mAccount);
         Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
         assertDirty(rawContactUri, false);
         assertMetadataDirty(rawContactUri, false);
         assertNetworkNotified(true);
-        assertMetadataNetworkNotified(false);
-
-        // When inserting a rawcontact with metadata.
-        ContentValues values = new ContentValues();
-        values.put(ContactsContract.RawContacts.STARRED, 1);
-        values.put(ContactsContract.RawContacts.ACCOUNT_NAME, mAccount.name);
-        values.put(ContactsContract.RawContacts.ACCOUNT_TYPE, mAccount.type);
-        Uri rawContactId2Uri = mResolver.insert(RawContacts.CONTENT_URI, values);
-        assertDirty(rawContactId2Uri, false);
-        assertMetadataDirty(rawContactId2Uri, false);
-        assertNetworkNotified(true);
-        assertMetadataNetworkNotified(false);
     }
 
     public void testRawContactDirtyAndVersion() {
-        // Enable metadataSync flag.
-        final ContactsProvider2 cp = (ContactsProvider2) getProvider();
-        cp.setMetadataSyncForTest(true);
-
         final long rawContactId = RawContactUtil.createRawContact(mResolver, mAccount);
         Uri uri = ContentUris.withAppendedId(ContactsContract.RawContacts.CONTENT_URI, rawContactId);
         assertDirty(uri, false);
@@ -6933,9 +6476,8 @@
         assertEquals(version, getVersion(uri));
 
         assertDirty(uri, false);
-        assertNetworkNotified(false);
         assertMetadataDirty(uri, false);
-        assertMetadataNetworkNotified(false);
+        assertNetworkNotified(false);
 
         Uri emailUri = insertEmail(rawContactId, "goo@woo.com");
         assertDirty(uri, true);
@@ -6992,10 +6534,6 @@
     }
 
     public void testNotifyMetadataChangeForRawContactInsertBySyncAdapter() {
-        // Enable metadataSync flag.
-        final ContactsProvider2 cp = (ContactsProvider2) getProvider();
-        cp.setMetadataSyncForTest(true);
-
         Uri uri = RawContacts.CONTENT_URI.buildUpon()
                 .appendQueryParameter(RawContacts.ACCOUNT_NAME, mAccount.name)
                 .appendQueryParameter(RawContacts.ACCOUNT_TYPE, mAccount.type)
@@ -7005,15 +6543,9 @@
         long rawContactId = ContentUris.parseId(mResolver.insert(uri, new ContentValues()));
         Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
         assertMetadataDirty(rawContactUri, false);
-        // Will not notify Lychee since it's removed.
-        assertMetadataNetworkNotified(false);
     }
 
     public void testMarkAsMetadataDirtyForRawContactMetadataChange() {
-        // Enable metadataSync flag.
-        final ContactsProvider2 cp = (ContactsProvider2) getProvider();
-        cp.setMetadataSyncForTest(true);
-
         long rawContactId = RawContactUtil.createRawContact(mResolver, mAccount);
         long contactId = queryContactId(rawContactId);
         Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
@@ -7025,7 +6557,6 @@
 
         Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
         assertMetadataDirty(rawContactUri, false);
-        assertMetadataNetworkNotified(false);
 
         clearMetadataDirty(rawContactUri);
         values = new ContentValues();
@@ -7034,7 +6565,6 @@
         assertStoredValue(contactUri, Contacts.PINNED, 1);
 
         assertMetadataDirty(rawContactUri, false);
-        assertMetadataNetworkNotified(false);
 
         clearMetadataDirty(rawContactUri);
         values = new ContentValues();
@@ -7043,14 +6573,9 @@
         assertStoredValue(contactUri, Contacts.SEND_TO_VOICEMAIL, 1);
 
         assertMetadataDirty(rawContactUri, false);
-        assertMetadataNetworkNotified(false);
     }
 
     public void testMarkAsMetadataDirtyForRawContactBackupIdChange() {
-        // Enable metadataSync flag.
-        final ContactsProvider2 cp = (ContactsProvider2) getProvider();
-        cp.setMetadataSyncForTest(true);
-
         long rawContactId = RawContactUtil.createRawContact(mResolver, mAccount);
         Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
 
@@ -7066,14 +6591,9 @@
         mResolver.update(rawContactUri, values, null, null);
         assertStoredValue(rawContactUri, RawContacts.BACKUP_ID, "newBackupId");
         assertMetadataDirty(rawContactUri, false);
-        assertMetadataNetworkNotified(false);
     }
 
     public void testMarkAsMetadataDirtyForAggregationExceptionChange() {
-        // Enable metadataSync flag.
-        final ContactsProvider2 cp = (ContactsProvider2) getProvider();
-        cp.setMetadataSyncForTest(true);
-
         long rawContactId1 = RawContactUtil.createRawContact(mResolver, new Account("a", "a"));
         long rawContactId2 = RawContactUtil.createRawContact(mResolver, new Account("b", "b"));
 
@@ -7084,28 +6604,18 @@
                 false);
         assertMetadataDirty(ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId2),
                 false);
-        assertMetadataNetworkNotified(false);
     }
 
     public void testMarkAsMetadataNotDirtyForUsageStatsChange() {
-        // Enable metadataSync flag.
-        final ContactsProvider2 cp = (ContactsProvider2) getProvider();
-        cp.setMetadataSyncForTest(true);
-
         final long rid1 = RawContactUtil.createRawContactWithName(mResolver, "contact", "a");
         final long did1a = ContentUris.parseId(insertEmail(rid1, "email_1_a@email.com"));
         updateDataUsageFeedback(DataUsageFeedback.USAGE_TYPE_LONG_TEXT, did1a);
 
         // Usage feedback no longer works, so "false".
         assertMetadataDirty(ContentUris.withAppendedId(RawContacts.CONTENT_URI, rid1), false);
-        assertMetadataNetworkNotified(false);
     }
 
     public void testMarkAsMetadataDirtyForDataPrimarySettingInsert() {
-        // Enable metadataSync flag.
-        final ContactsProvider2 cp = (ContactsProvider2) getProvider();
-        cp.setMetadataSyncForTest(true);
-
         long rawContactId1 = RawContactUtil.createRawContact(mResolver, new Account("a", "a"));
         Uri mailUri11 = insertEmail(rawContactId1, "test1@domain1.com", true, true);
 
@@ -7113,14 +6623,9 @@
         assertStoredValue(mailUri11, Data.IS_SUPER_PRIMARY, 1);
         assertMetadataDirty(ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId1),
                 false);
-        assertMetadataNetworkNotified(false);
     }
 
     public void testMarkAsMetadataDirtyForDataPrimarySettingUpdate() {
-        // Enable metadataSync flag.
-        final ContactsProvider2 cp = (ContactsProvider2) getProvider();
-        cp.setMetadataSyncForTest(true);
-
         long rawContactId = RawContactUtil.createRawContact(mResolver, new Account("a", "a"));
         Uri mailUri1 = insertEmail(rawContactId, "test1@domain1.com");
 
@@ -7133,14 +6638,9 @@
 
         assertMetadataDirty(ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId),
                 false);
-        assertMetadataNetworkNotified(false);
     }
 
     public void testMarkAsMetadataDirtyForDataDelete() {
-        // Enable metadataSync flag.
-        final ContactsProvider2 cp = (ContactsProvider2) getProvider();
-        cp.setMetadataSyncForTest(true);
-
         long rawContactId = RawContactUtil.createRawContact(mResolver, new Account("a", "a"));
         Uri mailUri1 = insertEmail(rawContactId, "test1@domain1.com", true, true);
 
@@ -7148,7 +6648,6 @@
 
         assertMetadataDirty(ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId),
                 false);
-        assertMetadataNetworkNotified(false);
     }
 
     public void testDeleteContactWithoutName() {
@@ -8809,25 +8308,6 @@
         }
     }
 
-    public void testMarkMetadataNotDirtyWhenDataUsageUpdate() {
-        // Enable metadataSync flag.
-        final ContactsProvider2 cp = (ContactsProvider2) getProvider();
-        cp.setMetadataSyncForTest(true);
-
-        final long rid1 = RawContactUtil.createRawContactWithName(mResolver, "contact", "a");
-        final long did1a = ContentUris.parseId(insertEmail(rid1, "email_1_a@email.com"));
-        final Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rid1);
-        assertDirty(rawContactUri, true);
-        clearDirty(rawContactUri);
-        updateDataUsageFeedback(DataUsageFeedback.USAGE_TYPE_LONG_TEXT, did1a);
-
-        assertDirty(rawContactUri, false);
-        // Usage feedback no longer works, so "false".
-        assertMetadataDirty(rawContactUri, false);
-        assertNetworkNotified(false);
-        assertMetadataNetworkNotified(false);
-    }
-
     public void testDataUsageFeedbackAndDelete() {
 
         sMockClock.install();
@@ -9006,10 +8486,6 @@
     }
 
     public void testContactUpdate_metadataChange() {
-        // Enable metadataSync flag.
-        final ContactsProvider2 cp = (ContactsProvider2) getProvider();
-        cp.setMetadataSyncForTest(true);
-
         DatabaseAsserts.ContactIdPair ids = DatabaseAsserts.assertAndCreateContact(mResolver);
         Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, ids.mRawContactId);
         assertDirty(rawContactUri, true);
@@ -9022,7 +8498,6 @@
         assertDirty(rawContactUri, false);
         assertMetadataDirty(rawContactUri, false);
         assertNetworkNotified(false);
-        assertMetadataNetworkNotified(false);
     }
 
     public void testContactUpdate_updatesContactUpdatedTimestamp() {
diff --git a/tests/src/com/android/providers/contacts/MetadataEntryParserTest.java b/tests/src/com/android/providers/contacts/MetadataEntryParserTest.java
deleted file mode 100644
index fe6ed13..0000000
--- a/tests/src/com/android/providers/contacts/MetadataEntryParserTest.java
+++ /dev/null
@@ -1,304 +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.Context;
-import android.test.suitebuilder.annotation.SmallTest;
-import com.android.providers.contacts.MetadataEntryParser.AggregationData;
-import com.android.providers.contacts.MetadataEntryParser.FieldData;
-import com.android.providers.contacts.MetadataEntryParser.MetadataEntry;
-import com.android.providers.contacts.MetadataEntryParser.RawContactInfo;
-import com.android.providers.contacts.MetadataEntryParser.UsageStats;
-import org.json.JSONException;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-
-/**
- * Unit tests for {@link MetadataEntryParser}.
- *
- * Run the test like this:
- * <code>
- adb shell am instrument -e class com.android.providers.contacts.MetadataEntryParserTest -w \
-         com.android.providers.contacts.tests/android.test.InstrumentationTestRunner
- * </code>
- */
-@SmallTest
-public class MetadataEntryParserTest extends FixedAndroidTestCase {
-
-    public void testErrorForEmptyInput() {
-        try {
-            MetadataEntryParser.parseDataToMetaDataEntry("");
-        } catch (IllegalArgumentException e) {
-            // Expected.
-        }
-    }
-
-    public void testParseDataToMetadataEntry() throws IOException {
-        String contactBackupId = "1111111";
-        String accountType = "facebook";
-        String accountName = "android-test";
-        String dataSet = null;
-        int sendToVoicemail = 1;
-        int starred = 0;
-        int pinned = 2;
-        String dataHashId1 = "1001";
-        String usageType1_1 = "CALL";
-        long lastTimeUsed1_1 = 10000001;
-        int timesUsed1_1 = 10;
-        String usageType1_2 = "SHORT_TEXT";
-        long lastTimeUsed1_2 = 20000002;
-        int timesUsed1_2 = 20;
-        String dataHashId2 = "1002";
-        String usageType2 = "LONG_TEXT";
-        long lastTimeUsed2 = 30000003;
-        int timesUsed2 = 30;
-        String aggregationContactBackupId1 = "2222222";
-        String aggregationAccountType1 = "com.google";
-        String aggregationAccountName1 = "android-test2";
-        String aggregationDataSet1 = "plus";
-        String aggregationContactBackupId2 = "3333333";
-        String aggregationAccountType2 = "com.google";
-        String aggregationAccountName2 = "android-test3";
-        String aggregationDataSet2 = "custom type";
-        String type = "TOGETHER";
-        String inputFile = "test1/testFileDeviceContactMetadataJSON.txt";
-
-        RawContactInfo rawContactInfo = new RawContactInfo(
-                contactBackupId, accountType, accountName, dataSet);
-        RawContactInfo aggregationContact1 = new RawContactInfo(aggregationContactBackupId1,
-                aggregationAccountType1, aggregationAccountName1, aggregationDataSet1);
-        RawContactInfo aggregationContact2 = new RawContactInfo(aggregationContactBackupId2,
-                aggregationAccountType2, aggregationAccountName2, aggregationDataSet2);
-        AggregationData aggregationData = new AggregationData(
-                aggregationContact1, aggregationContact2, type);
-        ArrayList<AggregationData> aggregationDataList = new ArrayList<>();
-        aggregationDataList.add(aggregationData);
-
-        UsageStats usageStats1_1 = new UsageStats(usageType1_1, lastTimeUsed1_1, timesUsed1_1);
-        UsageStats usageStats1_2 = new UsageStats(usageType1_2, lastTimeUsed1_2, timesUsed1_2);
-        UsageStats usageStats2 = new UsageStats(usageType2, lastTimeUsed2, timesUsed2);
-
-        ArrayList<UsageStats> usageStats1List = new ArrayList<>();
-        usageStats1List.add(usageStats1_1);
-        usageStats1List.add(usageStats1_2);
-        FieldData fieldData1 = new FieldData(dataHashId1, true, true, usageStats1List);
-
-        ArrayList<UsageStats> usageStats2List = new ArrayList<>();
-        usageStats2List.add(usageStats2);
-        FieldData fieldData2 = new FieldData(dataHashId2, false, false, usageStats2List);
-
-        ArrayList<FieldData> fieldDataList = new ArrayList<>();
-        fieldDataList.add(fieldData1);
-        fieldDataList.add(fieldData2);
-
-        MetadataEntry expectedResult = new MetadataEntry(rawContactInfo,
-                sendToVoicemail, starred, pinned, fieldDataList, aggregationDataList);
-
-        String inputJson = readAssetAsString(inputFile);
-        MetadataEntry metadataEntry = MetadataEntryParser.parseDataToMetaDataEntry(
-                inputJson.toString());
-        assertMetaDataEntry(expectedResult, metadataEntry);
-    }
-
-    public void testErrorForMissingContactId() {
-        String input = "{\"unique_contact_id\": {\n" +
-                "    \"account_type\": \"CUSTOM_ACCOUNT\",\n" +
-                "    \"custom_account_type\": \"facebook\",\n" +
-                "    \"account_name\": \"android-test\"\n" +
-                "  }}";
-        try {
-            MetadataEntryParser.parseDataToMetaDataEntry(input);
-        } catch (IllegalArgumentException e) {
-            // Expected.
-        }
-    }
-
-    public void testErrorForNullContactId() throws JSONException {
-        String input = "{\"unique_contact_id\": {\n" +
-                "    \"account_type\": \"CUSTOM_ACCOUNT\",\n" +
-                "    \"custom_account_type\": \"facebook\",\n" +
-                "    \"account_name\": \"android-test\",\n" +
-                "    \"contact_id\": \"\"\n" +
-                "  }}";
-        try {
-            MetadataEntryParser.parseDataToMetaDataEntry(input);
-        } catch (IllegalArgumentException e) {
-            // Expected.
-        }
-    }
-
-    public void testErrorForNullAccountType() throws JSONException {
-        String input = "{\"unique_contact_id\": {\n" +
-                "    \"account_type\": \"\",\n" +
-                "    \"custom_account_type\": \"facebook\",\n" +
-                "    \"account_name\": \"android-test\",\n" +
-                "    \"contact_id\": \"\"\n" +
-                "  }}";
-        try {
-            MetadataEntryParser.parseDataToMetaDataEntry(input);
-        } catch (IllegalArgumentException e) {
-            // Expected.
-        }
-    }
-
-    public void testErrorForNullAccountName() throws JSONException {
-        String input = "{\"unique_contact_id\": {\n" +
-                "    \"account_type\": \"CUSTOM_ACCOUNT\",\n" +
-                "    \"custom_account_type\": \"facebook\",\n" +
-                "    \"account_name\": \"\",\n" +
-                "    \"contact_id\": \"1111111\"\n" +
-                "  }}";
-        try {
-            MetadataEntryParser.parseDataToMetaDataEntry(input);
-        } catch (IllegalArgumentException e) {
-            // Expected.
-        }
-    }
-
-    public void testErrorForNullFieldDataId() throws JSONException {
-        String input = "{\"unique_contact_id\": {\n" +
-                "    \"account_type\": \"CUSTOM_ACCOUNT\",\n" +
-                "    \"custom_account_type\": \"facebook\",\n" +
-                "    \"account_name\": \"android-test\",\n" +
-                "    \"contact_id\": \"1111111\"\n" +
-                "  },\n" +
-                "    \"contact_prefs\": {\n" +
-                "    \"send_to_voicemail\": true,\n" +
-                "    \"starred\": false,\n" +
-                "    \"pinned\": 2\n" +
-                "  }," +
-                "    \"field_data\": [{\n" +
-                "      \"field_data_id\": \"\"}]" +
-                "}";
-        try {
-            MetadataEntryParser.parseDataToMetaDataEntry(input);
-        } catch (IllegalArgumentException e) {
-            // Expected.
-        }
-    }
-
-    public void testErrorForNullAggregationType() throws JSONException {
-        String input = "{\n" +
-                "  \"unique_contact_id\": {\n" +
-                "    \"account_type\": \"CUSTOM_ACCOUNT\",\n" +
-                "    \"custom_account_type\": \"facebook\",\n" +
-                "    \"account_name\": \"android-test\",\n" +
-                "    \"contact_id\": \"1111111\"\n" +
-                "  },\n" +
-                "  \"contact_prefs\": {\n" +
-                "    \"send_to_voicemail\": true,\n" +
-                "    \"starred\": false,\n" +
-                "    \"pinned\": 2\n" +
-                "  },\n" +
-                "  \"aggregation_data\": [\n" +
-                "    {\n" +
-                "      \"type\": \"\",\n" +
-                "      \"contact_ids\": [\n" +
-                "        {\n" +
-                "          \"contact_id\": \"2222222\"\n" +
-                "        },\n" +
-                "        {\n" +
-                "          \"contact_id\": \"3333333\"\n" +
-                "        }\n" +
-                "      ]\n" +
-                "    }\n" +
-                "  ]}";
-        try {
-            MetadataEntryParser.parseDataToMetaDataEntry(input);
-        } catch (IllegalArgumentException e) {
-            // Expected.
-        }
-    }
-
-    private String readAssetAsString(String fileName) throws IOException {
-        Context context = getTestContext();
-        InputStream input = context.getAssets().open(fileName);
-        ByteArrayOutputStream contents = new ByteArrayOutputStream();
-        int len;
-        byte[] data = new byte[1024];
-        do {
-            len = input.read(data);
-            if (len > 0) contents.write(data, 0, len);
-        } while (len == data.length);
-        return contents.toString();
-    }
-
-    private void assertMetaDataEntry(MetadataEntry entry1, MetadataEntry entry2) {
-        assertRawContactInfoEquals(entry1.mRawContactInfo, entry2.mRawContactInfo);
-        assertEquals(entry1.mSendToVoicemail, entry2.mSendToVoicemail);
-        assertEquals(entry1.mStarred, entry2.mStarred);
-        assertEquals(entry1.mPinned, entry2.mPinned);
-        assertAggregationDataListEquals(entry1.mAggregationDatas, entry2.mAggregationDatas);
-        assertFieldDataListEquals(entry1.mFieldDatas, entry2.mFieldDatas);
-    }
-
-    private void assertRawContactInfoEquals(RawContactInfo contact1, RawContactInfo contact2) {
-        assertEquals(contact1.mBackupId, contact2.mBackupId);
-        assertEquals(contact1.mAccountType, contact2.mAccountType);
-        assertEquals(contact1.mAccountName, contact2.mAccountName);
-        assertEquals(contact1.mDataSet, contact2.mDataSet);
-    }
-
-    private void assertAggregationDataListEquals(ArrayList<AggregationData> aggregationList1,
-            ArrayList<AggregationData> aggregationList2) {
-        assertEquals(aggregationList1.size(), aggregationList2.size());
-        for (int i = 0; i < aggregationList1.size(); i++) {
-            assertAggregationDataEquals(aggregationList1.get(i), aggregationList2.get(i));
-        }
-    }
-
-    private void assertAggregationDataEquals(AggregationData aggregationData1,
-            AggregationData aggregationData2) {
-        assertRawContactInfoEquals(aggregationData1.mRawContactInfo1,
-                aggregationData2.mRawContactInfo1);
-        assertRawContactInfoEquals(aggregationData1.mRawContactInfo2,
-                aggregationData2.mRawContactInfo2);
-        assertEquals(aggregationData1.mType, aggregationData2.mType);
-    }
-
-    private void assertFieldDataListEquals(ArrayList<FieldData> fieldDataList1,
-            ArrayList<FieldData> fieldDataList2) {
-        assertEquals(fieldDataList1.size(), fieldDataList2.size());
-        for (int i = 0; i < fieldDataList1.size(); i++) {
-            assertFieldDataEquals(fieldDataList1.get(i), fieldDataList2.get(i));
-        }
-    }
-
-    private void assertFieldDataEquals(FieldData fieldData1, FieldData fieldData2) {
-        assertEquals(fieldData1.mDataHashId, fieldData2.mDataHashId);
-        assertEquals(fieldData1.mIsPrimary, fieldData2.mIsPrimary);
-        assertEquals(fieldData1.mIsSuperPrimary, fieldData2.mIsSuperPrimary);
-        assertUsageStatsListEquals(fieldData1.mUsageStatsList, fieldData2.mUsageStatsList);
-    }
-
-    private void assertUsageStatsListEquals(ArrayList<UsageStats> usageStatsList1,
-            ArrayList<UsageStats> usageStatsList2) {
-        assertEquals(usageStatsList1.size(), usageStatsList2.size());
-        for (int i = 0; i < usageStatsList1.size(); i++) {
-            assertUsageStatsEquals(usageStatsList1.get(i), usageStatsList2.get(i));
-        }
-    }
-
-    private void assertUsageStatsEquals(UsageStats usageStats1, UsageStats usageStats2) {
-        assertEquals(usageStats1.mUsageType, usageStats2.mUsageType);
-        assertEquals(usageStats1.mLastTimeUsed, usageStats2.mLastTimeUsed);
-        assertEquals(usageStats1.mTimesUsed, usageStats2.mTimesUsed);
-    }
-}
diff --git a/tests/src/com/android/providers/contacts/SynchronousContactsProvider2.java b/tests/src/com/android/providers/contacts/SynchronousContactsProvider2.java
index f674dd5..c2ab74f 100644
--- a/tests/src/com/android/providers/contacts/SynchronousContactsProvider2.java
+++ b/tests/src/com/android/providers/contacts/SynchronousContactsProvider2.java
@@ -36,7 +36,6 @@
     private static ContactsDatabaseHelper sDbHelper;
     private Account mAccount;
     private boolean mNetworkNotified;
-    private boolean mMetadataNetworkNotified;
     private boolean mIsPhone = true;
     private boolean mIsVoiceCapable = true;
 
@@ -62,23 +61,17 @@
     public void onBegin() {
         super.onBegin();
         mNetworkNotified = false;
-        mMetadataNetworkNotified = false;
     }
 
     @Override
-    protected void notifyChange(boolean syncToNetwork, boolean syncToMetadataNetwork) {
+    protected void notifyChange(boolean syncToNetwork) {
         mNetworkNotified |= syncToNetwork;
-        mMetadataNetworkNotified |= syncToMetadataNetwork;
     }
 
     public boolean isNetworkNotified() {
         return mNetworkNotified;
     }
 
-    public boolean isMetadataNetworkNotified() {
-        return mMetadataNetworkNotified;
-    }
-
     public void setIsPhone(boolean flag) {
         mIsPhone = flag;
     }
diff --git a/tests2/Android.bp b/tests2/Android.bp
index 9a2351a..3dcdebb 100644
--- a/tests2/Android.bp
+++ b/tests2/Android.bp
@@ -14,6 +14,17 @@
 // 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: [