Snap for 10453563 from a14e75b24a083007bb01ea72b1dcbc3f50be982e to mainline-documentsui-release

Change-Id: I3bf4104cb962e6716a16165959ecfc3866bbae66
diff --git a/src/com/android/providers/contacts/AbstractContactsProvider.java b/src/com/android/providers/contacts/AbstractContactsProvider.java
index e00bd86..38daa50 100644
--- a/src/com/android/providers/contacts/AbstractContactsProvider.java
+++ b/src/com/android/providers/contacts/AbstractContactsProvider.java
@@ -228,7 +228,7 @@
                     if (++opCount >= BULK_INSERTS_PER_YIELD_POINT) {
                         opCount = 0;
                         try {
-                            yield(transaction);
+                            this.yield(transaction);
                         } catch (RuntimeException re) {
                             transaction.markYieldFailed();
                             throw re;
@@ -274,7 +274,7 @@
                         }
                         opCount = 0;
                         try {
-                            if (yield(transaction)) {
+                            if (this.yield(transaction)) {
                                 ypCount++;
                             }
                         } catch (RuntimeException re) {
diff --git a/src/com/android/providers/contacts/CallLogDatabaseHelper.java b/src/com/android/providers/contacts/CallLogDatabaseHelper.java
index c5052d7..63ba17d 100644
--- a/src/com/android/providers/contacts/CallLogDatabaseHelper.java
+++ b/src/com/android/providers/contacts/CallLogDatabaseHelper.java
@@ -21,6 +21,7 @@
 import android.content.SharedPreferences;
 import android.database.Cursor;
 import android.database.DatabaseUtils;
+import android.database.sqlite.SQLiteCantOpenDatabaseException;
 import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteOpenHelper;
 import android.preference.PreferenceManager;
@@ -203,7 +204,6 @@
                     VoicemailContract.Status.SOURCE_TYPE + " TEXT" +
                     ");");
 
-            migrateFromLegacyTables(db);
         }
 
         @Override
@@ -532,87 +532,6 @@
         mPhoneAccountHandleMigrationUtils.migrateIccIdToSubId(db);
     }
 
