diff options
Diffstat (limited to 'src')
6 files changed, 203 insertions, 101 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); |