Merge "Parse metadata_sync.data to MetadataEntry Object."
diff --git a/src/com/android/providers/contacts/ContactsProvider2.java b/src/com/android/providers/contacts/ContactsProvider2.java
index 47dbf2e..afbec70 100644
--- a/src/com/android/providers/contacts/ContactsProvider2.java
+++ b/src/com/android/providers/contacts/ContactsProvider2.java
@@ -6388,7 +6388,10 @@
                 break;
             }
             case RAW_CONTACT_ENTITIES_CORP: {
-                final int corpUserId = UserUtils.getCorpUserId(getContext());
+                // As it's protected by android.permission.INTERACT_ACROSS_USERS
+                // already and it is used by Bluetooth application, we do not
+                // check caller-id policy
+                final int corpUserId = UserUtils.getCorpUserId(getContext(), false);
                 if (corpUserId < 0) {
                     // No Corp user or policy not allowed, return empty cursor
                     final String[] outputProjection = (projection != null) ? projection
@@ -6564,8 +6567,10 @@
         final Cursor primaryCursor = queryLocal(localUri, projection, selection, selectionArgs,
                 sortOrder, directoryId, null);
         try {
-            // TODO: Maybe we want to have a DPM policy for it
-            final int corpUserId = UserUtils.getCorpUserId(getContext());
+            // As it's protected by android.permission.INTERACT_ACROSS_USERS
+            // already and it is used by Bluetooth application, we do not
+            // check caller-id policy
+            final int corpUserId = UserUtils.getCorpUserId(getContext(), false);
             if (corpUserId < 0) {
                 // No Corp user or policy not allowed
                 return primaryCursor;
@@ -6595,7 +6600,7 @@
     private Cursor queryEnterpriseIfNecessary(Uri localUri, String[] projection, String selection,
             String[] selectionArgs, String sortOrder, String contactIdColumnName) {
 
-        final int corpUserId = UserUtils.getCorpUserId(getContext());
+        final int corpUserId = UserUtils.getCorpUserId(getContext(), true);
 
         // Step 1. Look at the database on the current profile.
         if (VERBOSE_LOGGING) {
@@ -6712,6 +6717,14 @@
                     case Data.CUSTOM_RINGTONE:
                         builder.add(null);
                         break;
+                    case Contacts.LOOKUP_KEY:
+                        final String lookupKey = original.getString(originalColumnIndex);
+                        if (TextUtils.isEmpty(lookupKey)) {
+                            builder.add(null);
+                        } else {
+                            builder.add(Contacts.ENTERPRISE_CONTACT_LOOKUP_PREFIX + lookupKey);
+                        }
+                        break;
                     default:
                         if (outputColumnName.equals(contactIdColumnName)) {
                             // This will be _id if it's PhoneLookup, contacts_id
@@ -8159,7 +8172,7 @@
             throw new IllegalArgumentException(
                     "Photos retrieved by contact ID can only be read.");
         }
-        final int corpUserId = UserUtils.getCorpUserId(getContext());
+        final int corpUserId = UserUtils.getCorpUserId(getContext(), true);
         if (corpUserId < 0) {
             // No corp profile or the currrent profile is not the personal.
             throw new FileNotFoundException(uri.toString());
diff --git a/src/com/android/providers/contacts/VoicemailContentTable.java b/src/com/android/providers/contacts/VoicemailContentTable.java
index 9813eea..7f943e3 100644
--- a/src/com/android/providers/contacts/VoicemailContentTable.java
+++ b/src/com/android/providers/contacts/VoicemailContentTable.java
@@ -68,6 +68,8 @@
             .add(Voicemails.SOURCE_DATA)
             .add(Voicemails.SOURCE_PACKAGE)
             .add(Voicemails.HAS_CONTENT)
+            .add(Voicemails.PHONE_ACCOUNT_COMPONENT_NAME)
+            .add(Voicemails.PHONE_ACCOUNT_ID)
             .add(Voicemails.MIME_TYPE)
             .add(Voicemails.DIRTY)
             .add(Voicemails.DELETED)
@@ -101,6 +103,8 @@
                 .add(Voicemails.HAS_CONTENT)
                 .add(Voicemails.MIME_TYPE)
                 .add(Voicemails._DATA)
+                .add(Voicemails.PHONE_ACCOUNT_COMPONENT_NAME)
+                .add(Voicemails.PHONE_ACCOUNT_ID)
                 .add(Voicemails.DIRTY)
                 .add(Voicemails.DELETED)
                 .add(OpenableColumns.DISPLAY_NAME, createDisplayName(context))
diff --git a/src/com/android/providers/contacts/aggregation/ContactAggregator.java b/src/com/android/providers/contacts/aggregation/ContactAggregator.java
index a970d3b..253adf0 100644
--- a/src/com/android/providers/contacts/aggregation/ContactAggregator.java
+++ b/src/com/android/providers/contacts/aggregation/ContactAggregator.java
@@ -63,6 +63,7 @@
 import com.android.providers.contacts.ReorderingCursorWrapper;
 import com.android.providers.contacts.TransactionContext;
 import com.android.providers.contacts.aggregation.util.CommonNicknameCache;
+import com.android.providers.contacts.aggregation.util.ContactAggregatorHelper;
 import com.android.providers.contacts.aggregation.util.ContactMatcher;
 import com.android.providers.contacts.aggregation.util.ContactMatcher.MatchScore;
 import com.android.providers.contacts.database.ContactsTableUtil;
@@ -1206,37 +1207,7 @@
         findIdPairs(db, buildPhoneMatchingSql(rawContactIds, rawContactIds,  /* countOnly =*/false),
                 matchingRawIdPairs);
 
-        return findConnectedComponents(rawContactIdSet, matchingRawIdPairs);
-    }
-
-    /**
-     * Given a set of raw contact ids {@code rawContactIdSet} and the connection among them
-     * {@code matchingRawIdPairs}, find the connected components.
-     */
-    @VisibleForTesting
-    static Set<Set<Long>> findConnectedComponents(Set<Long> rawContactIdSet, Multimap<Long,
-            Long> matchingRawIdPairs) {
-        Set<Set<Long>> connectedRawContactSets = new HashSet<Set<Long>>();
-        Set<Long> visited = new HashSet<Long>();
-        for (Long id : rawContactIdSet) {
-            if (!visited.contains(id)) {
-                Set<Long> set = new HashSet<Long>();
-                findConnectedComponentForRawContact(matchingRawIdPairs, visited, id, set);
-                connectedRawContactSets.add(set);
-            }
-        }
-        return connectedRawContactSets;
-    }
-
-    private static void findConnectedComponentForRawContact(Multimap<Long, Long> connections,
-            Set<Long> visited, Long rawContactId, Set<Long> results) {
-        visited.add(rawContactId);
-        results.add(rawContactId);
-        for (long match : connections.get(rawContactId)) {
-            if (!visited.contains(match)) {
-                findConnectedComponentForRawContact(connections, visited, match, results);
-            }
-        }
+        return ContactAggregatorHelper.findConnectedComponents(rawContactIdSet, matchingRawIdPairs);
     }
 
     /**
diff --git a/src/com/android/providers/contacts/aggregation/ContactAggregator2.java b/src/com/android/providers/contacts/aggregation/ContactAggregator2.java
index c0f9d91..879dcbc 100644
--- a/src/com/android/providers/contacts/aggregation/ContactAggregator2.java
+++ b/src/com/android/providers/contacts/aggregation/ContactAggregator2.java
@@ -67,6 +67,7 @@
 import com.android.providers.contacts.ReorderingCursorWrapper;
 import com.android.providers.contacts.TransactionContext;
 import com.android.providers.contacts.aggregation.util.CommonNicknameCache;
+import com.android.providers.contacts.aggregation.util.ContactAggregatorHelper;
 import com.android.providers.contacts.aggregation.util.RawContactMatcher;
 import com.android.providers.contacts.aggregation.util.RawContactMatcher.MatchScore;
 import com.android.providers.contacts.aggregation.util.RawContactMatchingCandidates;
@@ -839,7 +840,7 @@
         // Keep doing the following until no new raw contact candidate is found.
         // TODO: may need to cache the matching score to improve performance.
         while (!newIds.isEmpty()) {
-            final Set<Long> tmpIdSet = new HashSet<Long>();
+            final Set<Long> tmpIdSet = new HashSet<>();
             for (long rId : newIds) {
                 final RawContactMatcher rMatcher = new RawContactMatcher();
                 updateMatchScoresForSuggestionsBasedOnDataMatches(db, rId, new MatchCandidateList(),
@@ -974,15 +975,18 @@
                 RawContactMatchingSelectionStatement.SELECT_ID + sql;
     }
 
-    private String buildExceptionMatchingSql(String rawContactIdSet1, String rawContactIdSet2) {
-        return "SELECT " + AggregationExceptions.RAW_CONTACT_ID1 + ", " +
-                AggregationExceptions.RAW_CONTACT_ID2 +
+    private String buildExceptionMatchingSql(String rawContactIdSet1, String rawContactIdSet2,
+            int aggregationType, boolean countOnly) {
+        final String idPairSelection =  "SELECT " + AggregationExceptions.RAW_CONTACT_ID1 + ", " +
+                AggregationExceptions.RAW_CONTACT_ID2;
+        final String sql =
                 " FROM " + Tables.AGGREGATION_EXCEPTIONS +
                 " WHERE " + AggregationExceptions.RAW_CONTACT_ID1 + " IN (" +
                         rawContactIdSet1 + ")" +
                 " AND " + AggregationExceptions.RAW_CONTACT_ID2 + " IN (" + rawContactIdSet2 + ")" +
-                " AND " + AggregationExceptions.TYPE + "=" +
-                        AggregationExceptions.TYPE_KEEP_TOGETHER ;
+                " AND " + AggregationExceptions.TYPE + "=" + aggregationType;
+        return (countOnly) ? RawContactMatchingSelectionStatement.SELECT_COUNT + sql :
+                idPairSelection + sql;
     }
 
     private boolean isFirstColumnGreaterThanZero(SQLiteDatabase db, String query) {
@@ -1004,29 +1008,30 @@
         // Find the connected component based on the aggregation exceptions or
         // identity/email/phone matching for all the raw contacts of [contactId] and the give
         // raw contact.
-        final Set<Long> allIds = new HashSet<Long>();
+        final Set<Long> allIds = new HashSet<>();
         allIds.add(rawContactId);
         allIds.addAll(matchingCandidates.getRawContactIdSet());
         final Set<Set<Long>> connectedRawContactSets = findConnectedRawContacts(db, allIds);
 
         final Map<Long, Long> rawContactsToAccounts = matchingCandidates.getRawContactToAccount();
         rawContactsToAccounts.put(rawContactId, accountId);
-        mergeComponentsWithDisjointAccounts(connectedRawContactSets, rawContactsToAccounts);
-        breakComponentsByExceptions(connectedRawContactSets, matchingCandidates);
+        ContactAggregatorHelper.mergeComponentsWithDisjointAccounts(connectedRawContactSets,
+                rawContactsToAccounts);
+        breakComponentsByExceptions(db, connectedRawContactSets);
 
         // Create new contact for each connected component. Use the first reusable contactId if
         // possible. If no reusable contactId found, create new contact for the connected component.
         // Update aggregate data for all the contactIds touched by this connected component,
         for (Set<Long> connectedRawContactIds : connectedRawContactSets) {
             Long contactId = null;
-            Set<Long> cidsNeedToBeUpdated = new HashSet<Long>();
+            Set<Long> cidsNeedToBeUpdated = new HashSet<>();
             if (connectedRawContactIds.contains(rawContactId)) {
                 // If there is no other raw contacts aggregated with the given raw contact currently
                 // or all the raw contacts in [currentCidForRawContact] are still in the same
                 // connected component, we might as well reuse it.
                 if (currentCidForRawContact != 0 &&
                         (currentContactContentsCount == 0) ||
-                        canBeReused(currentCidForRawContact,connectedRawContactIds)) {
+                        canBeReused(db, currentCidForRawContact, connectedRawContactIds)) {
                     contactId = currentCidForRawContact;
                 } else if (currentCidForRawContact != 0){
                     cidsNeedToBeUpdated.add(currentCidForRawContact);
@@ -1036,7 +1041,7 @@
                 for (Long connectedRawContactId : connectedRawContactIds) {
                     Long currentContactId = matchingCandidates.getContactId(connectedRawContactId);
                     if (!foundContactId && currentContactId != null &&
-                            canBeReused(currentContactId, connectedRawContactIds)) {
+                            canBeReused(db, currentContactId, connectedRawContactIds)) {
                         contactId = currentContactId;
                         foundContactId = true;
                     } else {
@@ -1071,26 +1076,48 @@
      * connectedRawContactIds. If connectedRawContactIds set contains all the raw contacts
      * currently aggregated under contactId, return true; Otherwise, return false.
      */
-    private boolean canBeReused(Long contactId, Set<Long> connectedRawContactIds) {
-        throw new UnsupportedOperationException();
+    private boolean canBeReused(SQLiteDatabase db, Long contactId,
+            Set<Long> connectedRawContactIds) {
+        final String sql = "SELECT " + RawContactsColumns.CONCRETE_ID + " FROM " +
+                Tables.RAW_CONTACTS + " WHERE " + RawContacts.CONTACT_ID + "=? AND " +
+                RawContacts.DELETED + "=0";
+        mSelectionArgs1[0] = String.valueOf(contactId);
+        final Cursor cursor = db.rawQuery(sql, mSelectionArgs1);
+        try {
+            cursor.moveToPosition(-1);
+            while (cursor.moveToNext()) {
+                if (!connectedRawContactIds.contains(cursor.getLong(0))) {
+                    return false;
+                }
+            }
+        } finally {
+            cursor.close();
+        }
+        return true;
     }
 
     /**
      * Separate all the raw_contacts which has "SEPARATE" aggregation exception to another
      * raw_contacts in the same component.
      */
-    private void breakComponentsByExceptions(Set<Set<Long>> connectedRawContactSets,
-            RawContactMatchingCandidates matchingCandidates) {
-        throw new UnsupportedOperationException();
-    }
-
-    /**
-     * If two connected components have disjoint accounts, merge them.
-     * If there is any uncertainty, keep them separate.
-     */
-    private void mergeComponentsWithDisjointAccounts(Set<Set<Long>> connectedRawContactSets,
-            Map<Long, Long> matchingCandidates) {
-        throw new UnsupportedOperationException();
+    private void breakComponentsByExceptions(SQLiteDatabase db,
+            Set<Set<Long>> connectedRawContacts) {
+        final Set<Set<Long>> tmpSets = new HashSet<>(connectedRawContacts);
+        for (Set<Long> component : tmpSets) {
+            final String rawContacts = TextUtils.join(",", component);
+            // If "SEPARATE" exception is found inside an connected component [component],
+            // remove the [component] from [connectedRawContacts], and create a new connected
+            // component for each raw contact of [component] and add to [connectedRawContacts].
+            if (isFirstColumnGreaterThanZero(db, buildExceptionMatchingSql(rawContacts, rawContacts,
+                    AggregationExceptions.TYPE_KEEP_SEPARATE, /* countOnly =*/true))) {
+                connectedRawContacts.remove(component);
+                for (Long rId : component) {
+                    final Set<Long> s= new HashSet<>();
+                    s.add(rId);
+                    connectedRawContacts.add(s);
+                }
+            }
+        }
     }
 
     /**
@@ -1101,7 +1128,8 @@
         // Connections between two raw contacts
        final Multimap<Long, Long> matchingRawIdPairs = HashMultimap.create();
         String rawContactIds = TextUtils.join(",", rawContactIdSet);
-        findIdPairs(db, buildExceptionMatchingSql(rawContactIds, rawContactIds),
+        findIdPairs(db, buildExceptionMatchingSql(rawContactIds, rawContactIds,
+                AggregationExceptions.TYPE_KEEP_TOGETHER,  /* countOnly =*/false),
                 matchingRawIdPairs);
         findIdPairs(db, buildIdentityMatchingSql(rawContactIds, rawContactIds,
                 /* isIdentityMatching =*/ true, /* countOnly =*/false), matchingRawIdPairs);
@@ -1110,37 +1138,7 @@
         findIdPairs(db, buildPhoneMatchingSql(rawContactIds, rawContactIds,  /* countOnly =*/false),
                 matchingRawIdPairs);
 
-        return findConnectedComponents(rawContactIdSet, matchingRawIdPairs);
-    }
-
-    /**
-     * Given a set of raw contact ids {@code rawContactIdSet} and the connection among them
-     * {@code matchingRawIdPairs}, find the connected components.
-     */
-    @VisibleForTesting
-    static Set<Set<Long>> findConnectedComponents(Set<Long> rawContactIdSet, Multimap<Long,
-            Long> matchingRawIdPairs) {
-        Set<Set<Long>> connectedRawContactSets = new HashSet<Set<Long>>();
-        Set<Long> visited = new HashSet<Long>();
-        for (Long id : rawContactIdSet) {
-            if (!visited.contains(id)) {
-                Set<Long> set = new HashSet<Long>();
-                findConnectedComponentForRawContact(matchingRawIdPairs, visited, id, set);
-                connectedRawContactSets.add(set);
-            }
-        }
-        return connectedRawContactSets;
-    }
-
-    private static void findConnectedComponentForRawContact(Multimap<Long, Long> connections,
-            Set<Long> visited, Long rawContactId, Set<Long> results) {
-        visited.add(rawContactId);
-        results.add(rawContactId);
-        for (long match : connections.get(rawContactId)) {
-            if (!visited.contains(match)) {
-                findConnectedComponentForRawContact(connections, visited, match, results);
-            }
-        }
+        return ContactAggregatorHelper.findConnectedComponents(rawContactIdSet, matchingRawIdPairs);
     }
 
     /**
@@ -1305,7 +1303,7 @@
     }
 
     // A set of raw contact IDs for which there are aggregation exceptions
-    private final HashSet<Long> mAggregationExceptionIds = new HashSet<Long>();
+    private final HashSet<Long> mAggregationExceptionIds = new HashSet<>();
     private boolean mAggregationExceptionIdsValid;
 
     public void invalidateAggregationExceptionCache() {
@@ -1771,7 +1769,7 @@
      */
     private void lookupApproximateNameMatches(SQLiteDatabase db, MatchCandidateList candidates,
             RawContactMatcher matcher) {
-        HashSet<String> firstLetters = new HashSet<String>();
+        HashSet<String> firstLetters = new HashSet<>();
         for (int i = 0; i < candidates.mCount; i++) {
             final NameMatchCandidate candidate = candidates.mList.get(i);
             if (candidate.mName.length() >= 2) {
@@ -2572,7 +2570,7 @@
         }
 
         // Run a query and find ids of best matching contacts satisfying the filter (if any)
-        HashSet<Long> foundIds = new HashSet<Long>();
+        HashSet<Long> foundIds = new HashSet<>();
         Cursor cursor = db.query(qb.getTables(), ContactIdQuery.COLUMNS, sb.toString(),
                 null, null, null, null);
         try {
diff --git a/src/com/android/providers/contacts/aggregation/util/ContactAggregatorHelper.java b/src/com/android/providers/contacts/aggregation/util/ContactAggregatorHelper.java
new file mode 100644
index 0000000..983cd60
--- /dev/null
+++ b/src/com/android/providers/contacts/aggregation/util/ContactAggregatorHelper.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.providers.contacts.aggregation.util;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Multimap;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Helper class for contacts aggregation.
+ */
+public class ContactAggregatorHelper {
+
+    private ContactAggregatorHelper() {}
+
+    /**
+     * If two connected components have disjoint accounts, merge them.
+     * If there is any uncertainty, keep them separate.
+     */
+    @VisibleForTesting
+    public static void mergeComponentsWithDisjointAccounts(Set<Set<Long>> connectedRawContactSets,
+            Map<Long, Long> rawContactsToAccounts) {
+        // Index to rawContactIds mapping
+        final Map<Integer, Set<Long>> rawContactIds = new HashMap<>();
+        // AccountId to indices mapping
+        final Map<Long, Set<Integer>> accounts = new HashMap<>();
+
+        int index = 0;
+        for (Set<Long> rIds : connectedRawContactSets) {
+            rawContactIds.put(index, rIds);
+            for (Long rId : rIds) {
+                long acctId = rawContactsToAccounts.get(rId);
+                Set<Integer> s = accounts.get(acctId);
+                if (s == null) {
+                    s = new HashSet<Integer>();
+                }
+                s.add(index);
+                accounts.put(acctId, s);
+            }
+            index++;
+        }
+        final Set<Long> mergedSet = new HashSet<>();
+        connectedRawContactSets.clear();
+        for (Long accountId : accounts.keySet()) {
+            final Set<Integer> s = accounts.get(accountId);
+            if (s.size() > 1) {
+                for (Integer i : s) {
+                    final Set<Long> rIdSet = rawContactIds.get(i);
+                    if (rIdSet != null) {
+                        connectedRawContactSets.add(rawContactIds.get(i));
+                        rawContactIds.remove(i);
+                    }
+                }
+            } else {
+                Set<Long> ids = rawContactIds.get(Iterables.getOnlyElement(s));
+                if (ids != null) {
+                    mergedSet.addAll(ids);
+                }
+            }
+        }
+        connectedRawContactSets.add(mergedSet);
+    }
+
+    /**
+     * Given a set of raw contact ids {@code rawContactIdSet} and the connection among them
+     * {@code matchingRawIdPairs}, find the connected components.
+     */
+    @VisibleForTesting
+    public static Set<Set<Long>> findConnectedComponents(Set<Long> rawContactIdSet, Multimap<Long,
+            Long> matchingRawIdPairs) {
+        Set<Set<Long>> connectedRawContactSets = new HashSet<>();
+        Set<Long> visited = new HashSet<>();
+        for (Long id : rawContactIdSet) {
+            if (!visited.contains(id)) {
+                Set<Long> set = new HashSet<>();
+                findConnectedComponentForRawContact(matchingRawIdPairs, visited, id, set);
+                connectedRawContactSets.add(set);
+            }
+        }
+        return connectedRawContactSets;
+    }
+
+    private static void findConnectedComponentForRawContact(Multimap<Long, Long> connections,
+            Set<Long> visited, Long rawContactId, Set<Long> results) {
+        visited.add(rawContactId);
+        results.add(rawContactId);
+        for (long match : connections.get(rawContactId)) {
+            if (!visited.contains(match)) {
+                findConnectedComponentForRawContact(connections, visited, match, results);
+            }
+        }
+    }
+}
diff --git a/src/com/android/providers/contacts/util/UserUtils.java b/src/com/android/providers/contacts/util/UserUtils.java
index 74fd2e7..8ae770a 100644
--- a/src/com/android/providers/contacts/util/UserUtils.java
+++ b/src/com/android/providers/contacts/util/UserUtils.java
@@ -45,10 +45,13 @@
     }
 
     /**
-     * @return the user ID of the corp user that is linked to the current user, if any.
-     * If there's no such user or cross-user contacts access is disallowed by policy, returns -1.
+     * @param enforceCallerIdCheck True if we want to enforce cross profile
+     *            caller-id device policy.
+     * @return the user ID of the corp user that is linked to the current user,
+     *         if any. If there's no such user or cross-user contacts access is
+     *         disallowed by policy, returns -1.
      */
-    public static int getCorpUserId(Context context) {
+    public static int getCorpUserId(Context context, boolean enforceCallerIdCheck) {
         final UserManager um = getUserManager(context);
         if (um == null) {
             Log.e(TAG, "No user manager service found");
@@ -75,7 +78,8 @@
                 // Check if profile is blocking calling id.
                 // TODO DevicePolicyManager is not mockable -- the constructor is private.
                 // Test it somehow.
-                if (getDevicePolicyManager(context)
+                if (enforceCallerIdCheck
+                        && getDevicePolicyManager(context)
                         .getCrossProfileCallerIdDisabled(ui.getUserHandle())) {
                     if (VERBOSE_LOGGING) {
                         Log.v(TAG, "Enterprise caller-id disabled for user " + ui.id);
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
index d0e3810..91fceae 100644
--- a/tests/AndroidManifest.xml
+++ b/tests/AndroidManifest.xml
@@ -20,10 +20,12 @@
 
     <uses-permission android:name="android.permission.READ_CONTACTS" />
     <uses-permission android:name="android.permission.WRITE_CONTACTS" />
+    <uses-permission android:name="android.permission.GET_ACCOUNTS" />
+    <uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
 
     <application>
         <uses-library android:name="android.test.runner" />
-        
+
         <!-- Mock contacts sync adapter -->
         <service android:name=".MockSyncAdapter" android:exported="true">
             <meta-data android:name="android.provider.CONTACTS_STRUCTURE"
diff --git a/tests/src/com/android/providers/contacts/ContactsProvider2Test.java b/tests/src/com/android/providers/contacts/ContactsProvider2Test.java
index 6a175ed..0e3c68b 100644
--- a/tests/src/com/android/providers/contacts/ContactsProvider2Test.java
+++ b/tests/src/com/android/providers/contacts/ContactsProvider2Test.java
@@ -2024,7 +2024,7 @@
         rewritten.moveToNext();
         column = 0;
         assertEquals(1000000010L, rewritten.getLong(column++)); // With offset.
-        assertEquals("key", rewritten.getString(column++));
+        assertEquals("c-key", rewritten.getString(column++));
         assertEquals("name", rewritten.getString(column++));
         assertEquals(123, rewritten.getInt(column++));
         assertEquals(456, rewritten.getInt(column++));
diff --git a/tests/src/com/android/providers/contacts/VoicemailProviderTest.java b/tests/src/com/android/providers/contacts/VoicemailProviderTest.java
index 1d3ac8a..4f79837 100644
--- a/tests/src/com/android/providers/contacts/VoicemailProviderTest.java
+++ b/tests/src/com/android/providers/contacts/VoicemailProviderTest.java
@@ -58,7 +58,7 @@
             Calls.COUNTRY_ISO
     };
     /** Total number of columns exposed by voicemail provider. */
-    private static final int NUM_VOICEMAIL_FIELDS = 16;
+    private static final int NUM_VOICEMAIL_FIELDS = 18;
 
     @Override
     protected void setUp() throws Exception {
@@ -115,6 +115,8 @@
         values.put(Voicemails.STATE, 2);
         values.put(Voicemails.HAS_CONTENT, 1);
         values.put(Voicemails.SOURCE_DATA, "foo");
+        values.put(Voicemails.PHONE_ACCOUNT_COMPONENT_NAME, "dummy_name");
+        values.put(Voicemails.PHONE_ACCOUNT_ID, "dummy_account");
         int count = mResolver.update(uri, values, null, null);
         assertEquals(1, count);
         assertStoredValues(uri, values);
diff --git a/tests/src/com/android/providers/contacts/aggregation/ContactAggregatorTest.java b/tests/src/com/android/providers/contacts/aggregation/ContactAggregatorTest.java
index 43fc488..12f0ba8 100644
--- a/tests/src/com/android/providers/contacts/aggregation/ContactAggregatorTest.java
+++ b/tests/src/com/android/providers/contacts/aggregation/ContactAggregatorTest.java
@@ -1765,44 +1765,6 @@
         assertTrue(queryContactId(newId) > 0);
     }
 
-    public void testFindConnectedRawContacts() {
-        Set<Long> rawContactIdSet = new HashSet<Long>();
-        rawContactIdSet.addAll(Arrays.asList(1l, 2l, 3l, 4l, 5l, 6l, 7l, 8l, 9l));
-
-        Multimap<Long, Long> matchingrawIdPairs = HashMultimap.create();
-        matchingrawIdPairs.put(1l, 2l);
-        matchingrawIdPairs.put(2l, 1l);
-
-        matchingrawIdPairs.put(1l, 7l);
-        matchingrawIdPairs.put(7l, 1l);
-
-        matchingrawIdPairs.put(2l, 3l);
-        matchingrawIdPairs.put(3l, 2l);
-
-        matchingrawIdPairs.put(2l, 8l);
-        matchingrawIdPairs.put(8l, 2l);
-
-        matchingrawIdPairs.put(8l, 9l);
-        matchingrawIdPairs.put(9l, 8l);
-
-        matchingrawIdPairs.put(4l, 5l);
-        matchingrawIdPairs.put(5l, 4l);
-
-        Set<Set<Long>> actual = ContactAggregator.findConnectedComponents(rawContactIdSet,
-                matchingrawIdPairs);
-
-        Set<Set<Long>> expected = new HashSet<Set<Long>>();
-        Set<Long> result1 = new HashSet<Long>();
-        result1.addAll(Arrays.asList(1l, 2l, 3l, 7l, 8l, 9l));
-        Set<Long> result2 = new HashSet<Long>();
-        result2.addAll(Arrays.asList(4l, 5l));
-        Set<Long> result3 = new HashSet<Long>();
-        result3.addAll(Arrays.asList(6l));
-        expected.addAll(Arrays.asList(result1, result2, result3));
-
-        assertEquals(expected, actual);
-    }
-
     private void assertSuggestions(long contactId, long... suggestions) {
         final Uri aggregateUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
         Uri uri = Uri.withAppendedPath(aggregateUri,
diff --git a/tests/src/com/android/providers/contacts/aggregation/util/ContactAggregatorHelperTest.java b/tests/src/com/android/providers/contacts/aggregation/util/ContactAggregatorHelperTest.java
new file mode 100644
index 0000000..4373a76
--- /dev/null
+++ b/tests/src/com/android/providers/contacts/aggregation/util/ContactAggregatorHelperTest.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.providers.contacts.aggregation.util;
+
+import android.test.MoreAsserts;
+import android.test.suitebuilder.annotation.SmallTest;
+import com.google.android.collect.Sets;
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Multimap;
+import junit.framework.TestCase;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+@SmallTest
+public class ContactAggregatorHelperTest extends TestCase {
+
+    private static final long ACCOUNT_1 = 1;
+    private static final long ACCOUNT_2 = 2;
+    private static final long ACCOUNT_3 = 3;
+    private static final long ACCOUNT_4 = 4;
+    private static final long ACCOUNT_5 = 5;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+    }
+
+    public void testMergeComponentsWithDisjointAccounts() {
+        Set<Set<Long>> connectedRawContactSets = new HashSet<>();
+        Map<Long, Long> rawContactsToAccounts = new HashMap<>();
+        for (long i = 100; i < 108; ) {
+            Set<Long> rawContactSet = new HashSet<>();
+            rawContactSet.add(i++);
+            rawContactSet.add(i++);
+            connectedRawContactSets.add(rawContactSet);
+        }
+
+        for (long i = 100; i < 103; i++) {
+            rawContactsToAccounts.put(i, ACCOUNT_1);
+        }
+        rawContactsToAccounts.put(100l, ACCOUNT_1);
+        rawContactsToAccounts.put(101l, ACCOUNT_1);
+        rawContactsToAccounts.put(102l, ACCOUNT_1);
+        rawContactsToAccounts.put(103l, ACCOUNT_2);
+        rawContactsToAccounts.put(104l, ACCOUNT_3);
+        rawContactsToAccounts.put(105l, ACCOUNT_4);
+        rawContactsToAccounts.put(106l, ACCOUNT_5);
+        rawContactsToAccounts.put(107l, ACCOUNT_5);
+        // Component1: [rawContactId=100, accountId=1; raw_contactId=101, accountId=1]
+        // Component2: [rawContactId=102, accountId=1; raw_contactId=103, accountId=2]
+        // Component3: [rawContactId=104, accountId=3; raw_contactId=105, accountId=4]
+        // Component4: [rawContactId=106, accountId=5; raw_contactId=107, accountId=5]
+        // Component3 and component4 can be merged because they have disjoint accounts, but cannot
+        // merge with either component1 or component2 due to the uncertainty.
+
+        ContactAggregatorHelper.mergeComponentsWithDisjointAccounts(connectedRawContactSets,
+                rawContactsToAccounts);
+
+        MoreAsserts.assertContentsInAnyOrder(connectedRawContactSets, Sets.newHashSet(100l,
+                101l), Sets.newHashSet(102l, 103l), Sets.newHashSet(104l, 105l, 106l, 107l));
+    }
+
+    public void testFindConnectedRawContacts() {
+        Set<Long> rawContactIdSet = new HashSet<>();
+        rawContactIdSet.addAll(Arrays.asList(1l, 2l, 3l, 4l, 5l, 6l, 7l, 8l, 9l));
+
+        Multimap<Long, Long> matchingrawIdPairs = HashMultimap.create();
+        matchingrawIdPairs.put(1l, 2l);
+        matchingrawIdPairs.put(2l, 1l);
+
+        matchingrawIdPairs.put(1l, 7l);
+        matchingrawIdPairs.put(7l, 1l);
+
+        matchingrawIdPairs.put(2l, 3l);
+        matchingrawIdPairs.put(3l, 2l);
+
+        matchingrawIdPairs.put(2l, 8l);
+        matchingrawIdPairs.put(8l, 2l);
+
+        matchingrawIdPairs.put(8l, 9l);
+        matchingrawIdPairs.put(9l, 8l);
+
+        matchingrawIdPairs.put(4l, 5l);
+        matchingrawIdPairs.put(5l, 4l);
+
+        Set<Set<Long>> actual = ContactAggregatorHelper.findConnectedComponents(rawContactIdSet,
+                matchingrawIdPairs);
+
+        Set<Set<Long>> expected = new HashSet<>();
+        Set<Long> result1 = new HashSet<>();
+        result1.addAll(Arrays.asList(1l, 2l, 3l, 7l, 8l, 9l));
+        Set<Long> result2 = new HashSet<>();
+        result2.addAll(Arrays.asList(4l, 5l));
+        Set<Long> result3 = new HashSet<>();
+        result3.addAll(Arrays.asList(6l));
+        expected.addAll(Arrays.asList(result1, result2, result3));
+
+        assertEquals(expected, actual);
+    }
+}
diff --git a/tests/src/com/android/providers/contacts/util/UserUtilsTest.java b/tests/src/com/android/providers/contacts/util/UserUtilsTest.java
index 4417ad5..7482ee6 100644
--- a/tests/src/com/android/providers/contacts/util/UserUtilsTest.java
+++ b/tests/src/com/android/providers/contacts/util/UserUtilsTest.java
@@ -47,37 +47,37 @@
         final MockUserManager um = mActor.mockUserManager;
 
         // No corp user.  Primary only.
-        assertEquals(-1, UserUtils.getCorpUserId(c));
+        assertEquals(-1, UserUtils.getCorpUserId(c, false));
 
         // Primary + corp
         um.setUsers(MockUserManager.PRIMARY_USER, MockUserManager.CORP_USER);
 
         um.myUser = MockUserManager.PRIMARY_USER.id;
-        assertEquals(MockUserManager.CORP_USER.id, UserUtils.getCorpUserId(c));
+        assertEquals(MockUserManager.CORP_USER.id, UserUtils.getCorpUserId(c, false));
 
         um.myUser = MockUserManager.CORP_USER.id;
-        assertEquals(-1, UserUtils.getCorpUserId(c));
+        assertEquals(-1, UserUtils.getCorpUserId(c, false));
 
         // Primary + secondary + corp
         um.setUsers(MockUserManager.PRIMARY_USER, MockUserManager.SECONDARY_USER,
                 MockUserManager.CORP_USER);
 
         um.myUser = MockUserManager.PRIMARY_USER.id;
-        assertEquals(MockUserManager.CORP_USER.id, UserUtils.getCorpUserId(c));
+        assertEquals(MockUserManager.CORP_USER.id, UserUtils.getCorpUserId(c, false));
 
         um.myUser = MockUserManager.CORP_USER.id;
-        assertEquals(-1, UserUtils.getCorpUserId(c));
+        assertEquals(-1, UserUtils.getCorpUserId(c, false));
 
         um.myUser = MockUserManager.SECONDARY_USER.id;
-        assertEquals(-1, UserUtils.getCorpUserId(c));
+        assertEquals(-1, UserUtils.getCorpUserId(c, false));
 
         // Primary + secondary
         um.setUsers(MockUserManager.PRIMARY_USER, MockUserManager.SECONDARY_USER);
 
         um.myUser = MockUserManager.PRIMARY_USER.id;
-        assertEquals(-1, UserUtils.getCorpUserId(c));
+        assertEquals(-1, UserUtils.getCorpUserId(c, false));
 
         um.myUser = MockUserManager.SECONDARY_USER.id;
-        assertEquals(-1, UserUtils.getCorpUserId(c));
+        assertEquals(-1, UserUtils.getCorpUserId(c, false));
     }
 }