-    /**
-     * Perform the migration from the contacts2.db (of the latest version) to the current calllog/
-     * voicemail status tables.
-     */
-    private void migrateFromLegacyTables(SQLiteDatabase calllog) {
-        final SQLiteDatabase contacts = getContactsWritableDatabaseForMigration();
-
-        if (contacts == null) {
-            Log.w(TAG, "Contacts DB == null, skipping migration. (running tests?)");
-            return;
-        }
-        if (DEBUG) {
-            Log.d(TAG, "migrateFromLegacyTables");
-        }
-
-        if ("1".equals(PropertyUtils.getProperty(calllog, DbProperties.DATA_MIGRATED, ""))) {
-            return;
-        }
-
-        Log.i(TAG, "Migrating from old tables...");
-
-        contacts.beginTransaction();
-        try {
-            if (!tableExists(contacts, LegacyConstants.CALLS_LEGACY)
-                    || !tableExists(contacts, LegacyConstants.VOICEMAIL_STATUS_LEGACY)) {
-                // This is fine on new devices. (or after a "clear data".)
-                Log.i(TAG, "Source tables don't exist.");
-                return;
-            }
-            calllog.beginTransaction();
-            try {
-
-                final ContentValues cv = new ContentValues();
-
-                try (Cursor source = contacts.rawQuery(
-                        "SELECT * FROM " + LegacyConstants.CALLS_LEGACY, null)) {
-                    while (source.moveToNext()) {
-                        cv.clear();
-
-                        DatabaseUtils.cursorRowToContentValues(source, cv);
-
-                        calllog.insertOrThrow(Tables.CALLS, null, cv);
-                    }
-                }
-
-                try (Cursor source = contacts.rawQuery("SELECT * FROM " +
-                        LegacyConstants.VOICEMAIL_STATUS_LEGACY, null)) {
-                    while (source.moveToNext()) {
-                        cv.clear();
-
-                        DatabaseUtils.cursorRowToContentValues(source, cv);
-
-                        calllog.insertOrThrow(Tables.VOICEMAIL_STATUS, null, cv);
-                    }
-                }
-
-                contacts.execSQL("DROP TABLE " + LegacyConstants.CALLS_LEGACY + ";");
-                contacts.execSQL("DROP TABLE " + LegacyConstants.VOICEMAIL_STATUS_LEGACY + ";");
-
-                // Also copy the last sync time.
-                PropertyUtils.setProperty(calllog, DbProperties.CALL_LOG_LAST_SYNCED,
-                        PropertyUtils.getProperty(contacts,
-                                LegacyConstants.CALL_LOG_LAST_SYNCED_LEGACY, null));
-
-                Log.i(TAG, "Migration completed.");
-
-                calllog.setTransactionSuccessful();
-            } finally {
-                calllog.endTransaction();
-            }
-
-            contacts.setTransactionSuccessful();
-        } catch (RuntimeException e) {
-            // We don't want to be stuck here, so we just swallow exceptions...
-            Log.w(TAG, "Exception caught during migration", e);
-        } finally {
-            contacts.endTransaction();
-        }
-        PropertyUtils.setProperty(calllog, DbProperties.DATA_MIGRATED, "1");
-    }
-
     @VisibleForTesting
     static boolean tableExists(SQLiteDatabase db, String table) {
         return DatabaseUtils.longForQuery(db,
@@ -621,9 +540,15 @@
     }
 
     @VisibleForTesting
-    @Nullable // We return null during tests when migration is not needed.
+    @Nullable // We return null during tests when migration is not needed or database
+              // is unavailable.
     SQLiteDatabase getContactsWritableDatabaseForMigration() {
-        return ContactsDatabaseHelper.getInstance(mContext).getWritableDatabase();
+        try {
+            return ContactsDatabaseHelper.getInstance(mContext).getWritableDatabase();
+        } catch (SQLiteCantOpenDatabaseException e) {
+            Log.i(TAG, "Exception caught during opening database for migration: " + e);
+            return null;
+        }
     }
 
     public PhoneAccountHandleMigrationUtils getPhoneAccountHandleMigrationUtils() {
diff --git a/src/com/android/providers/contacts/ContactsProvider2.java b/src/com/android/providers/contacts/ContactsProvider2.java
index 3f61a15..5616291 100644
--- a/src/com/android/providers/contacts/ContactsProvider2.java
+++ b/src/com/android/providers/contacts/ContactsProvider2.java
@@ -22,13 +22,15 @@
 
 import static com.android.providers.contacts.util.PhoneAccountHandleMigrationUtils.TELEPHONY_COMPONENT_NAME;
 
-import android.os.Looper;
 import android.accounts.Account;
 import android.accounts.AccountManager;
 import android.accounts.OnAccountsUpdateListener;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.annotation.WorkerThread;
 import android.app.AppOpsManager;
+import android.app.BroadcastOptions;
 import android.app.SearchManager;
 import android.content.BroadcastReceiver;
 import android.content.ContentProviderOperation;
@@ -47,6 +49,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ProviderInfo;
+import android.content.pm.UserInfo;
 import android.content.res.AssetFileDescriptor;
 import android.content.res.Resources;
 import android.content.res.Resources.NotFoundException;
@@ -69,6 +72,7 @@
 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;
@@ -129,10 +133,14 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Log;
+import android.util.SparseArray;
 
 import com.android.common.content.ProjectionMap;
 import com.android.common.content.SyncStateContentProviderHelper;
 import com.android.common.io.MoreCloseables;
+import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.config.appcloning.AppCloningDeviceConfigHelper;
 import com.android.internal.util.ArrayUtils;
 import com.android.providers.contacts.ContactLookupKey.LookupKeySegment;
 import com.android.providers.contacts.ContactsDatabaseHelper.AccountsColumns;
@@ -282,6 +290,14 @@
     /** Rate limit (in milliseconds) for dangling contacts cleanup.  Do it at most once per day. */
     private static final int DANGLING_CONTACTS_CLEANUP_RATE_LIMIT = 24 * 60 * 60 * 1000;
 
+    /** Time after which an entry in the launchable clone packages cache is invalidated and needs to
+     * be refreshed.
+     */
+    private static final int LAUNCHABLE_CLONE_APPS_CACHE_ENTRY_REFRESH_INTERVAL = 10 * 60 * 1000;
+
+    /** This value indicates the frequency of cleanup of the launchable clone apps cache */
+    private static final int LAUNCHABLE_CLONE_APPS_CACHE_CLEANUP_LIMIT = 7 * 24 * 60 * 60 * 1000;
+
     /** Maximum length of a phone number that can be inserted into the database */
     private static final int PHONE_NUMBER_LENGTH_LIMIT = 1000;
 
@@ -341,6 +357,7 @@
     public static final int CONTACTS_ID_PHOTO_CORP = 1027;
     public static final int CONTACTS_ID_DISPLAY_PHOTO_CORP = 1028;
     public static final int CONTACTS_FILTER_ENTERPRISE = 1029;
+    public static final int CONTACTS_ENTERPRISE = 1030;
 
     public static final int RAW_CONTACTS = 2002;
     public static final int RAW_CONTACTS_ID = 2003;
@@ -1206,6 +1223,7 @@
         matcher.addURI(ContactsContract.AUTHORITY, "contacts/frequent", CONTACTS_FREQUENT);
         matcher.addURI(ContactsContract.AUTHORITY, "contacts/delete_usage", CONTACTS_DELETE_USAGE);
 
+        matcher.addURI(ContactsContract.AUTHORITY, "contacts/enterprise", CONTACTS_ENTERPRISE);
         matcher.addURI(ContactsContract.AUTHORITY, "contacts/filter_enterprise",
                 CONTACTS_FILTER_ENTERPRISE);
         matcher.addURI(ContactsContract.AUTHORITY, "contacts/filter_enterprise/*",
@@ -1355,6 +1373,7 @@
         String authority;
         String accountName;
         String accountType;
+        String packageName;
     }
 
     /**
@@ -1368,6 +1387,18 @@
         long groupId;
     }
 
+    @VisibleForTesting
+    protected static class LaunchableCloneAppsCacheEntry {
+        boolean doesAppHaveLaunchableActivity;
+        long lastUpdatedAt;
+
+        public LaunchableCloneAppsCacheEntry(boolean doesAppHaveLaunchableActivity,
+                long lastUpdatedAt) {
+            this.doesAppHaveLaunchableActivity = doesAppHaveLaunchableActivity;
+            this.lastUpdatedAt = lastUpdatedAt;
+        }
+    }
+
     /**
      * The thread-local holder of the active transaction.  Shared between this and the profile
      * provider, to keep transactions on both databases synchronized.
@@ -1431,6 +1462,18 @@
     private ArrayMap<String, ArrayList<GroupIdCacheEntry>> mGroupIdCache = new ArrayMap<>();
 
     /**
+     * Cache to store info on whether a cloned app has a launchable activity. This will be used to
+     * provide it access to query cross-profile contacts.
+     * The key to this map is the uid of the cloned app. The entries in the cache are refreshed
+     * after {@link LAUNCHABLE_CLONE_APPS_CACHE_ENTRY_REFRESH_INTERVAL} to ensure the uid values
+     * stored in the cache aren't stale.
+     */
+    @VisibleForTesting
+    @GuardedBy("mLaunchableCloneAppsCache")
+    protected final SparseArray<LaunchableCloneAppsCacheEntry> mLaunchableCloneAppsCache =
+            new SparseArray<>();
+
+    /**
      * Sub-provider for handling profile requests against the profile database.
      */
     private ProfileProvider mProfileProvider;
@@ -1481,6 +1524,9 @@
 
     private long mLastDanglingContactsCleanup = 0;
 
+    @GuardedBy("mLaunchableCloneAppsCache")
+    private long mLastLaunchableCloneAppsCacheCleanup = 0;
+
     private FastScrollingIndexCache mFastScrollingIndexCache;
 
     // Stats about FastScrollingIndex.
@@ -1493,6 +1539,8 @@
 
     private Set<PhoneAccountHandle> mMigratedPhoneAccountHandles;
 
+    private AppCloningDeviceConfigHelper mAppCloningDeviceConfigHelper;
+
     /**
      * Subscription change will trigger ACTION_PHONE_ACCOUNT_REGISTERED that broadcasts new
      * PhoneAccountHandle that is created based on the new subscription. This receiver is used
@@ -1568,6 +1616,7 @@
 
         mFastScrollingIndexCache = FastScrollingIndexCache.getInstance(getContext());
         mSubscriptionManager = getContext().getSystemService(SubscriptionManager.class);
+        mAppCloningDeviceConfigHelper = AppCloningDeviceConfigHelper.getInstance(getContext());
         mContactsHelper = getDatabaseHelper();
         mDbHelper.set(mContactsHelper);
 
@@ -1579,7 +1628,7 @@
 
         if (mContactsHelper.getPhoneAccountHandleMigrationUtils()
                 .isPhoneAccountMigrationPending()) {
-            IntentFilter filter = new IntentFilter(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);
+            IntentFilter filter = new IntentFilter(TelecomManager.ACTION_PHONE_ACCOUNT_REGISTERED);
             getContext().registerReceiver(mBroadcastReceiver, filter);
         }
 
@@ -1720,6 +1769,13 @@
         return new PhotoPriorityResolver(context);
     }
 
+    @VisibleForTesting
+    protected SparseArray<LaunchableCloneAppsCacheEntry> getLaunchableCloneAppsCacheForTesting() {
+        synchronized (mLaunchableCloneAppsCache) {
+            return mLaunchableCloneAppsCache;
+        }
+    }
+
     protected void scheduleBackgroundTask(int task) {
         scheduleBackgroundTask(task, null);
     }
@@ -2138,6 +2194,20 @@
     }
 
     /**
+     * Returns whether contacts sharing is enabled allowing the clone contacts provider to use the
+     * parent contacts providers contacts data to serve its requests. The method returns true if
+     * the device supports clone profile contacts sharing and the feature flag for the same is
+     * turned on.
+     *
+     * @return true/false if contact sharing is enabled/disabled
+     */
+    @VisibleForTesting
+    protected boolean isContactSharingEnabledForCloneProfile() {
+        return getContext().getResources().getBoolean(R.bool.config_enableAppCloningBuildingBlocks)
+                && mAppCloningDeviceConfigHelper.getEnableAppCloningBuildingBlocks();
+    }
+
+    /**
      * Maximum dimension (height or width) of photo thumbnails.
      */
     public int getMaxThumbnailDim() {
@@ -2297,7 +2367,8 @@
                 .setUriType(sUriMatcher.match(uri))
                 .setCallerIsSyncAdapter(readBooleanQueryParameter(
                         uri, ContactsContract.CALLER_IS_SYNCADAPTER, false))
-                .setStartNanos(SystemClock.elapsedRealtimeNanos());
+                .setStartNanos(SystemClock.elapsedRealtimeNanos())
+                .setUid(Binder.getCallingUid());
         Uri resultUri = null;
 
         try {
@@ -2305,6 +2376,12 @@
 
             mContactsHelper.validateContentValues(getCallingPackage(), values);
 
+            if (!areContactWritesEnabled()) {
+                // Returning fake uri since the insert was rejected
+                Log.w(TAG, "Blocked insert with uri [" + uri + "]. Contact writes not enabled "
+                        + "for the user");
+                return rejectInsert(uri, values);
+            }
             if (mapsToProfileDbWithInsertedValues(uri, values)) {
                 switchToProfileMode();
                 resultUri = mProfileProvider.insert(uri, values);
@@ -2330,7 +2407,8 @@
                 .setUriType(sUriMatcher.match(uri))
                 .setCallerIsSyncAdapter(readBooleanQueryParameter(
                         uri, ContactsContract.CALLER_IS_SYNCADAPTER, false))
-                .setStartNanos(SystemClock.elapsedRealtimeNanos());
+                .setStartNanos(SystemClock.elapsedRealtimeNanos())
+                .setUid(Binder.getCallingUid());
         int updates = 0;
 
         try {
@@ -2339,6 +2417,12 @@
             mContactsHelper.validateContentValues(getCallingPackage(), values);
             mContactsHelper.validateSql(getCallingPackage(), selection);
 
+            if (!areContactWritesEnabled()) {
+                // Returning 0, no rows were updated as writes are disabled
+                Log.w(TAG, "Blocked update with uri [" + uri + "]. Contact writes not enabled "
+                        + "for the user");
+                return 0;
+            }
             if (mapsToProfileDb(uri)) {
                 switchToProfileMode();
                 updates = mProfileProvider.update(uri, values, selection, selectionArgs);
@@ -2362,7 +2446,8 @@
                 .setUriType(sUriMatcher.match(uri))
                 .setCallerIsSyncAdapter(readBooleanQueryParameter(
                         uri, ContactsContract.CALLER_IS_SYNCADAPTER, false))
-                .setStartNanos(SystemClock.elapsedRealtimeNanos());
+                .setStartNanos(SystemClock.elapsedRealtimeNanos())
+                .setUid(Binder.getCallingUid());
         int deletes = 0;
 
         try {
@@ -2370,6 +2455,12 @@
 
             mContactsHelper.validateSql(getCallingPackage(), selection);
 
+            if (!areContactWritesEnabled()) {
+                // Returning 0, no rows were deleted as writes are disabled
+                Log.w(TAG, "Blocked delete with uri [" + uri + "]. Contact writes not enabled "
+                        + "for the user");
+                return 0;
+            }
             if (mapsToProfileDb(uri)) {
                 switchToProfileMode();
                 deletes = mProfileProvider.delete(uri, selection, selectionArgs);
@@ -2386,9 +2477,25 @@
         }
     }
 
+    private void notifySimAccountsChanged() {
+        // This allows us to discard older broadcasts still waiting to be delivered.
+        final Bundle options = BroadcastOptions.makeBasic()
+                .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT)
+                .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE)
+                .toBundle();
+
+        getContext().sendBroadcast(new Intent(SimContacts.ACTION_SIM_ACCOUNTS_CHANGED), null,
+                options);
+    }
+
     @Override
     public Bundle call(String method, String arg, Bundle extras) {
         waitForAccess(mReadAccessLatch);
+        if (!areContactWritesEnabled()) {
+            // Returning EMPTY Bundle since the call was rejected and no rows were affected
+            Log.w(TAG, "Blocked call to method [" + method + "], not enabled for the user");
+            return Bundle.EMPTY;
+        }
         switchToContactMode();
         if (Authorization.AUTHORIZATION_METHOD.equals(method)) {
             Uri uri = extras.getParcelable(Authorization.KEY_URI_TO_AUTHORIZE);
@@ -2438,7 +2545,7 @@
             } finally {
                 db.endTransaction();
             }
-            getContext().sendBroadcast(new Intent(SimContacts.ACTION_SIM_ACCOUNTS_CHANGED));
+            notifySimAccountsChanged();
             return response;
         } else if (SimContacts.REMOVE_SIM_ACCOUNT_METHOD.equals(method)) {
             ContactsPermissions.enforceCallingOrSelfPermission(getContext(),
@@ -2458,7 +2565,7 @@
             } finally {
                 db.endTransaction();
             }
-            getContext().sendBroadcast(new Intent(SimContacts.ACTION_SIM_ACCOUNTS_CHANGED));
+            notifySimAccountsChanged();
             return response;
         } else if (SimContacts.QUERY_SIM_ACCOUNTS_METHOD.equals(method)) {
             ContactsPermissions.enforceCallingOrSelfPermission(getContext(), READ_PERMISSION);
@@ -2607,6 +2714,12 @@
     @Override
     public int bulkInsert(Uri uri, ContentValues[] values) {
         waitForAccess(mWriteAccessLatch);
+        if (!areContactWritesEnabled()) {
+            // Returning 0, no rows were affected since writes are disabled
+            Log.w(TAG, "Blocked bulkInsert with uri [" + uri + "]. Contact writes not enabled "
+                    + "for the user");
+            return 0;
+        }
         return super.bulkInsert(uri, values);
     }
 
@@ -5552,7 +5665,8 @@
                 .setUriType(sUriMatcher.match(uri))
                 .setCallerIsSyncAdapter(readBooleanQueryParameter(
                         uri, ContactsContract.CALLER_IS_SYNCADAPTER, false))
-                .setStartNanos(SystemClock.elapsedRealtimeNanos());
+                .setStartNanos(SystemClock.elapsedRealtimeNanos())
+                .setUid(Binder.getCallingUid());
 
         Cursor cursor = null;
         try {
@@ -5590,10 +5704,15 @@
 
         // If caller does not come from same profile, Check if it's privileged or allowed by
         // enterprise policy
-        if (!queryAllowedByEnterprisePolicy(uri)) {
+        if (!isCrossUserQueryAllowed(uri)) {
             return null;
         }
 
+        if (shouldRedirectQueryToParentProvider()) {
+            return queryParentProfileContactsProvider(uri, projection, selection, selectionArgs,
+                    sortOrder, cancellationSignal);
+        }
+
         // Query the profile DB if appropriate.
         if (mapsToProfileDb(uri)) {
             switchToProfileMode();
@@ -5613,7 +5732,68 @@
         }
     }
 
-    private boolean queryAllowedByEnterprisePolicy(Uri uri) {
+    /**
+     * Check if the query should be redirected to the parent profile's contacts provider.
+     */
+    private boolean shouldRedirectQueryToParentProvider() {
+        return isContactSharingEnabledForCloneProfile() &&
+                UserUtils.shouldUseParentsContacts(getContext()) &&
+                isAppAllowedToUseParentUsersContacts(getCallingPackage());
+    }
+
+    /**
+     * Check if the app with the given package name is allowed to use parent user's contacts to
+     * serve the contacts read queries.
+     */
+    @VisibleForTesting
+    protected boolean isAppAllowedToUseParentUsersContacts(@Nullable String packageName) {
+        final int callingUid = Binder.getCallingUid();
+        final UserHandle user = Binder.getCallingUserHandle();
+
+        synchronized (mLaunchableCloneAppsCache) {
+            maybeCleanupLaunchableCloneAppsCacheLocked();
+
+            final long now = System.currentTimeMillis();
+            LaunchableCloneAppsCacheEntry cacheEntry = mLaunchableCloneAppsCache.get(callingUid);
+            if (cacheEntry == null || ((now - cacheEntry.lastUpdatedAt)
+                    >= LAUNCHABLE_CLONE_APPS_CACHE_ENTRY_REFRESH_INTERVAL)) {
+                boolean result = doesPackageHaveALauncherActivity(packageName, user);
+                mLaunchableCloneAppsCache.put(callingUid,
+                        new LaunchableCloneAppsCacheEntry(result, now));
+            }
+            return mLaunchableCloneAppsCache.get(callingUid).doesAppHaveLaunchableActivity;
+        }
+    }
+
+    /**
+     * Clean up the launchable clone apps cache to ensure it doesn't have any stale entries taking
+     * up additional space. The frequency of the cleanup is governed by {@link
+     * LAUNCHABLE_CLONE_APPS_CACHE_CLEANUP_LIMIT}.
+     */
+    @GuardedBy("mLaunchableCloneAppsCache")
+    private void maybeCleanupLaunchableCloneAppsCacheLocked() {
+        long now = System.currentTimeMillis();
+        if (now - mLastLaunchableCloneAppsCacheCleanup
+                >= LAUNCHABLE_CLONE_APPS_CACHE_CLEANUP_LIMIT) {
+            mLaunchableCloneAppsCache.clear();
+            mLastLaunchableCloneAppsCacheCleanup = now;
+        }
+    }
+
+    @VisibleForTesting
+    @SuppressLint("AndroidFrameworkRequiresPermission")
+    protected boolean doesPackageHaveALauncherActivity(String packageName, UserHandle user) {
+        Intent launcherCategoryIntent = new Intent(Intent.ACTION_MAIN)
+                .addCategory(Intent.CATEGORY_LAUNCHER)
+                .setPackage(packageName);
+        final PackageManager pm = getContext().getPackageManager();
+        return !pm.queryIntentActivitiesAsUser(launcherCategoryIntent,
+                        PackageManager.ResolveInfoFlags.of(PackageManager.GET_ACTIVITIES),
+                        user)
+                .isEmpty();
+    }
+
+    private boolean isCrossUserQueryAllowed(Uri uri) {
         if (isCallerFromSameUser()) {
             // Caller is on the same user; query allowed.
             return true;
@@ -5621,14 +5801,22 @@
         if (!doesCallerHoldInteractAcrossUserPermission()) {
             // Cross-user and the caller has no INTERACT_ACROSS_USERS; don't allow query.
             // Technically, in a cross-profile sharing case, this would be a valid query.
-            // But for now we don't allow it. (We never allowe it and no one complained about it.)
+            // But for now we don't allow it. (We never allowed it and no one complained about it.)
             return false;
         }
         if (isCallerAnotherSelf()) {
-            // The caller is the other CP2 (which has INTERACT_ACROSS_USERS), meaning the reuest
+            // The caller is the other CP2 (which has INTERACT_ACROSS_USERS), meaning the request
             // is on behalf of a "real" client app.
+
+            if (isContactSharingEnabledForCloneProfile() &&
+                    doesCallingProviderUseCurrentUsersContacts())  {
+                // The caller is the other CP2 (which has INTERACT_ACROSS_USERS), from the child
+                // user (of the current user) profile with the property of using parent's contacts
+                // set.
+                return true;
+            }
             // Consult the enterprise policy.
-            return mEnterprisePolicyGuard.isCrossProfileAllowed(uri);
+            return mEnterprisePolicyGuard.isCrossProfileAllowed(uri, getRealCallerPackageName(uri));
         }
         return true;
     }
@@ -5638,6 +5826,22 @@
     }
 
     /**
+     * Returns true if calling contacts provider instance uses current users contacts.
+     * This can happen when the current user is the parent of the calling user and the calling user
+     * has the corresponding user property to use parent's contacts set. Please note that this
+     * cross-profile contact access will only be allowed if the call is redirected from the child
+     * user's CP2.
+     */
+    private boolean doesCallingProviderUseCurrentUsersContacts() {
+        UserHandle callingUserHandle = UserHandle.getUserHandleForUid(Binder.getCallingUid());
+        UserHandle currentUserHandle = android.os.Process.myUserHandle();
+        boolean isCallerFromSameUser = callingUserHandle.equals(currentUserHandle);
+        return isCallerFromSameUser ||
+                (UserUtils.shouldUseParentsContacts(getContext(), callingUserHandle) &&
+                UserUtils.isParentUser(getContext(), currentUserHandle, callingUserHandle));
+    }
+
+    /**
      * Returns true if called by a different user's CP2.
      */
     private boolean isCallerAnotherSelf() {
@@ -5655,7 +5859,19 @@
                 || context.checkCallingPermission(INTERACT_ACROSS_USERS) == PERMISSION_GRANTED;
     }
 
-    private Cursor queryDirectoryIfNecessary(Uri uri, String[] projection, String selection,
+    /**
+     * Returns true if the contact writes are enabled for the current instance of ContactsProvider.
+     * The ContactsProvider instance running in the clone profile should block inserts, updates
+     * and deletes and hence should return false.
+     */
+    @VisibleForTesting
+    protected boolean areContactWritesEnabled() {
+        return !isContactSharingEnabledForCloneProfile() ||
+                !UserUtils.shouldUseParentsContacts(getContext());
+    }
+
+    @VisibleForTesting
+    protected Cursor queryDirectoryIfNecessary(Uri uri, String[] projection, String selection,
             String[] selectionArgs, String sortOrder, CancellationSignal cancellationSignal) {
         String directory = getQueryParameter(uri, ContactsContract.DIRECTORY_PARAM_KEY);
         final long directoryId =
@@ -5703,14 +5919,14 @@
             final String passedPackage = queryUri.getQueryParameter(
                     Directory.CALLER_PACKAGE_PARAM_KEY);
             if (TextUtils.isEmpty(passedPackage)) {
-                Log.wtfStack(TAG,
-                        "Cross-profile query with no " + Directory.CALLER_PACKAGE_PARAM_KEY);
-                return "UNKNOWN";
+                throw new IllegalArgumentException(
+                        "Cross-profile query with no " + Directory.CALLER_PACKAGE_PARAM_KEY
+                                + " param. Uri: " + queryUri);
             }
             return passedPackage;
         } else {
             // Otherwise, just return the real calling package name.
-            return getCallingPackage();
+            return getCallingPackageUnchecked();
         }
     }
 
@@ -5749,8 +5965,20 @@
         if (projection == null) {
             projection = getDefaultProjection(uri);
         }
+        int galUid = -1;
+        try {
+            galUid = getContext().getPackageManager().getPackageUid(directoryInfo.packageName,
+                    PackageManager.MATCH_ALL);
+        } catch (NameNotFoundException e) {
+            // Shouldn't happen, but just in case.
+            Log.w(TAG, "getPackageUid() failed", e);
+        }
+        final LogFields.Builder logBuilder = LogFields.Builder.aLogFields()
+                .setApiType(LogUtils.ApiType.GAL_CALL)
+                .setUriType(sUriMatcher.match(uri))
+                .setUid(galUid);
 
-        Cursor cursor;
+        Cursor cursor = null;
         try {
             if (VERBOSE_LOGGING) {
                 Log.v(TAG, "Making directory query: uri=" + directoryUri +
@@ -5768,6 +5996,9 @@
         } catch (RuntimeException e) {
             Log.w(TAG, "Directory query failed", e);
             return null;
+        } finally {
+            LogUtils.log(
+                    logBuilder.setResultCount(cursor == null ? 0 : cursor.getCount()).build());
         }
 
         if (cursor.getCount() > 0) {
@@ -5804,11 +6035,7 @@
             return createEmptyCursor(localUri, projection);
         }
         // Make sure authority is CP2 not other providers
-        if (!ContactsContract.AUTHORITY.equals(localUri.getAuthority())) {
-            Log.w(TAG, "Invalid authority: " + localUri.getAuthority());
-            throw new IllegalArgumentException(
-                    "Authority " + localUri.getAuthority() + " is not a valid CP2 authority.");
-        }
+        validateAuthority(localUri.getAuthority());
         // Add the "user-id @" to the URI, and also pass the caller package name.
         final Uri remoteUri = maybeAddUserId(localUri, corpUserId).buildUpon()
                 .appendQueryParameter(Directory.CALLER_PACKAGE_PARAM_KEY, getCallingPackage())
@@ -5821,6 +6048,69 @@
         return cursor;
     }
 
+    private Uri getParentProviderUri(Uri uri, @NonNull UserInfo parentUserInfo) {
+        // Add the "user-id @" of the parent to the URI
+        final Builder remoteUriBuilder =
+                maybeAddUserId(uri, parentUserInfo.getUserHandle().getIdentifier())
+                        .buildUpon();
+        // Pass the caller package name query param and build the uri
+        return remoteUriBuilder
+                .appendQueryParameter(Directory.CALLER_PACKAGE_PARAM_KEY,
+                        getRealCallerPackageName(uri))
+                .build();
+    }
+
+    protected AssetFileDescriptor openAssetFileThroughParentProvider(Uri uri, String mode)
+            throws FileNotFoundException {
+        final UserInfo parentUserInfo = UserUtils.getProfileParentUser(getContext());
+        if (parentUserInfo == null) {
+            return null;
+        }
+        validateAuthority(uri.getAuthority());
+        final Uri remoteUri = getParentProviderUri(uri, parentUserInfo);
+        return getContext().getContentResolver().openAssetFile(remoteUri, mode, null);
+    }
+
+    /**
+     * A helper function to query parent CP2, should only be called from users that are allowed to
+     * use parents contacts
+     */
+    @VisibleForTesting
+    protected Cursor queryParentProfileContactsProvider(Uri uri, String[] projection,
+            String selection, String[] selectionArgs, String sortOrder,
+            CancellationSignal cancellationSignal) {
+        final UserInfo parentUserInfo = UserUtils.getProfileParentUser(getContext());
+        if (parentUserInfo == null) {
+            return createEmptyCursor(uri, projection);
+        }
+        // Make sure authority is CP2 not other providers
+        validateAuthority(uri.getAuthority());
+        Cursor cursor = queryContactsProviderForUser(uri, projection, selection, selectionArgs,
+                sortOrder, cancellationSignal, parentUserInfo);
+        if (cursor == null) {
+            Log.w(TAG, "null cursor returned from primary CP2");
+            return createEmptyCursor(uri, projection);
+        }
+        return cursor;
+    }
+
+    @VisibleForTesting
+    protected Cursor queryContactsProviderForUser(Uri uri, String[] projection, String selection,
+            String[] selectionArgs, String sortOrder, CancellationSignal cancellationSignal,
+            UserInfo parentUserInfo) {
+        final Uri remoteUri = getParentProviderUri(uri, parentUserInfo);
+        return getContext().getContentResolver().query(remoteUri, projection, selection,
+                selectionArgs, sortOrder, cancellationSignal);
+    }
+
+    private void validateAuthority(String authority) {
+        if (!ContactsContract.AUTHORITY.equals(authority)) {
+            Log.w(TAG, "Invalid authority: " + authority);
+            throw new IllegalArgumentException(
+                    "Authority " + authority + " is not a valid CP2 authority.");
+        }
+    }
+
     private Cursor addSnippetExtrasToCursor(Uri uri, Cursor cursor) {
 
         // If the cursor doesn't contain a snippet column, don't bother wrapping it.
@@ -5862,7 +6152,8 @@
                 Directory._ID,
                 Directory.DIRECTORY_AUTHORITY,
                 Directory.ACCOUNT_NAME,
-                Directory.ACCOUNT_TYPE
+                Directory.ACCOUNT_TYPE,
+                Directory.PACKAGE_NAME
         };
 
         public static final int DIRECTORY_ID = 0;
@@ -5888,6 +6179,8 @@
                         info.authority = cursor.getString(DirectoryQuery.AUTHORITY);
                         info.accountName = cursor.getString(DirectoryQuery.ACCOUNT_NAME);
                         info.accountType = cursor.getString(DirectoryQuery.ACCOUNT_TYPE);
+                        info.packageName =
+                                cursor.getString(cursor.getColumnIndex(Directory.PACKAGE_NAME));
                         mDirectoryCache.put(id, info);
                     }
                 } finally {
@@ -6341,8 +6634,6 @@
                         new Object[] {getMaxDisplayPhotoDim(), getMaxThumbnailDim()});
             }
             case PHONES_ENTERPRISE: {
-                ContactsPermissions.enforceCallingOrSelfPermission(getContext(),
-                        INTERACT_ACROSS_USERS);
                 return queryMergedDataPhones(uri, projection, selection, selectionArgs, sortOrder,
                         cancellationSignal);
             }
@@ -6510,6 +6801,9 @@
                 }
                 break;
             }
+            case CONTACTS_ENTERPRISE:
+                return queryMergedContacts(projection, selection, selectionArgs, sortOrder,
+                        cancellationSignal);
             case PHONES_FILTER_ENTERPRISE:
             case CALLABLES_FILTER_ENTERPRISE:
             case EMAILS_FILTER_ENTERPRISE:
@@ -7273,8 +7567,7 @@
             final Cursor[] cursorArray = new Cursor[] {
                     primaryCursor, rewriteCorpDirectories(corpCursor)
             };
-            final MergeCursor mergeCursor = new MergeCursor(cursorArray);
-            return mergeCursor;
+            return new MergeCursor(cursorArray);
         } catch (Throwable th) {
             if (primaryCursor != null) {
                 primaryCursor.close();
@@ -7288,6 +7581,35 @@
     }
 
     /**
+     * Handles {@link Contacts#ENTERPRISE_CONTENT_URI}.
+     */
+    private Cursor queryMergedContacts(String[] projection, String selection,
+            String[] selectionArgs, String sortOrder, CancellationSignal cancellationSignal) {
+        final Uri localUri = Contacts.CONTENT_URI;
+        final Cursor primaryCursor = queryLocal(localUri, projection, selection, selectionArgs,
+                sortOrder, Directory.DEFAULT, cancellationSignal);
+        try {
+            final int managedUserId = UserUtils.getCorpUserId(getContext());
+            if (managedUserId < 0) {
+                // No managed profile or policy not allowed
+                return primaryCursor;
+            }
+            final Cursor managedCursor = queryCorpContacts(localUri, projection, selection,
+                    selectionArgs, sortOrder, new String[] {Contacts._ID},
+                    Directory.ENTERPRISE_DEFAULT, cancellationSignal);
+            final Cursor[] cursorArray = new Cursor[] {
+                    primaryCursor, managedCursor
+            };
+            return new MergeCursor(cursorArray);
+        } catch (Throwable th) {
+            if (primaryCursor != null) {
+                primaryCursor.close();
+            }
+            throw th;
+        }
+    }
+
+    /**
      * Handles {@link Phone#ENTERPRISE_CONTENT_URI}.
      */
     private Cursor queryMergedDataPhones(Uri uri, String[] projection, String selection,
@@ -7310,8 +7632,6 @@
         final Cursor primaryCursor = queryLocal(localUri, projection, selection, selectionArgs,
                 sortOrder, directoryId, null);
         try {
-            // PHONES_ENTERPRISE should not be guarded by EnterprisePolicyGuard as Bluetooth app is
-            // responsible to guard it.
             final int corpUserId = UserUtils.getCorpUserId(getContext());
             if (corpUserId < 0) {
                 // No Corp user or policy not allowed
@@ -7321,21 +7641,10 @@
             final Cursor managedCursor = queryCorpContacts(localUri, projection, selection,
                     selectionArgs, sortOrder, new String[] {RawContacts.CONTACT_ID}, null,
                     cancellationSignal);
-            if (managedCursor == null) {
-                // No corp results.  Just return the local result.
-                return primaryCursor;
-            }
             final Cursor[] cursorArray = new Cursor[] {
                     primaryCursor, managedCursor
             };
-            // Sort order is not supported yet, will be fixed in M when we have
-            // merged provider
-            // MergeCursor will copy all the contacts from two cursors, which may
-            // cause OOM if there's a lot of contacts. But it's only used by
-            // Bluetooth, and Bluetooth will loop through the Cursor and put all
-            // content in ArrayList anyway, so we ignore OOM issue here for now
-            final MergeCursor mergeCursor = new MergeCursor(cursorArray);
-            return mergeCursor;
+            return new MergeCursor(cursorArray);
         } catch (Throwable th) {
             if (primaryCursor != null) {
                 primaryCursor.close();
@@ -8702,13 +9011,25 @@
     public AssetFileDescriptor openAssetFile(Uri uri, String mode) throws FileNotFoundException {
         boolean success = false;
         try {
+            if (!mode.equals("r") && !areContactWritesEnabled()) {
+                Log.w(TAG, "Blocked openAssetFile with uri [" + uri + "]. Contact writes not "
+                        + "enabled for the user");
+                return null;
+            }
             if (!isDirectoryParamValid(uri)){
                 return null;
             }
-            if (!queryAllowedByEnterprisePolicy(uri)) {
+            if (!isCrossUserQueryAllowed(uri)) {
                 return null;
             }
             waitForAccess(mode.equals("r") ? mReadAccessLatch : mWriteAccessLatch);
+
+            // Redirect reads to parent provider if the corresponding user property is set and app
+            // is allow-listed to access parent's contacts
+            if (mode.equals("r") && shouldRedirectQueryToParentProvider()) {
+                return openAssetFileThroughParentProvider(uri, mode);
+            }
+
             final AssetFileDescriptor ret;
             if (mapsToProfileDb(uri)) {
                 switchToProfileMode();
@@ -9022,6 +9343,8 @@
             builder.encodedPath(uri.getEncodedPath());
             builder.appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY,
                     String.valueOf(directoryId - Directory.ENTERPRISE_DIRECTORY_ID_BASE));
+            builder.appendQueryParameter(Directory.CALLER_PACKAGE_PARAM_KEY,
+                    getRealCallerPackageName(uri));
             addQueryParametersFromUri(builder, uri, MODIFIED_KEY_SET_FOR_ENTERPRISE_FILTER);
 
             // If work profile is not available, it will throw FileNotFoundException
@@ -9075,12 +9398,14 @@
             throw new FileNotFoundException(uri.toString());
         }
         // Convert the URI into:
-        // content://USER@com.android.contacts/contacts_corp/ID/{photo,display_photo}
+        // content://USER@com.android.contacts/contacts/ID/{photo,display_photo}
         // If work profile is not available, it will throw FileNotFoundException
         final Uri corpUri = maybeAddUserId(
                 ContentUris.appendId(Contacts.CONTENT_URI.buildUpon(), contactId)
                         .appendPath(displayPhoto ?
                                 Contacts.Photo.DISPLAY_PHOTO : Contacts.Photo.CONTENT_DIRECTORY)
+                        .appendQueryParameter(Directory.CALLER_PACKAGE_PARAM_KEY,
+                                getRealCallerPackageName(uri))
                         .build(), corpUserId);
 
         // TODO Make sure it doesn't leak any FDs.
diff --git a/src/com/android/providers/contacts/DataRowHandlerForNickname.java b/src/com/android/providers/contacts/DataRowHandlerForNickname.java
index 03b96a3..2806cf3 100644
--- a/src/com/android/providers/contacts/DataRowHandlerForNickname.java
+++ b/src/com/android/providers/contacts/DataRowHandlerForNickname.java
@@ -35,9 +35,14 @@
                 Nickname.LABEL);
     }
 
+    private void applySimpleFieldMaxSize(ContentValues cv) {
+        applySimpleFieldMaxSize(cv, Nickname.NAME);
+    }
+
     @Override
     public long insert(SQLiteDatabase db, TransactionContext txContext, long rawContactId,
             ContentValues values) {
+        applySimpleFieldMaxSize(values);
         String nickname = values.getAsString(Nickname.NAME);
 
         long dataId = super.insert(db, txContext, rawContactId, values);
@@ -53,6 +58,7 @@
     @Override
     public boolean update(SQLiteDatabase db, TransactionContext txContext, ContentValues values,
             Cursor c, boolean callerIsSyncAdapter) {
+        applySimpleFieldMaxSize(values);
         long dataId = c.getLong(DataUpdateQuery._ID);
         long rawContactId = c.getLong(DataUpdateQuery.RAW_CONTACT_ID);
 
diff --git a/src/com/android/providers/contacts/DataRowHandlerForOrganization.java b/src/com/android/providers/contacts/DataRowHandlerForOrganization.java
index 66a3b1b..74f2259 100644
--- a/src/com/android/providers/contacts/DataRowHandlerForOrganization.java
+++ b/src/com/android/providers/contacts/DataRowHandlerForOrganization.java
@@ -22,6 +22,7 @@
 import android.database.sqlite.SQLiteDatabase;
 import android.provider.ContactsContract.CommonDataKinds.Organization;
 import android.provider.ContactsContract.Data;
+
 import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
 import com.android.providers.contacts.SearchIndexManager.IndexBuilder;
 import com.android.providers.contacts.aggregation.AbstractContactAggregator;
@@ -37,9 +38,15 @@
                 Organization.CONTENT_ITEM_TYPE, Organization.TYPE, Organization.LABEL);
     }
 
+    private void applySimpleFieldMaxSize(ContentValues cv) {
+        applySimpleFieldMaxSize(cv, Organization.COMPANY);
+        applySimpleFieldMaxSize(cv, Organization.TITLE);
+    }
+
     @Override
     public long insert(SQLiteDatabase db, TransactionContext txContext, long rawContactId,
             ContentValues values) {
+        applySimpleFieldMaxSize(values);
         String company = values.getAsString(Organization.COMPANY);
         String title = values.getAsString(Organization.TITLE);
 
@@ -52,6 +59,7 @@
     @Override
     public boolean update(SQLiteDatabase db, TransactionContext txContext, ContentValues values,
             Cursor c, boolean callerIsSyncAdapter) {
+        applySimpleFieldMaxSize(values);
         if (!super.update(db, txContext, values, c, callerIsSyncAdapter)) {
             return false;
         }
diff --git a/src/com/android/providers/contacts/enterprise/EnterprisePolicyGuard.java b/src/com/android/providers/contacts/enterprise/EnterprisePolicyGuard.java
index 4684105..12a0305 100644
--- a/src/com/android/providers/contacts/enterprise/EnterprisePolicyGuard.java
+++ b/src/com/android/providers/contacts/enterprise/EnterprisePolicyGuard.java
@@ -19,8 +19,10 @@
 import android.annotation.NonNull;
 import android.app.admin.DevicePolicyManager;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.net.Uri;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.provider.ContactsContract;
 import android.provider.ContactsContract.Directory;
 import android.provider.Settings;
@@ -29,8 +31,11 @@
 import com.android.providers.contacts.ContactsProvider2;
 import com.android.providers.contacts.ProfileAwareUriMatcher;
 import com.android.providers.contacts.util.UserUtils;
+
 import com.google.common.annotations.VisibleForTesting;
 
+import static android.Manifest.permission.INTERACT_ACROSS_USERS;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.provider.Settings.Secure.MANAGED_PROFILE_CONTACT_REMOTE_SEARCH;
 
 /**
@@ -44,22 +49,25 @@
 
     final private Context mContext;
     final private DevicePolicyManager mDpm;
+    final private PackageManager mPm;
 
     public EnterprisePolicyGuard(Context context) {
         mContext = context;
         mDpm = context.getSystemService(DevicePolicyManager.class);
+        mPm = context.getPackageManager();
     }
 
     /**
      * Check if cross profile query is allowed for the given uri
      *
      * @param uri Uri that we want to check.
+     * @param callingPackage Name of the client package that called CP2 in the other profile
      * @return True if cross profile query is allowed for this uri
      */
-    public boolean isCrossProfileAllowed(@NonNull Uri uri) {
+    public boolean isCrossProfileAllowed(@NonNull Uri uri, @NonNull String callingPackage) {
         final int uriCode = sUriMatcher.match(uri);
         final UserHandle currentHandle = new UserHandle(UserUtils.getCurrentUserHandle(mContext));
-        if (uriCode == -1 || currentHandle == null) {
+        if (uriCode == -1) {
             return false;
         }
 
@@ -67,11 +75,14 @@
             return true;
         }
 
-        final boolean isCallerIdEnabled = !mDpm.getCrossProfileCallerIdDisabled(currentHandle);
+        final boolean isCallerIdEnabled =
+                mDpm.hasManagedProfileCallerIdAccess(currentHandle, callingPackage);
         final boolean isContactsSearchPolicyEnabled =
-                !mDpm.getCrossProfileContactsSearchDisabled(currentHandle);
+                mDpm.hasManagedProfileContactsAccess(currentHandle, callingPackage);
         final boolean isBluetoothContactSharingEnabled =
                 !mDpm.getBluetoothContactSharingDisabled(currentHandle);
+        final boolean isManagedProfileEnabled = !UserUtils.getUserManager(mContext)
+                .isQuietModeEnabled(new UserHandle(UserUtils.getCorpUserId(mContext)));
         final boolean isContactRemoteSearchUserEnabled = isContactRemoteSearchUserSettingEnabled();
 
         final String directory = uri.getQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY);
@@ -81,24 +92,34 @@
             Log.v(TAG, "isContactsSearchPolicyEnabled: " + isContactsSearchPolicyEnabled);
             Log.v(TAG, "isBluetoothContactSharingEnabled: " + isBluetoothContactSharingEnabled);
             Log.v(TAG, "isContactRemoteSearchUserEnabled: " + isContactRemoteSearchUserEnabled);
+            Log.v(TAG, "isManagedProfileEnabled: " + isManagedProfileEnabled);
         }
 
         // If it is a remote directory, it is allowed only when
         // (i) The uri supports directory
         // (ii) User enables it in settings
+        // (iii) The managed profile is enabled
         if (directory != null) {
             final long directoryId = Long.parseLong(directory);
             if (Directory.isRemoteDirectoryId(directoryId)
                     && !(isCrossProfileDirectorySupported(uri)
-                    && isContactRemoteSearchUserEnabled)) {
+                    && isContactRemoteSearchUserEnabled
+                    && isManagedProfileEnabled)) {
                 return false;
             }
         }
 
+        final boolean isAllowedByCallerIdPolicy = isCallerIdGuarded(uriCode) && isCallerIdEnabled;
+        final boolean isAllowedByContactSearchPolicy =
+                isContactsSearchGuarded(uriCode) && isContactsSearchPolicyEnabled;
+        final boolean isAllowedByBluetoothSharingPolicy =
+                isBluetoothContactSharing(uriCode) && isBluetoothContactSharingEnabled
+                        // Only allow apps with INTERACT_ACROSS_USERS to access the bluetooth APIs
+                        && mPm.checkPermission(INTERACT_ACROSS_USERS, callingPackage)
+                        == PERMISSION_GRANTED;
         // If either guard policy allows access, return true.
-        return (isCallerIdGuarded(uriCode) && isCallerIdEnabled)
-                || (isContactsSearchGuarded(uriCode) && isContactsSearchPolicyEnabled)
-                || (isBluetoothContactSharing(uriCode) && isBluetoothContactSharingEnabled);
+        return isAllowedByCallerIdPolicy || isAllowedByContactSearchPolicy
+                || isAllowedByBluetoothSharingPolicy;
     }
 
     private boolean isUriWhitelisted(int uriCode) {
@@ -148,7 +169,9 @@
         switch (uriCode) {
             case ContactsProvider2.PHONE_LOOKUP_ENTERPRISE:
             case ContactsProvider2.EMAILS_LOOKUP_ENTERPRISE:
+            case ContactsProvider2.CONTACTS_ENTERPRISE:
             case ContactsProvider2.CONTACTS_FILTER_ENTERPRISE:
+            case ContactsProvider2.PHONES_ENTERPRISE:
             case ContactsProvider2.PHONES_FILTER_ENTERPRISE:
             case ContactsProvider2.CALLABLES_FILTER_ENTERPRISE:
             case ContactsProvider2.EMAILS_FILTER_ENTERPRISE:
@@ -180,8 +203,10 @@
         switch(uriCode) {
             case ContactsProvider2.DIRECTORIES:
             case ContactsProvider2.DIRECTORIES_ID:
+            case ContactsProvider2.CONTACTS:
             case ContactsProvider2.CONTACTS_FILTER:
             case ContactsProvider2.CALLABLES_FILTER:
+            case ContactsProvider2.PHONES:
             case ContactsProvider2.PHONES_FILTER:
             case ContactsProvider2.EMAILS_FILTER:
             case ContactsProvider2.CONTACTS_ID_PHOTO:
@@ -208,4 +233,5 @@
                 mContext.getContentResolver(),
                 MANAGED_PROFILE_CONTACT_REMOTE_SEARCH, 0) == 1;
     }
+
 }
diff --git a/src/com/android/providers/contacts/util/LogFields.java b/src/com/android/providers/contacts/util/LogFields.java
index fc05c84..1672d3d 100644
--- a/src/com/android/providers/contacts/util/LogFields.java
+++ b/src/com/android/providers/contacts/util/LogFields.java
@@ -37,6 +37,8 @@
 
     private int resultCount;
 
+    private int uid;
+
     public LogFields(
             int apiType, int uriType, int taskType, boolean callerIsSyncAdapter, long startNanos) {
         this.apiType = apiType;
@@ -78,6 +80,10 @@
         return resultCount;
     }
 
+    public int getUid() {
+        return uid;
+    }
+
     public static final class Builder {
         private int apiType;
         private int uriType;
@@ -88,6 +94,8 @@
         private Uri resultUri;
         private int resultCount;
 
+        private int uid;
+
         private Builder() {
         }
 
@@ -135,12 +143,18 @@
             return this;
         }
 
+        public Builder setUid(int uid) {
+            this.uid = uid;
+            return this;
+        }
+
         public LogFields build() {
             LogFields logFields =
                     new LogFields(apiType, uriType, taskType, callerIsSyncAdapter, startNanos);
             logFields.resultCount = this.resultCount;
             logFields.exception = this.exception;
             logFields.resultUri = this.resultUri;
+            logFields.uid = this.uid;
             return logFields;
         }
     }
diff --git a/src/com/android/providers/contacts/util/LogUtils.java b/src/com/android/providers/contacts/util/LogUtils.java
index 23e2b14..4140964 100644
--- a/src/com/android/providers/contacts/util/LogUtils.java
+++ b/src/com/android/providers/contacts/util/LogUtils.java
@@ -37,6 +37,8 @@
         int INSERT = 2;
         int UPDATE = 3;
         int DELETE = 4;
+        int CALL = 5;
+        int GAL_CALL = 6;
     }
 
     // Keep in sync with ContactsProviderStatus#TaskType in
@@ -54,7 +56,6 @@
 
     private static final int STATSD_LOG_ATOM_ID = 301;
 
-
     // The write methods must be called in the same order as the order of fields in the
     // atom (frameworks/proto_logging/stats/atoms.proto) definition.
     public static void log(LogFields logFields) {
@@ -67,6 +68,8 @@
                 .writeInt(logFields.getResultCount())
                 .writeLong(getLatencyMicros(logFields.getStartNanos()))
                 .writeInt(logFields.getTaskType())
+                .writeInt(0) // Not used yet.
+                .writeInt(logFields.getUid())
                 .usePooledBuffer()
                 .build());
     }
@@ -92,5 +95,3 @@
         return (SystemClock.elapsedRealtimeNanos() - startNanos) / 1000;
     }
 }
-
-
diff --git a/src/com/android/providers/contacts/util/UserUtils.java b/src/com/android/providers/contacts/util/UserUtils.java
index 31ea41a..effe8ab 100644
--- a/src/com/android/providers/contacts/util/UserUtils.java
+++ b/src/com/android/providers/contacts/util/UserUtils.java
@@ -17,9 +17,12 @@
 
 import com.android.providers.contacts.ContactsProvider2;
 
+import android.annotation.SuppressLint;
 import android.app.admin.DevicePolicyManager;
 import android.content.Context;
 import android.content.pm.UserInfo;
+import android.content.pm.UserProperties;
+import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.Log;
 
@@ -78,4 +81,50 @@
         final UserInfo ui = getCorpUserInfo(context);
         return ui == null ? -1 : ui.id;
     }
+
+    @SuppressLint("AndroidFrameworkRequiresPermission")
+    public static boolean shouldUseParentsContacts(Context context) {
+        try {
+            final UserManager userManager = getUserManager(context);
+            final UserProperties userProperties = userManager.getUserProperties(context.getUser());
+            return userProperties.getUseParentsContacts();
+        } catch (IllegalArgumentException e) {
+            Log.w(TAG, "Trying to fetch user properties for non-existing/partial user "
+                    + context.getUser());
+            return false;
+        }
+    }
+
+    @SuppressLint("AndroidFrameworkRequiresPermission")
+    public static boolean shouldUseParentsContacts(Context context, UserHandle userHandle) {
+        try {
+            final UserManager userManager = getUserManager(context);
+            final UserProperties userProperties = userManager.getUserProperties(userHandle);
+            return userProperties.getUseParentsContacts();
+        } catch (IllegalArgumentException e) {
+            Log.w(TAG, "Trying to fetch user properties for non-existing/partial user "
+                    + userHandle);
+            return false;
+        }
+    }
+
+    /**
+     * Checks if the input profile user is the parent of the other user
+     * @return True if user1 is the parent profile of user2, false otherwise
+     */
+    @SuppressLint("AndroidFrameworkRequiresPermission")
+    public static boolean isParentUser(Context context, UserHandle user1, UserHandle user2) {
+        if (user1 == null || user2 == null) return false;
+        final UserManager userManager = getUserManager(context);
+        UserInfo parentUserInfo = userManager.getProfileParent(user2.getIdentifier());
+        return parentUserInfo != null
+                && parentUserInfo.getUserHandle() != null
+                && parentUserInfo.getUserHandle().equals(user1);
+    }
+
+    @SuppressLint("AndroidFrameworkRequiresPermission")
+    public static UserInfo getProfileParentUser(Context context) {
+        final UserManager userManager = getUserManager(context);
+        return userManager.getProfileParent(context.getUserId());
+    }
 }
diff --git a/tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java b/tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java
index 54984d2..e892053 100644
--- a/tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java
+++ b/tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java
@@ -70,6 +70,8 @@
 import com.android.providers.contacts.util.MockClock;
 import com.google.android.collect.Sets;
 
+import java.io.FileInputStream;
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.BitSet;
@@ -1381,6 +1383,25 @@
         assertEquals(timeStamp, time);
     }
 
