diff options
12 files changed, 336 insertions, 150 deletions
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 @@ public class ContactsProvider2 extends AbstractContactsProvider 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 @@ public class ContactsProvider2 extends AbstractContactsProvider 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 @@ public class ContactsProvider2 extends AbstractContactsProvider 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 @@ public class ContactsProvider2 extends AbstractContactsProvider 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 @@ public class ContactsProvider2 extends AbstractContactsProvider 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 @@ public class VoicemailContentTable implements VoicemailTable.Delegate { .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 @@ public class VoicemailContentTable implements VoicemailTable.Delegate { .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.PhotoPriorityResolver; 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 @@ public class ContactAggregator { 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.PhotoPriorityResolver; 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 @@ public class ContactAggregator2 { // 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 @@ public class ContactAggregator2 { 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 @@ public class ContactAggregator2 { // 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 @@ public class ContactAggregator2 { 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 @@ public class ContactAggregator2 { * 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 @@ public class ContactAggregator2 { // 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 @@ public class ContactAggregator2 { 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 @@ public class ContactAggregator2 { } // 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 @@ public class ContactAggregator2 { */ 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 @@ public class ContactAggregator2 { } // 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 @@ public final class UserUtils { } /** - * @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 @@ public final class UserUtils { // 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 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test { 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 @@ public class VoicemailProviderTest extends BaseVoicemailProviderTest { 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 @@ public class VoicemailProviderTest extends BaseVoicemailProviderTest { 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 @@ public class ContactAggregatorTest extends BaseContactsProvider2Test { 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 @@ public class UserUtilsTest extends AndroidTestCase { 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)); } } |