+    /**
+     * Asserts the equality of two Uri objects, ignoring the order of the query parameters.
+     */
+    protected static void assertUriEquals(Uri expected, Uri actual) {
+        assertEquals(expected.getScheme(), actual.getScheme());
+        assertEquals(expected.getAuthority(), actual.getAuthority());
+        assertEquals(expected.getPath(), actual.getPath());
+        assertEquals(expected.getFragment(), actual.getFragment());
+        Set<String> expectedParameterNames = expected.getQueryParameterNames();
+        Set<String> actualParameterNames = actual.getQueryParameterNames();
+        assertEquals(expectedParameterNames.size(), actualParameterNames.size());
+        assertTrue(expectedParameterNames.containsAll(actualParameterNames));
+        for (String parameterName : expectedParameterNames) {
+            assertEquals(expected.getQueryParameter(parameterName),
+                    actual.getQueryParameter(parameterName));
+        }
+
+    }
+
     protected void setTimeForTest(Long time) {
         Uri uri = Calls.CONTENT_URI.buildUpon()
                 .appendQueryParameter(CallLogProvider.PARAM_KEY_QUERY_FOR_TESTING, "1")
@@ -1400,6 +1421,71 @@
                 getContactsProvider().getProfileProviderForTest().getDatabaseHelper(), values);
     }
 
+    protected class VCardTestUriCreator {
+        private String mLookup1;
+        private String mLookup2;
+
+        public VCardTestUriCreator(String lookup1, String lookup2) {
+            super();
+            mLookup1 = lookup1;
+            mLookup2 = lookup2;
+        }
+
+        public Uri getUri1() {
+            return Uri.withAppendedPath(Contacts.CONTENT_VCARD_URI, mLookup1);
+        }
+
+        public Uri getUri2() {
+            return Uri.withAppendedPath(Contacts.CONTENT_VCARD_URI, mLookup2);
+        }
+
+        public Uri getCombinedUri() {
+            return Uri.withAppendedPath(Contacts.CONTENT_MULTI_VCARD_URI,
+                    Uri.encode(mLookup1 + ":" + mLookup2));
+        }
+    }
+
+    protected VCardTestUriCreator createVCardTestContacts() {
+        final long rawContactId1 = RawContactUtil.createRawContact(mResolver, mAccount,
+                RawContacts.SOURCE_ID, "4:12");
+        DataUtil.insertStructuredName(mResolver, rawContactId1, "John", "Doe");
+
+        final long rawContactId2 = RawContactUtil.createRawContact(mResolver, mAccount,
+                RawContacts.SOURCE_ID, "3:4%121");
+        DataUtil.insertStructuredName(mResolver, rawContactId2, "Jane", "Doh");
+
+        final long contactId1 = queryContactId(rawContactId1);
+        final long contactId2 = queryContactId(rawContactId2);
+        final Uri contact1Uri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId1);
+        final Uri contact2Uri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId2);
+        final String lookup1 =
+                Uri.encode(Contacts.getLookupUri(mResolver, contact1Uri).getPathSegments().get(2));
+        final String lookup2 =
+                Uri.encode(Contacts.getLookupUri(mResolver, contact2Uri).getPathSegments().get(2));
+        return new VCardTestUriCreator(lookup1, lookup2);
+    }
+
+    protected String readToEnd(FileInputStream inputStream) {
+        try {
+            System.out.println("DECLARED INPUT STREAM LENGTH: " + inputStream.available());
+            int ch;
+            StringBuilder stringBuilder = new StringBuilder();
+            int index = 0;
+            while (true) {
+                ch = inputStream.read();
+                System.out.println("READ CHARACTER: " + index + " " + ch);
+                if (ch == -1) {
+                    break;
+                }
+                stringBuilder.append((char)ch);
+                index++;
+            }
+            return stringBuilder.toString();
+        } catch (IOException e) {
+            return null;
+        }
+    }
+
     /**
      * A contact in the database, and the attributes used to create it.  Construct using
      * {@link GoldenContactBuilder#build()}.
diff --git a/tests/src/com/android/providers/contacts/CallLogMigrationTest.java b/tests/src/com/android/providers/contacts/CallLogMigrationTest.java
index d1e8003..74e5d0a 100644
--- a/tests/src/com/android/providers/contacts/CallLogMigrationTest.java
+++ b/tests/src/com/android/providers/contacts/CallLogMigrationTest.java
@@ -93,51 +93,6 @@
                         + " = 1", null));
     }
 
-    public void testMigration() throws IOException {
-        final File sourceDbFile = new File(getTestContext().getCacheDir(), "contacts2src.db");
-        writeAssetFileToDisk("calllogmigration/contacts2.db", sourceDbFile);
-
-        try (final SQLiteDatabase sourceDb = SQLiteDatabase.openDatabase(
-                sourceDbFile.getAbsolutePath(), /* cursorFactory=*/ null,
-                SQLiteDatabase.OPEN_READWRITE)) {
-
-            // Make sure the source tables exist initially.
-            assertTrue(CallLogDatabaseHelper.tableExists(sourceDb, "calls"));
-            assertTrue(CallLogDatabaseHelper.tableExists(sourceDb, "voicemail_status"));
-            // Create the calllog DB to perform the migration.
-            final CallLogDatabaseHelperTestable dbh =
-                    new CallLogDatabaseHelperTestable(getTestContext(), sourceDb);
-
-            final SQLiteDatabase db = dbh.getReadableDatabase();
-
-            // Check the content:
-            // Note what we worry here is basically insertion error due to additional constraints,
-            // renames, etc.  So here, we just check the number of rows and don't check the content.
-            assertEquals(3, DatabaseUtils.longForQuery(db, "select count(*) from " +
-                    CallLogDatabaseHelper.Tables.CALLS, null));
-
-            assertEquals(2, DatabaseUtils.longForQuery(db, "select count(*) from " +
-                    CallLogDatabaseHelper.Tables.VOICEMAIL_STATUS, null));
-
-            assertEquals("123456",
-                    dbh.getProperty(CallLogDatabaseHelper.DbProperties.CALL_LOG_LAST_SYNCED, ""));
-
-            // Test onCreate() step, check each entry with TelephonyComponent in the CALLS has
-            // a new coloumn of Calls.IS_PHONE_ACCOUNT_MIGRATION_PENDING.
-            assertEquals(3,
-                    DatabaseUtils.longForQuery(db, "select count(*) from "
-                            + CallLogDatabaseHelper.Tables.CALLS + " where "
-                                    + Calls.IS_PHONE_ACCOUNT_MIGRATION_PENDING + " = 0", null));
-
-            // Also, the source table should have been removed.
-            assertFalse(CallLogDatabaseHelper.tableExists(sourceDb, "calls"));
-            assertFalse(CallLogDatabaseHelper.tableExists(sourceDb, "voicemail_status"));
-
-            assertEquals("1",
-                    dbh.getProperty(CallLogDatabaseHelper.DbProperties.DATA_MIGRATED, ""));
-        }
-    }
-
     public static final class InMemoryCallLogProviderDbHelperV1 extends SQLiteOpenHelper {
         public InMemoryCallLogProviderDbHelperV1(Context context, int databaseVersion) {
             super(context,
diff --git a/tests/src/com/android/providers/contacts/CloneContactsProvider2Test.java b/tests/src/com/android/providers/contacts/CloneContactsProvider2Test.java
new file mode 100644
index 0000000..26a7814
--- /dev/null
+++ b/tests/src/com/android/providers/contacts/CloneContactsProvider2Test.java
@@ -0,0 +1,504 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.providers.contacts;
+
+import static com.android.providers.contacts.ContactsActor.MockUserManager.CLONE_PROFILE_USER;
+import static com.android.providers.contacts.ContactsActor.MockUserManager.PRIMARY_USER;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.spy;
+
+import android.content.ContentProviderOperation;
+import android.content.ContentProviderResult;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.OperationApplicationException;
+import android.content.res.AssetFileDescriptor;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.provider.CallLog;
+import android.provider.ContactsContract;
+import android.util.SparseArray;
+
+import androidx.test.filters.MediumTest;
+import androidx.test.filters.SdkSuppress;
+
+import org.junit.Assert;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Set;
+
+@MediumTest
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+public class CloneContactsProvider2Test extends BaseContactsProvider2Test {
+
+    private ContactsActor mCloneContactsActor;
+    private SynchronousContactsProvider2 mCloneContactsProvider;
+
+    private SynchronousContactsProvider2 getCloneContactsProvider() {
+        return (SynchronousContactsProvider2) mCloneContactsActor.provider;
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mCloneContactsActor = new ContactsActor(
+                new ContactsActor.AlteringUserContext(getContext(), CLONE_PROFILE_USER.id),
+                getContextPackageName(), SynchronousContactsProvider2.class, getAuthority());
+        mActor.mockUserManager.setUsers(ContactsActor.MockUserManager.PRIMARY_USER,
+                CLONE_PROFILE_USER);
+        mCloneContactsActor.mockUserManager.setUsers(ContactsActor.MockUserManager.PRIMARY_USER,
+                CLONE_PROFILE_USER);
+        mCloneContactsActor.mockUserManager.myUser = CLONE_PROFILE_USER.id;
+        mCloneContactsProvider = spy(getCloneContactsProvider());
+        mCloneContactsProvider.wipeData();
+    }
+
+    private ContentValues getSampleContentValues() {
+        ContentValues values = new ContentValues();
+        values.put(ContactsContract.RawContacts.ACCOUNT_NAME, "test@test.com");
+        values.put(ContactsContract.RawContacts.ACCOUNT_TYPE, "test.com");
+        values.put(ContactsContract.RawContacts.CUSTOM_RINGTONE, "custom");
+        values.put(ContactsContract.RawContacts.STARRED, "1");
+        return values;
+    }
+
+    private void getCloneContactsProviderWithMockedCallToParent(Uri uri) {
+        Cursor primaryProfileCursor = mActor.provider.query(uri,
+                null /* projection */, null /* queryArgs */, null /* cancellationSignal */);
+        assertNotNull(primaryProfileCursor);
+        doReturn(primaryProfileCursor).when(mCloneContactsProvider)
+                .queryContactsProviderForUser(eq(uri), any(), any(), any(), any(),
+                        any(), eq(PRIMARY_USER));
+    }
+
+    private void getCloneContactsProviderWithMockedOpenAssetFileCall(Uri uri)
+            throws FileNotFoundException {
+        AssetFileDescriptor fileDescriptor = mActor.provider.openAssetFile(uri, "r");
+        doReturn(fileDescriptor).when(mCloneContactsProvider)
+                .openAssetFileThroughParentProvider(eq(uri), eq("r"));
+    }
+
+    private String getCursorValue(Cursor c, String columnName) {
+        return c.getString(c.getColumnIndex(columnName));
+    }
+
+    private void assertEqualContentValues(ContentValues contentValues, Cursor cursor) {
+        for (String key: contentValues.getValues().keySet()) {
+            assertEquals(contentValues.get(key), getCursorValue(cursor, key));
+        }
+    }
+
+    private void assertRawContactsCursorEquals(Cursor expectedCursor, Cursor actualCursor,
+            Set<String> columnNames) {
+        assertNotNull(actualCursor);
+        assertEquals(expectedCursor.getCount(), actualCursor.getCount());
+        while (actualCursor.moveToNext()) {
+            expectedCursor.moveToNext();
+            for (String key: columnNames) {
+                assertEquals(getCursorValue(expectedCursor, key),
+                        getCursorValue(actualCursor, key));
+            }
+        }
+    }
+
+    private void assertRejectedApplyBatchResults(ContentProviderResult[] res,
+            ArrayList<ContentProviderOperation> ops) {
+        assertEquals(ops.size(), res.length);
+        for (int i = 0;i < ops.size();i++) {
+            Uri expectedUri = ops.get(i).getUri()
+                    .buildUpon()
+                    .appendPath("0")
+                    .build();
+            assertUriEquals(expectedUri, res[i].uri);
+        }
+    }
+
+    /**
+     * Asserts that no contacts are returned when queried by the given contacts provider
+     */
+    private void assertContactsProviderEmpty(ContactsProvider2 contactsProvider2) {
+        Cursor cursor = contactsProvider2.query(ContactsContract.RawContacts.CONTENT_URI,
+                new String[]{ContactsContract.RawContactsEntity._ID},
+                null /* queryArgs */, null /* cancellationSignal */);
+        assertNotNull(cursor);
+        assertEquals(cursor.getCount(), 0);
+    }
+
+    private long insertRawContactsThroughPrimaryProvider(ContentValues values) {
+        Uri resultUri = mActor.resolver.insert(ContactsContract.RawContacts.CONTENT_URI,
+                values);
+        assertNotNull(resultUri);
+        return ContentUris.parseId(resultUri);
+    }
+
+    public void testAreContactWritesEnabled() {
+        // Check that writes are disabled for clone CP2
+        ContactsProvider2 cloneContactsProvider =
+                (ContactsProvider2) mCloneContactsActor.provider;
+        assertFalse(cloneContactsProvider.areContactWritesEnabled());
+
+        // Check that writes are enabled for primary CP2
+        ContactsProvider2 primaryContactsProvider = (ContactsProvider2) getProvider();
+        assertTrue(primaryContactsProvider.areContactWritesEnabled());
+    }
+
+    public void testCloneContactsProviderInsert() {
+        Uri resultUri =
+                mCloneContactsActor.resolver.insert(ContactsContract.RawContacts.CONTENT_URI,
+                        getSampleContentValues());
+
+        // Here we expect a fakeUri returned to fail silently
+        Uri expectedUri = ContactsContract.RawContacts.CONTENT_URI.buildUpon()
+                .appendPath("0")
+                .build();
+        assertUriEquals(expectedUri, resultUri);
+        // No contacts should be present in both clone and primary providers
+        assertContactsProviderEmpty(getContactsProvider());
+        doReturn(false)
+                .when(mCloneContactsProvider).isAppAllowedToUseParentUsersContacts(any());
+        assertContactsProviderEmpty(mCloneContactsProvider);
+
+    }
+
+    public void testPrimaryContactsProviderInsert() {
+        ContentValues inputContentValues = getSampleContentValues();
+        long rawContactId = insertRawContactsThroughPrimaryProvider(inputContentValues);
+        Cursor cursor = mActor.resolver.query(ContentUris.withAppendedId(
+                ContactsContract.RawContacts.CONTENT_URI, rawContactId),
+                null /* projection */, null /* queryArgs */, null /* cancellationSignal */);
+        assertNotNull(cursor);
+        assertEquals(1, cursor.getCount());
+        assertTrue(cursor.moveToFirst());
+        assertEquals(rawContactId,
+                cursor.getLong(cursor.getColumnIndex(ContactsContract.RawContacts._ID)));
+        assertEqualContentValues(inputContentValues, cursor);
+    }
+
+    public void testCloneContactsProviderUpdate() {
+        // Insert contact through the primary clone provider
+        ContentValues inputContentValues = getSampleContentValues();
+        long rawContactId = insertRawContactsThroughPrimaryProvider(inputContentValues);
+
+        // Update display name in the input content values
+        ContentValues updatedContentValues = getSampleContentValues();
+        updatedContentValues.put(ContactsContract.RawContacts.STARRED,
+                "0");
+        updatedContentValues.put(ContactsContract.RawContacts.CUSTOM_RINGTONE,
+                "beethoven5");
+
+        // Call clone contacts provider update method to update the raw contact inserted earlier
+        int updateResult = mCloneContactsActor.resolver.update(
+                ContentUris.withAppendedId(ContactsContract.RawContacts.CONTENT_URI, rawContactId),
+                updatedContentValues, null /* extras */);
+
+        // Check results, no rows should have been affected
+        assertEquals(0, updateResult);
+
+        // Check values associated with rawContactId by querying the database
+        Cursor cursor = mActor.resolver.query(ContentUris.withAppendedId(
+                        ContactsContract.RawContacts.CONTENT_URI, rawContactId),
+                null /* projection */, null /* queryArgs */, null /* cancellationSignal */);
+        assertNotNull(cursor);
+        assertEquals(1, cursor.getCount());
+        assertTrue(cursor.moveToFirst());
+        assertEqualContentValues(inputContentValues, cursor);
+    }
+
+    public void testCloneContactsProviderDelete() {
+        // Insert contact through the primary clone provider
+        ContentValues inputContentValues = getSampleContentValues();
+        long rawContactId = insertRawContactsThroughPrimaryProvider(inputContentValues);
+
+        // Delete the inserted row through clone provider
+        int deleteResult = mCloneContactsActor.resolver.delete(
+                ContentUris.withAppendedId(ContactsContract.RawContacts.CONTENT_URI, rawContactId),
+                null);
+
+        // Check results, no rows should have been affected
+        assertEquals(0, deleteResult);
+
+        // Check that contact is present in the primary CP2 database
+        Cursor cursor = mActor.resolver.query(ContentUris.withAppendedId(
+                        ContactsContract.RawContacts.CONTENT_URI, rawContactId),
+                null /* projection */, null /* queryArgs */, null /* cancellationSignal */);
+        assertNotNull(cursor);
+        assertEquals(1, cursor.getCount());
+        assertTrue(cursor.moveToFirst());
+        assertEqualContentValues(inputContentValues, cursor);
+    }
+
+    public void testCloneContactsProviderBulkInsert() {
+        int bulkInsertResult =
+                mCloneContactsActor.resolver.bulkInsert(ContactsContract.RawContacts.CONTENT_URI,
+                new ContentValues[]{ getSampleContentValues() });
+
+        // Check results, no rows should have been affected
+        assertEquals(0, bulkInsertResult);
+        // No contacts should be present in both clone and primary providers
+        assertContactsProviderEmpty(getContactsProvider());
+        doReturn(false)
+                .when(mCloneContactsProvider).isAppAllowedToUseParentUsersContacts(any());
+        assertContactsProviderEmpty(mCloneContactsProvider);
+    }
+
+    public void testCloneContactsApplyBatch()
+            throws RemoteException, OperationApplicationException {
+        ArrayList<ContentProviderOperation> ops = new ArrayList<>();
+
+        ops.add(ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
+                .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, null /* value */)
+                .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, null /* value */).build());
+
+        // Phone Number
+        ops.add(ContentProviderOperation
+                .newInsert(ContactsContract.Data.CONTENT_URI)
+                .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
+                .withValue(ContactsContract.Data.MIMETYPE,
+                        ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
+                .withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, "7XXXXXXXXXX")
+                .withValue(ContactsContract.Data.MIMETYPE,
+                        ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
+                .withValue(ContactsContract.CommonDataKinds.Phone.TYPE, "1").build());
+
+        // Display name/Contact name
+        ops.add(ContentProviderOperation
+                .newInsert(ContactsContract.Data.CONTENT_URI)
+                .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
+                .withValue(ContactsContract.Data.MIMETYPE,
+                        ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
+                .withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, "Name")
+                .build());
+
+        // Check results, fake uris should be returned for each of the insert operation
+        ContentProviderResult[] res = mCloneContactsActor.resolver.applyBatch(
+                    ContactsContract.AUTHORITY, ops);
+        assertRejectedApplyBatchResults(res, ops);
+
+        // No contacts should be present in both clone and primary providers
+        assertContactsProviderEmpty(getContactsProvider());
+        doReturn(false)
+                .when(mCloneContactsProvider).isAppAllowedToUseParentUsersContacts(any());
+        assertContactsProviderEmpty(mCloneContactsProvider);
+    }
+
+    public void testCloneContactsCallOperation() {
+        // Query Account Operation
+        Bundle response = mCloneContactsActor.resolver.call(ContactsContract.AUTHORITY_URI,
+                ContactsContract.Settings.QUERY_DEFAULT_ACCOUNT_METHOD, null /* arg */,
+                null /* extras */);
+        assertNotNull(response);
+        assertEquals(Bundle.EMPTY, response);
+
+        // Set account operation
+        Bundle bundle = new Bundle();
+        bundle.putString(ContactsContract.Settings.ACCOUNT_NAME, "test@test.com");
+        bundle.putString(ContactsContract.Settings.ACCOUNT_TYPE, "test.com");
+        Bundle setAccountResponse =
+                mCloneContactsActor.resolver.call(ContactsContract.AUTHORITY_URI,
+                ContactsContract.Settings.SET_DEFAULT_ACCOUNT_METHOD, null /* arg */, bundle);
+        assertNotNull(setAccountResponse);
+        assertEquals(Bundle.EMPTY, response);
+
+        // Authorization URI
+        Uri testUri = ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, 1);
+        final Bundle uriBundle = new Bundle();
+        uriBundle.putParcelable(ContactsContract.Authorization.KEY_URI_TO_AUTHORIZE, testUri);
+        final Bundle authResponse = mCloneContactsActor.resolver.call(
+                ContactsContract.AUTHORITY_URI,
+                ContactsContract.Authorization.AUTHORIZATION_METHOD,
+                null /* arg */,
+                uriBundle);
+        assertNotNull(authResponse);
+        assertEquals(Bundle.EMPTY, authResponse);
+    }
+
+    public void testCloneContactsProviderReads_callerNotInAllowlist() {
+        // Insert raw contact through the primary clone provider
+        ContentValues inputContentValues = getSampleContentValues();
+        long rawContactId = insertRawContactsThroughPrimaryProvider(inputContentValues);
+        Uri uri = ContentUris.withAppendedId(ContactsContract.RawContacts.CONTENT_URI,
+                rawContactId);
+
+        // Mock call to parent profile contacts provider to return the correct result containing all
+        // contacts in the parent profile.
+        getCloneContactsProviderWithMockedCallToParent(uri);
+
+        // Mock call to ensure the caller package is not in the app-cloning allowlist
+        doReturn(false)
+                .when(mCloneContactsProvider).isAppAllowedToUseParentUsersContacts(any());
+
+        // Test clone contacts provider read with the uri of the contact added above
+        mCloneContactsProvider.query(uri,
+                null /* projection */, null /* queryArgs */, null /* cancellationSignal */);
+
+        // Check that the call passed through to the local query instead of redirecting to the
+        // parent provider
+        verify(mCloneContactsProvider, times(1))
+                .queryDirectoryIfNecessary(any(), any(), any(), any(), any(), any());
+    }
+
+    public void testContactsProviderReads_callerInAllowlist() {
+        // Insert raw contact through the primary clone provider
+        ContentValues inputContentValues = getSampleContentValues();
+        long rawContactId = insertRawContactsThroughPrimaryProvider(inputContentValues);
+        Uri uri = ContentUris.withAppendedId(ContactsContract.RawContacts.CONTENT_URI,
+                rawContactId);
+
+        // Mock call to parent profile contacts provider to return the correct result containing all
+        // contacts in the parent profile.
+        getCloneContactsProviderWithMockedCallToParent(uri);
+
+        // Mock call to ensure the caller package is in the app-cloning allowlist
+        doReturn(true)
+                .when(mCloneContactsProvider).isAppAllowedToUseParentUsersContacts(any());
+
+        // Test clone contacts provider read with the uri of the contact added above
+        Cursor cursor = mCloneContactsProvider.query(uri,
+                null /* projection */, null /* queryArgs */, null /* cancellationSignal */);
+
+        // Check that the call did not pass through to the local query and instead redirected to the
+        // parent provider
+        verify(mCloneContactsProvider, times(0))
+                .queryDirectoryIfNecessary(any(), any(), any(), any(), any(), any());
+        assertNotNull(cursor);
+        Cursor primaryProfileCursor = mActor.provider.query(uri,
+                null /* projection */, null /* queryArgs */, null /* cancellationSignal */);
+        assertNotNull(primaryProfileCursor);
+        assertRawContactsCursorEquals(primaryProfileCursor, cursor,
+                inputContentValues.getValues().keySet());
+    }
+
+    public void testQueryPrimaryProfileProvider_callingFromParentUser() {
+        ContentValues inputContentValues = getSampleContentValues();
+        long rawContactId = insertRawContactsThroughPrimaryProvider(inputContentValues);
+        Uri uri = ContentUris.withAppendedId(ContactsContract.RawContacts.CONTENT_URI,
+                rawContactId);
+
+        // Fetch primary contacts provider and call method to redirect to parent provider
+        final ContactsProvider2 primaryCP2 = (ContactsProvider2) getProvider();
+        Cursor cursor = primaryCP2.queryParentProfileContactsProvider(uri,
+                null /* projection */, null /* selection */, null /* selectionArgs */,
+                null /* sortOrder */, null /* cancellationSignal */);
+
+        // Assert that empty cursor is returned
+        assertNotNull(cursor);
+        assertEquals(0, cursor.getCount());
+    }
+
+    public void testQueryPrimaryProfileProvider_incorrectAuthority() {
+        ContentValues inputContentValues = getSampleContentValues();
+        insertRawContactsThroughPrimaryProvider(inputContentValues);
+
+        Assert.assertThrows(IllegalArgumentException.class, () ->
+                mCloneContactsProvider.queryParentProfileContactsProvider(CallLog.CONTENT_URI,
+                null /* projection */, null /* selection */, null /* selectionArgs */,
+                null /* sortOrder */, null /* cancellationSignal */));
+    }
+
+    public void testOpenAssetFileMultiVCard() throws IOException {
+        final VCardTestUriCreator contacts = createVCardTestContacts();
+
+        // Mock call to parent profile contacts provider to return the correct asset file
+        getCloneContactsProviderWithMockedOpenAssetFileCall(contacts.getCombinedUri());
+
+        // Mock call to ensure the caller package is in the app-cloning allowlist
+        doReturn(true)
+                .when(mCloneContactsProvider).isAppAllowedToUseParentUsersContacts(any());
+
+        final AssetFileDescriptor descriptor =
+                mCloneContactsProvider.openAssetFile(contacts.getCombinedUri(), "r");
+        final FileInputStream inputStream = descriptor.createInputStream();
+        String data = readToEnd(inputStream);
+        inputStream.close();
+        descriptor.close();
+
+        // Ensure that the resulting VCard has both contacts
+        assertTrue(data.contains("N:Doe;John;;;"));
+        assertTrue(data.contains("N:Doh;Jane;;;"));
+    }
+
+    public void testOpenAssetFileMultiVCard_callerNotInAllowlist() throws IOException {
+        final VCardTestUriCreator contacts = createVCardTestContacts();
+
+        // Mock call to parent profile contacts provider to return the correct asset file
+        getCloneContactsProviderWithMockedOpenAssetFileCall(contacts.getCombinedUri());
+
+        // Mock call to ensure the caller package is not in the app-cloning allowlist
+        doReturn(false)
+                .when(mCloneContactsProvider).isAppAllowedToUseParentUsersContacts(any());
+
+        final AssetFileDescriptor descriptor =
+                mCloneContactsProvider.openAssetFile(contacts.getCombinedUri(), "r");
+
+        // Check that the call passed through to the local call instead of redirecting to the
+        // parent provider
+        verify(mCloneContactsProvider, times(1))
+                .openAssetFile(eq(contacts.getCombinedUri()), any());
+    }
+
+    public void testIsAppAllowedToUseParentUsersContacts_AppInAllowlistCacheEmpty()
+            throws InterruptedException {
+        String testPackageName = mCloneContactsActor.packageName;
+        int processUid = Binder.getCallingUid();
+        doReturn(true)
+                .when(mCloneContactsProvider)
+                .doesPackageHaveALauncherActivity(eq(testPackageName), any());
+
+        SparseArray<ContactsProvider2.LaunchableCloneAppsCacheEntry> launchableCloneAppsCache =
+                mCloneContactsProvider.getLaunchableCloneAppsCacheForTesting();
+        launchableCloneAppsCache.clear();
+        boolean appAllowedToUseParentUsersContacts =
+                mCloneContactsProvider.isAppAllowedToUseParentUsersContacts(testPackageName);
+        assertTrue(appAllowedToUseParentUsersContacts);
+
+        // Check that the cache has been updated with an entry corresponding to current app uid
+        ContactsProvider2.LaunchableCloneAppsCacheEntry cacheEntry =
+                launchableCloneAppsCache.get(processUid);
+        assertNotNull(cacheEntry);
+        assertEquals(1, launchableCloneAppsCache.size());
+        assertTrue(cacheEntry.doesAppHaveLaunchableActivity);
+    }
+
+    public void testIsAppAllowedToUseParentUsersContacts_AppNotInAllowlistCacheEmtpy() {
+        String testPackageName = mCloneContactsActor.packageName;
+        int processUid = Binder.getCallingUid();
+
+        SparseArray<ContactsProvider2.LaunchableCloneAppsCacheEntry> launchableCloneAppsCache =
+                mCloneContactsProvider.getLaunchableCloneAppsCacheForTesting();
+        launchableCloneAppsCache.clear();
+        assertFalse(mCloneContactsProvider.isAppAllowedToUseParentUsersContacts(testPackageName));
+
+        // Check that the cache has been updated with an entry corresponding to current app uid
+        ContactsProvider2.LaunchableCloneAppsCacheEntry cacheEntry =
+                launchableCloneAppsCache.get(processUid);
+        assertNotNull(cacheEntry);
+        assertEquals(1, launchableCloneAppsCache.size());
+        assertFalse(cacheEntry.doesAppHaveLaunchableActivity);
+    }
+}
diff --git a/tests/src/com/android/providers/contacts/ContactDirectoryManagerTest.java b/tests/src/com/android/providers/contacts/ContactDirectoryManagerTest.java
index fca00af..a4165ce 100644
--- a/tests/src/com/android/providers/contacts/ContactDirectoryManagerTest.java
+++ b/tests/src/com/android/providers/contacts/ContactDirectoryManagerTest.java
@@ -16,8 +16,6 @@
 
 package com.android.providers.contacts;
 
-import static com.android.providers.contacts.ContactsActor.PACKAGE_GREY;
-
 import android.accounts.Account;
 import android.content.ContentValues;
 import android.content.Context;
@@ -38,10 +36,15 @@
 import android.test.suitebuilder.annotation.MediumTest;
 import android.util.Log;
 
+import androidx.test.platform.app.InstrumentationRegistry;
+
 import com.android.providers.contacts.ContactsDatabaseHelper.AggregationExceptionColumns;
 
 import com.google.android.collect.Lists;
 
+import java.util.Arrays;
+import java.util.Set;
+
 /**
  * Unit tests for {@link ContactDirectoryManager}. Run the test like this:
  *
@@ -634,8 +637,17 @@
             return;
         }
 
+        try {
+            InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                    .executeShellCommand("am wait-for-broadcast-idle");
+            Thread.sleep(1000); // wait for the system
+        } catch (Exception ignored) { }
+
         // If installed, getDirectoryProviderPackages() should return it.
-        assertTrue(ContactDirectoryManager.getDirectoryProviderPackages(pm).contains(googleSync));
+        Set<String> dirProviderPackages = ContactDirectoryManager.getDirectoryProviderPackages(pm);
+        assertTrue(googleSync + " package not found in the list of directory provider packages: "
+                        + Arrays.toString(dirProviderPackages.toArray()),
+                        dirProviderPackages.contains(googleSync));
     }
 
     protected PackageInfo createProviderPackage(String packageName, String authority) {
diff --git a/tests/src/com/android/providers/contacts/ContactsActor.java b/tests/src/com/android/providers/contacts/ContactsActor.java
index e3c606e..0d7d9b3 100644
--- a/tests/src/com/android/providers/contacts/ContactsActor.java
+++ b/tests/src/com/android/providers/contacts/ContactsActor.java
@@ -16,6 +16,10 @@
 
 package com.android.providers.contacts;
 
+import static android.content.pm.UserProperties.SHOW_IN_LAUNCHER_WITH_PARENT;
+import static com.android.providers.contacts.ContactsActor.MockUserManager.CLONE_PROFILE_USER;
+import static com.android.providers.contacts.ContactsActor.MockUserManager.PRIMARY_USER;
+
 import static org.mockito.Mockito.when;
 
 import android.accounts.Account;
@@ -25,6 +29,7 @@
 import android.accounts.AuthenticatorException;
 import android.accounts.OnAccountsUpdateListener;
 import android.accounts.OperationCanceledException;
+import android.annotation.NonNull;
 import android.content.ContentProvider;
 import android.content.ContentResolver;
 import android.content.ContentUris;
@@ -37,6 +42,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ProviderInfo;
 import android.content.pm.UserInfo;
+import android.content.pm.UserProperties;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.database.Cursor;
@@ -170,6 +176,8 @@
         public static final UserInfo CORP_USER = createUserInfo("corp", 10, 0,
                 UserInfo.FLAG_MANAGED_PROFILE);
         public static final UserInfo SECONDARY_USER = createUserInfo("2nd", 11, 11, 0);
+        public static final UserInfo CLONE_PROFILE_USER = createUserInfo("clone", 12, 0,
+                UserInfo.FLAG_PROFILE);
 
         /** "My" user.  Set it to change the current user. */
         public int myUser = DEFAULT_USER_ID;
@@ -253,6 +261,18 @@
         public boolean isUserRunning(int userId) {
             return true;
         }
+
+        @Override
+        public UserProperties getUserProperties(@NonNull UserHandle userHandle) {
+            if (CLONE_PROFILE_USER.getUserHandle().equals(userHandle)) {
+                return new UserProperties.Builder()
+                        .setUseParentsContacts(true)
+                        .setShowInLauncher(SHOW_IN_LAUNCHER_WITH_PARENT)
+                        .setStartWithParent(true)
+                        .build();
+            }
+            return new UserProperties.Builder().build();
+        }
     }
 
     private MockTelephonyManager mMockTelephonyManager;
@@ -411,6 +431,15 @@
             }
 
             @Override
+            public UserHandle getUser() {
+                if (mockUserManager != null &&
+                        mockUserManager.getProcessUserId() == CLONE_PROFILE_USER.id) {
+                    return CLONE_PROFILE_USER.getUserHandle();
+                }
+                return PRIMARY_USER.getUserHandle();
+            }
+
+            @Override
             public void sendBroadcast(Intent intent, String receiverPermission) {
                 // Ignore.
             }
diff --git a/tests/src/com/android/providers/contacts/ContactsMockPackageManager.java b/tests/src/com/android/providers/contacts/ContactsMockPackageManager.java
index a9420dd..62f17ea 100644
--- a/tests/src/com/android/providers/contacts/ContactsMockPackageManager.java
+++ b/tests/src/com/android/providers/contacts/ContactsMockPackageManager.java
@@ -24,6 +24,7 @@
 import android.content.pm.ResolveInfo;
 import android.content.res.Resources;
 import android.os.Binder;
+import android.os.UserHandle;
 import android.test.mock.MockPackageManager;
 
 import java.util.ArrayList;
@@ -149,4 +150,15 @@
         }
         return ret;
     }
+
+    @Override
+    public List<ResolveInfo> queryIntentActivitiesAsUser(Intent intent, ResolveInfoFlags flags,
+            UserHandle user) {
+        return new ArrayList<>();
+    }
+
+    @Override
+    public int getPackageUid(String packageName, int flags) throws NameNotFoundException {
+        return 123;
+    }
 }
diff --git a/tests/src/com/android/providers/contacts/ContactsProvider2Test.java b/tests/src/com/android/providers/contacts/ContactsProvider2Test.java
index 09ea19f..69ae0fb 100644
--- a/tests/src/com/android/providers/contacts/ContactsProvider2Test.java
+++ b/tests/src/com/android/providers/contacts/ContactsProvider2Test.java
@@ -8163,50 +8163,6 @@
         assertEquals("default", helper.getProperty("existent1", "default"));
     }
 
-    private class VCardTestUriCreator {
-        private String mLookup1;
-        private String mLookup2;
-
-        public VCardTestUriCreator(String lookup1, String lookup2) {
-            super();
-            mLookup1 = lookup1;
-            mLookup2 = lookup2;
-        }
-
-        public Uri getUri1() {
-            return Uri.withAppendedPath(Contacts.CONTENT_VCARD_URI, mLookup1);
-        }
-
-        public Uri getUri2() {
-            return Uri.withAppendedPath(Contacts.CONTENT_VCARD_URI, mLookup2);
-        }
-
-        public Uri getCombinedUri() {
-            return Uri.withAppendedPath(Contacts.CONTENT_MULTI_VCARD_URI,
-                    Uri.encode(mLookup1 + ":" + mLookup2));
-        }
-    }
-
-    private VCardTestUriCreator createVCardTestContacts() {
-        final long rawContactId1 = RawContactUtil.createRawContact(mResolver, mAccount,
-                RawContacts.SOURCE_ID, "4:12");
-        DataUtil.insertStructuredName(mResolver, rawContactId1, "John", "Doe");
-
-        final long rawContactId2 = RawContactUtil.createRawContact(mResolver, mAccount,
-                RawContacts.SOURCE_ID, "3:4%121");
-        DataUtil.insertStructuredName(mResolver, rawContactId2, "Jane", "Doh");
-
-        final long contactId1 = queryContactId(rawContactId1);
-        final long contactId2 = queryContactId(rawContactId2);
-        final Uri contact1Uri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId1);
-        final Uri contact2Uri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId2);
-        final String lookup1 =
-            Uri.encode(Contacts.getLookupUri(mResolver, contact1Uri).getPathSegments().get(2));
-        final String lookup2 =
-            Uri.encode(Contacts.getLookupUri(mResolver, contact2Uri).getPathSegments().get(2));
-        return new VCardTestUriCreator(lookup1, lookup2);
-    }
-
     public void testQueryMultiVCard() {
         // No need to create any contacts here, because the query for multiple vcards
         // does not go into the database at all
@@ -9688,27 +9644,6 @@
         return c;
     }
 
-    private String readToEnd(FileInputStream inputStream) {
-        try {
-            System.out.println("DECLARED INPUT STREAM LENGTH: " + inputStream.available());
-            int ch;
-            StringBuilder stringBuilder = new StringBuilder();
-            int index = 0;
-            while (true) {
-                ch = inputStream.read();
-                System.out.println("READ CHARACTER: " + index + " " + ch);
-                if (ch == -1) {
-                    break;
-                }
-                stringBuilder.append((char)ch);
-                index++;
-            }
-            return stringBuilder.toString();
-        } catch (IOException e) {
-            return null;
-        }
-    }
-
     private void assertQueryParameter(String uriString, String parameter, String expectedValue) {
         assertEquals(expectedValue, ContactsProvider2.getQueryParameter(
                 Uri.parse(uriString), parameter));
@@ -9908,24 +9843,4 @@
         }
         return false;
     }
-
-
-    /**
-     * Asserts the equality of two Uri objects, ignoring the order of the query parameters.
-     */
-    public static void assertUriEquals(Uri expected, Uri actual) {
-        assertEquals(expected.getScheme(), actual.getScheme());
-        assertEquals(expected.getAuthority(), actual.getAuthority());
-        assertEquals(expected.getPath(), actual.getPath());
-        assertEquals(expected.getFragment(), actual.getFragment());
-        Set<String> expectedParameterNames = expected.getQueryParameterNames();
-        Set<String> actualParameterNames = actual.getQueryParameterNames();
-        assertEquals(expectedParameterNames.size(), actualParameterNames.size());
-        assertTrue(expectedParameterNames.containsAll(actualParameterNames));
-        for (String parameterName : expectedParameterNames) {
-            assertEquals(expected.getQueryParameter(parameterName),
-                    actual.getQueryParameter(parameterName));
-        }
-
-    }
 }
diff --git a/tests/src/com/android/providers/contacts/SynchronousContactsProvider2.java b/tests/src/com/android/providers/contacts/SynchronousContactsProvider2.java
index c2ab74f..ca8cb66 100644
--- a/tests/src/com/android/providers/contacts/SynchronousContactsProvider2.java
+++ b/tests/src/com/android/providers/contacts/SynchronousContactsProvider2.java
@@ -32,7 +32,8 @@
 public class SynchronousContactsProvider2 extends ContactsProvider2 {
     public static final String READ_ONLY_ACCOUNT_TYPE = "ro";
 
-    private static Boolean sDataWiped = false;
+    private static final Object sDataWipedLock = new Object();
+    private static boolean sDataWiped = false;
     private static ContactsDatabaseHelper sDbHelper;
     private Account mAccount;
     private boolean mNetworkNotified;
@@ -93,7 +94,7 @@
     @Override
     public boolean onCreate() {
         boolean created = super.onCreate();
-        synchronized (sDataWiped) {
+        synchronized (sDataWipedLock) {
             if (!sDataWiped) {
                 sDataWiped = true;
                 wipeData();
@@ -178,6 +179,11 @@
     }
 
     @Override
+    protected boolean isContactSharingEnabledForCloneProfile() {
+        return true;
+    }
+
+    @Override
     public boolean isWritableAccountWithDataSet(String accountType) {
         return !READ_ONLY_ACCOUNT_TYPE.equals(accountType);
     }
diff --git a/tests/src/com/android/providers/contacts/enterprise/EnterprisePolicyGuardTest.java b/tests/src/com/android/providers/contacts/enterprise/EnterprisePolicyGuardTest.java
index 2e5241f..5aedd8d 100644
--- a/tests/src/com/android/providers/contacts/enterprise/EnterprisePolicyGuardTest.java
+++ b/tests/src/com/android/providers/contacts/enterprise/EnterprisePolicyGuardTest.java
@@ -17,6 +17,7 @@
 
 import android.app.admin.DevicePolicyManager;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
 import android.net.Uri;
 import android.os.UserHandle;
@@ -31,6 +32,9 @@
 import java.util.Arrays;
 import java.util.List;
 
+import static android.Manifest.permission.INTERACT_ACROSS_USERS;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
@@ -49,6 +53,7 @@
     private static final String CONTACT_EMAIL = "david.green@android.com";
     private static final String CONTACT_PHONE = "+1234567890";
     private static final long DIRECTORY_ID = Directory.ENTERPRISE_DEFAULT;
+    private static final String CALLING_PACKAGE = "package";
 
     private static final Uri URI_CONTACTS_ID_PHOTO =
             Uri.parse("content://com.android.contacts/contacts/" + CONTACT_ID + "/photo");
@@ -179,6 +184,32 @@
         checkCrossProfile(guard, URI_CONTACTS_ID_PHOTO, false);
         checkCrossProfile(guard, URI_CONTACTS_ID_DISPLAY_PHOTO, false);
         checkCrossProfile(guard, URI_OTHER, false);
+
+        // ManagedProfile is paused
+        context = getMockContext(true, true, false);
+        guard = new EnterprisePolicyGuardTestable(context, true);
+        checkCrossProfile(guard, appendRemoteDirectoryId(URI_PHONE_LOOKUP), false);
+        checkCrossProfile(guard, appendRemoteDirectoryId(URI_EMAILS_LOOKUP), false);
+        checkCrossProfile(guard, appendRemoteDirectoryId(URI_CONTACTS_FILTER), false);
+        checkCrossProfile(guard, appendRemoteDirectoryId(URI_PHONES_FILTER), false);
+        checkCrossProfile(guard, appendRemoteDirectoryId(URI_CALLABLES_FILTER), false);
+        checkCrossProfile(guard, appendRemoteDirectoryId(URI_EMAILS_FILTER), false);
+        checkCrossProfile(guard, URI_DIRECTORY_FILE, false);
+
+        // Always allow uri with no directory support.
+        checkCrossProfile(guard, URI_DIRECTORIES, true);
+        checkCrossProfile(guard, URI_DIRECTORIES_ID, true);
+        checkCrossProfile(guard, URI_CONTACTS_ID_PHOTO, true);
+        checkCrossProfile(guard, URI_CONTACTS_ID_DISPLAY_PHOTO, true);
+        checkCrossProfile(guard, URI_OTHER, false);
+
+        // Always allow uri with no remote directory id.
+        checkCrossProfile(guard, URI_PHONE_LOOKUP, true);
+        checkCrossProfile(guard, URI_EMAILS_LOOKUP, true);
+        checkCrossProfile(guard, URI_CONTACTS_FILTER, true);
+        checkCrossProfile(guard, URI_PHONES_FILTER, true);
+        checkCrossProfile(guard, URI_CALLABLES_FILTER, true);
+        checkCrossProfile(guard, URI_EMAILS_FILTER, true);
     }
 
     public void testCrossProfile_userSettingOff() {
@@ -210,6 +241,7 @@
         checkCrossProfile(guard, URI_EMAILS_FILTER, true);
     }
 
+
     private static Uri appendRemoteDirectoryId(Uri uri) {
         return appendDirectoryId(uri, REMOTE_DIRECTORY_ID);
     }
@@ -231,13 +263,13 @@
         }
     }
 
-    private static void checkCrossProfile(EnterprisePolicyGuard guard, Uri uri, boolean expected) {
+    private void checkCrossProfile(EnterprisePolicyGuard guard, Uri uri, boolean expected) {
         if (expected) {
             assertTrue("Expected true but got false for uri: " + uri,
-                    guard.isCrossProfileAllowed(uri));
+                    guard.isCrossProfileAllowed(uri, CALLING_PACKAGE));
         } else {
             assertFalse("Expected false but got true for uri: " + uri,
-                    guard.isCrossProfileAllowed(uri));
+                    guard.isCrossProfileAllowed(uri, CALLING_PACKAGE));
         }
     }
 
@@ -256,11 +288,16 @@
 
 
     private Context getMockContext(boolean isCallerIdEnabled, boolean isContactsSearchEnabled) {
+        return getMockContext(isCallerIdEnabled, isContactsSearchEnabled, true);
+    }
+
+    private Context getMockContext(boolean isCallerIdEnabled, boolean isContactsSearchEnabled,
+            boolean isManagedProfileEnabled) {
         DevicePolicyManager mockDpm = mock(DevicePolicyManager.class);
-        when(mockDpm.getCrossProfileCallerIdDisabled(Matchers.<UserHandle>any()))
-                .thenReturn(!isCallerIdEnabled);
-        when(mockDpm.getCrossProfileContactsSearchDisabled(Matchers.<UserHandle>any()))
-                .thenReturn(!isContactsSearchEnabled);
+        when(mockDpm.hasManagedProfileCallerIdAccess(Matchers.any(),Matchers.any()))
+                .thenReturn(isCallerIdEnabled);
+        when(mockDpm.hasManagedProfileContactsAccess(Matchers.any(),Matchers.any()))
+                .thenReturn(isContactsSearchEnabled);
 
         List<UserInfo> userInfos = MANAGED_USERINFO_LIST;
         UserManager mockUm = mock(UserManager.class);
@@ -268,8 +305,14 @@
         when(mockUm.getUsers()).thenReturn(userInfos);
         when(mockUm.getProfiles(Matchers.anyInt())).thenReturn(userInfos);
         when(mockUm.getProfileParent(WORK_USER_ID)).thenReturn(CURRENT_USER_INFO);
+        when(mockUm.isQuietModeEnabled(UserHandle.of(WORK_USER_ID)))
+                .thenReturn(!isManagedProfileEnabled);
 
-        Context mockContext = new TestMockContext(getContext(), mockDpm, mockUm);
+        PackageManager mockPm = mock(PackageManager.class);
+        when(mockPm.checkPermission(INTERACT_ACROSS_USERS, CALLING_PACKAGE))
+                .thenReturn(PERMISSION_GRANTED);
+
+        Context mockContext = new TestMockContext(getContext(), mockDpm, mockUm, mockPm);
 
         return mockContext;
     }
@@ -278,11 +321,19 @@
         private Context mRealContext;
         private DevicePolicyManager mDpm;
         private UserManager mUm;
+        private PackageManager mPm;
 
-        public TestMockContext(Context realContext, DevicePolicyManager dpm, UserManager um) {
+        public TestMockContext(
+                Context realContext, DevicePolicyManager dpm, UserManager um, PackageManager pm) {
             mRealContext = realContext;
             mDpm = dpm;
             mUm = um;
+            mPm = pm;
+        }
+
+        @Override
+        public PackageManager getPackageManager() {
+            return mPm;
         }
 
         public Object getSystemService(String name) {
diff --git a/tests/src/com/android/providers/contacts/util/UserUtilsTest.java b/tests/src/com/android/providers/contacts/util/UserUtilsTest.java
index 93613cf..c672697 100644
--- a/tests/src/com/android/providers/contacts/util/UserUtilsTest.java
+++ b/tests/src/com/android/providers/contacts/util/UserUtilsTest.java
@@ -18,6 +18,7 @@
 import static com.android.providers.contacts.ContactsActor.PACKAGE_GREY;
 
 import android.content.Context;
+import android.os.UserHandle;
 import android.provider.ContactsContract;
 import android.test.suitebuilder.annotation.SmallTest;
 
@@ -77,5 +78,82 @@
 
         um.myUser = MockUserManager.SECONDARY_USER.id;
         assertEquals(-1, UserUtils.getCorpUserId(c));
+
+        // Primary + clone + corp
+        um.setUsers(MockUserManager.PRIMARY_USER, MockUserManager.CLONE_PROFILE_USER,
+                MockUserManager.CORP_USER);
+
+        um.myUser = MockUserManager.PRIMARY_USER.id;
+        assertEquals(MockUserManager.CORP_USER.id, UserUtils.getCorpUserId(c));
+
+        um.myUser = MockUserManager.CLONE_PROFILE_USER.id;
+        assertEquals(-1, UserUtils.getCorpUserId(c));
+
+        um.myUser = MockUserManager.CORP_USER.id;
+        assertEquals(-1, UserUtils.getCorpUserId(c));
+    }
+
+    public void testShouldUseParentsContacts() {
+        final Context c = mActor.getProviderContext();
+        final MockUserManager um = mActor.mockUserManager;
+
+        um.setUsers(MockUserManager.PRIMARY_USER, MockUserManager.SECONDARY_USER,
+                MockUserManager.CLONE_PROFILE_USER, MockUserManager.CORP_USER);
+
+        um.myUser = MockUserManager.PRIMARY_USER.id;
+        assertFalse(UserUtils.shouldUseParentsContacts(c));
+        assertFalse(UserUtils.shouldUseParentsContacts(c,
+                MockUserManager.PRIMARY_USER.getUserHandle()));
+
+        um.myUser = MockUserManager.SECONDARY_USER.id;
+        assertFalse(UserUtils.shouldUseParentsContacts(c));
+        assertFalse(UserUtils.shouldUseParentsContacts(c,
+                MockUserManager.SECONDARY_USER.getUserHandle()));
+
+        um.myUser = MockUserManager.CORP_USER.id;
+        assertFalse(UserUtils.shouldUseParentsContacts(c));
+        assertFalse(UserUtils.shouldUseParentsContacts(c,
+                MockUserManager.CORP_USER.getUserHandle()));
+
+        um.myUser = MockUserManager.CLONE_PROFILE_USER.id;
+        assertTrue(UserUtils.shouldUseParentsContacts(c));
+        assertTrue(UserUtils.shouldUseParentsContacts(c,
+                MockUserManager.CLONE_PROFILE_USER.getUserHandle()));
+
+    }
+
+    public void testIsParentUser() {
+        final Context c = mActor.getProviderContext();
+        final MockUserManager um = mActor.mockUserManager;
+        um.setUsers(MockUserManager.PRIMARY_USER, MockUserManager.SECONDARY_USER,
+                MockUserManager.CLONE_PROFILE_USER, MockUserManager.CORP_USER);
+
+        UserHandle primaryProfileUserHandle = MockUserManager.PRIMARY_USER.getUserHandle();
+        UserHandle cloneUserHandle = MockUserManager.CLONE_PROFILE_USER.getUserHandle();
+        UserHandle corpUserHandle = MockUserManager.CORP_USER.getUserHandle();
+
+        assertTrue(UserUtils.isParentUser(c, primaryProfileUserHandle, cloneUserHandle));
+        assertTrue(UserUtils.isParentUser(c, primaryProfileUserHandle, corpUserHandle));
+        assertFalse(UserUtils.isParentUser(c, primaryProfileUserHandle, primaryProfileUserHandle));
+        assertFalse(UserUtils.isParentUser(c, cloneUserHandle, cloneUserHandle));
+        assertFalse(UserUtils.isParentUser(c, cloneUserHandle, primaryProfileUserHandle));
+        assertFalse(UserUtils.isParentUser(c, corpUserHandle, primaryProfileUserHandle));
+    }
+
+    public void testGetProfileParent() {
+        final Context c = mActor.getProviderContext();
+        final MockUserManager um = mActor.mockUserManager;
+
+        um.setUsers(MockUserManager.PRIMARY_USER, MockUserManager.SECONDARY_USER,
+                MockUserManager.CLONE_PROFILE_USER, MockUserManager.CORP_USER);
+
+        um.myUser = MockUserManager.PRIMARY_USER.id;
+        assertNull(UserUtils.getProfileParentUser(c));
+
+        um.myUser = MockUserManager.CLONE_PROFILE_USER.id;
+        assertEquals(MockUserManager.PRIMARY_USER, UserUtils.getProfileParentUser(c));
+
+        um.myUser = MockUserManager.CORP_USER.id;
+        assertEquals(MockUserManager.PRIMARY_USER, UserUtils.getProfileParentUser(c));
     }
 }