diff options
3 files changed, 218 insertions, 139 deletions
diff --git a/java/com/android/dialer/phonenumbercache/ContactInfoHelper.java b/java/com/android/dialer/phonenumbercache/ContactInfoHelper.java index b680bd57d..f501792f9 100644 --- a/java/com/android/dialer/phonenumbercache/ContactInfoHelper.java +++ b/java/com/android/dialer/phonenumbercache/ContactInfoHelper.java @@ -337,30 +337,30 @@ public class ContactInfoHelper { return ContactInfo.EMPTY; } - Cursor phoneLookupCursor = null; - try { - String[] projection = PhoneQuery.getPhoneLookupProjection(uri); - phoneLookupCursor = mContext.getContentResolver().query(uri, projection, null, null, null); - } catch (NullPointerException e) { - LogUtil.e("ContactInfoHelper.lookupContactFromUri", "phone lookup", e); - // Trap NPE from pre-N CP2 - return null; - } - if (phoneLookupCursor == null) { - LogUtil.d("ContactInfoHelper.lookupContactFromUri", "phoneLookupCursor is null"); - return null; - } + try (Cursor phoneLookupCursor = + mContext + .getContentResolver() + .query(uri, PhoneQuery.getPhoneLookupProjection(uri), null, null, null)) { + if (phoneLookupCursor == null) { + LogUtil.d("ContactInfoHelper.lookupContactFromUri", "phoneLookupCursor is null"); + return null; + } - try { if (!phoneLookupCursor.moveToFirst()) { return ContactInfo.EMPTY; } - String lookupKey = phoneLookupCursor.getString(PhoneQuery.LOOKUP_KEY); - ContactInfo contactInfo = createPhoneLookupContactInfo(phoneLookupCursor, lookupKey); + + Cursor matchedCursor = + PhoneNumberHelper.getCursorMatchForContactLookupUri( + phoneLookupCursor, PhoneQuery.MATCHED_NUMBER, uri); + if (matchedCursor == null) { + return ContactInfo.EMPTY; + } + + String lookupKey = matchedCursor.getString(PhoneQuery.LOOKUP_KEY); + ContactInfo contactInfo = createPhoneLookupContactInfo(matchedCursor, lookupKey); fillAdditionalContactInfo(mContext, contactInfo); return contactInfo; - } finally { - phoneLookupCursor.close(); } } diff --git a/java/com/android/dialer/phonenumberutil/PhoneNumberHelper.java b/java/com/android/dialer/phonenumberutil/PhoneNumberHelper.java index cc9b73081..84cbb6947 100644 --- a/java/com/android/dialer/phonenumberutil/PhoneNumberHelper.java +++ b/java/com/android/dialer/phonenumberutil/PhoneNumberHelper.java @@ -17,6 +17,8 @@ package com.android.dialer.phonenumberutil; import android.content.Context; +import android.database.Cursor; +import android.net.Uri; import android.os.Trace; import android.provider.CallLog; import android.support.annotation.Nullable; @@ -24,6 +26,7 @@ import android.telecom.PhoneAccountHandle; import android.telephony.PhoneNumberUtils; import android.telephony.TelephonyManager; import android.text.TextUtils; +import com.android.dialer.common.Assert; import com.android.dialer.common.LogUtil; import com.android.dialer.compat.CompatUtils; import com.android.dialer.compat.telephony.TelephonyManagerCompat; @@ -47,6 +50,78 @@ public class PhoneNumberHelper { } /** + * Find the cursor pointing to a number that matches the number in a contact lookup URI. + * + * <p>When determining whether two phone numbers are identical enough for caller ID purposes, the + * Contacts Provider uses {@link PhoneNumberUtils#compare(String, String)}, which ignores special + * dialable characters such as '#', '*', '+', etc. This makes it possible for the cursor returned + * by the Contacts Provider to have multiple rows even when the URI asks for a specific number. + * + * <p>For example, suppose the user has two contacts whose numbers are "#123" and "123", + * respectively. When the URI asks for number "123", both numbers will be returned. Therefore, the + * following strategy is employed to find a match. + * + * <p>If the cursor points to a global phone number (i.e., a number that can be accepted by {@link + * PhoneNumberUtils#isGlobalPhoneNumber(String)}) and the lookup number in the URI is a PARTIAL + * match, return the cursor. + * + * <p>If the cursor points to a number that is not a global phone number, return the cursor iff + * the lookup number in the URI is an EXACT match. + * + * <p>Return null in all other circumstances. + * + * @param cursor A cursor returned by the Contacts Provider. + * @param columnIndexForNumber The index of the column where phone numbers are stored. It is the + * caller's responsibility to pass the correct column index. + * @param contactLookupUri A URI used to retrieve a contact via the Contacts Provider. It is the + * caller's responsibility to ensure the URI is one that asks for a specific phone number. + * @return The cursor considered as a match by the description above or null if no such cursor can + * be found. + */ + public static Cursor getCursorMatchForContactLookupUri( + Cursor cursor, int columnIndexForNumber, Uri contactLookupUri) { + if (cursor == null || contactLookupUri == null) { + return null; + } + + if (!cursor.moveToFirst()) { + return null; + } + + Assert.checkArgument( + 0 <= columnIndexForNumber && columnIndexForNumber < cursor.getColumnCount()); + + String lookupNumber = contactLookupUri.getLastPathSegment(); + if (lookupNumber == null) { + return null; + } + + boolean isMatchFound; + do { + // All undialable characters should be converted/removed before comparing the lookup number + // and the existing contact number. + String rawExistingContactNumber = + PhoneNumberUtils.stripSeparators( + PhoneNumberUtils.convertKeypadLettersToDigits( + cursor.getString(columnIndexForNumber))); + String rawQueryNumber = + PhoneNumberUtils.stripSeparators( + PhoneNumberUtils.convertKeypadLettersToDigits(lookupNumber)); + + isMatchFound = + PhoneNumberUtils.isGlobalPhoneNumber(rawExistingContactNumber) + ? rawExistingContactNumber.contains(rawQueryNumber) + : rawExistingContactNumber.equals(rawQueryNumber); + + if (isMatchFound) { + return cursor; + } + } while (cursor.moveToNext()); + + return null; + } + + /** * Returns true if the given number is the number of the configured voicemail. To be able to * mock-out this, it is not a static method. */ diff --git a/java/com/android/incallui/CallerInfo.java b/java/com/android/incallui/CallerInfo.java index cc1a60a5b..809ed594c 100644 --- a/java/com/android/incallui/CallerInfo.java +++ b/java/com/android/incallui/CallerInfo.java @@ -192,141 +192,145 @@ public class CallerInfo { * * @param context the context used to retrieve string constants * @param contactRef the URI to attach to this CallerInfo object - * @param cursor the first object in the cursor is used to build the CallerInfo object. + * @param cursor the first matching object in the cursor is used to build the CallerInfo object. * @return the CallerInfo which contains the caller id for the given number. The returned * CallerInfo is null if no number is supplied. */ public static CallerInfo getCallerInfo(Context context, Uri contactRef, Cursor cursor) { CallerInfo info = new CallerInfo(); - info.photoResource = 0; - info.phoneLabel = null; - info.numberType = 0; - info.numberLabel = null; info.cachedPhoto = null; - info.isCachedPhotoCurrent = false; info.contactExists = false; + info.contactRefUri = contactRef; + info.isCachedPhotoCurrent = false; + info.name = null; + info.needUpdate = false; + info.numberLabel = null; + info.numberType = 0; + info.phoneLabel = null; + info.photoResource = 0; info.userType = ContactsUtils.USER_TYPE_CURRENT; Log.v(TAG, "getCallerInfo() based on cursor..."); - if (cursor != null) { - if (cursor.moveToFirst()) { - // TODO: photo_id is always available but not taken - // care of here. Maybe we should store it in the - // CallerInfo object as well. - - long contactId = 0L; - int columnIndex; - - // Look for the name - columnIndex = cursor.getColumnIndex(PhoneLookup.DISPLAY_NAME); - if (columnIndex != -1) { - info.name = cursor.getString(columnIndex); - } - - // Look for the number - columnIndex = cursor.getColumnIndex(PhoneLookup.NUMBER); - if (columnIndex != -1) { - info.phoneNumber = cursor.getString(columnIndex); - } - - // Look for the normalized number - columnIndex = cursor.getColumnIndex(PhoneLookup.NORMALIZED_NUMBER); - if (columnIndex != -1) { - info.normalizedNumber = cursor.getString(columnIndex); - } - - // Look for the label/type combo - columnIndex = cursor.getColumnIndex(PhoneLookup.LABEL); - if (columnIndex != -1) { - int typeColumnIndex = cursor.getColumnIndex(PhoneLookup.TYPE); - if (typeColumnIndex != -1) { - info.numberType = cursor.getInt(typeColumnIndex); - info.numberLabel = cursor.getString(columnIndex); - info.phoneLabel = - Phone.getTypeLabel(context.getResources(), info.numberType, info.numberLabel) - .toString(); - } - } - - // cache the lookup key for later use to create lookup URIs - columnIndex = cursor.getColumnIndex(PhoneLookup.LOOKUP_KEY); - if (columnIndex != -1) { - info.lookupKeyOrNull = cursor.getString(columnIndex); - } - - // Look for the person_id. - columnIndex = getColumnIndexForPersonId(contactRef, cursor); - if (columnIndex != -1) { - contactId = cursor.getLong(columnIndex); - // QuickContacts in M doesn't support enterprise contact id - if (contactId != 0 - && (VERSION.SDK_INT >= VERSION_CODES.N - || !Contacts.isEnterpriseContactId(contactId))) { - info.contactIdOrZero = contactId; - Log.v(TAG, "==> got info.contactIdOrZero: " + info.contactIdOrZero); - } - } else { - // No valid columnIndex, so we can't look up person_id. - Log.v(TAG, "Couldn't find contactId column for " + contactRef); - // Watch out: this means that anything that depends on - // person_id will be broken (like contact photo lookups in - // the in-call UI, for example.) - } - - // Display photo URI. - columnIndex = cursor.getColumnIndex(PhoneLookup.PHOTO_URI); - if ((columnIndex != -1) && (cursor.getString(columnIndex) != null)) { - info.contactDisplayPhotoUri = Uri.parse(cursor.getString(columnIndex)); - } else { - info.contactDisplayPhotoUri = null; - } - - // look for the custom ringtone, create from the string stored - // in the database. - columnIndex = cursor.getColumnIndex(PhoneLookup.CUSTOM_RINGTONE); - if ((columnIndex != -1) && (cursor.getString(columnIndex) != null)) { - if (TextUtils.isEmpty(cursor.getString(columnIndex))) { - // make it consistent with frameworks/base/.../CallerInfo.java - info.contactRingtoneUri = Uri.EMPTY; - } else { - info.contactRingtoneUri = Uri.parse(cursor.getString(columnIndex)); - } - } else { - info.contactRingtoneUri = null; - } - - // look for the send to voicemail flag, set it to true only - // under certain circumstances. - columnIndex = cursor.getColumnIndex(PhoneLookup.SEND_TO_VOICEMAIL); - info.shouldSendToVoicemail = (columnIndex != -1) && ((cursor.getInt(columnIndex)) == 1); - info.contactExists = true; - - // Determine userType by directoryId and contactId - final String directory = - contactRef == null - ? null - : contactRef.getQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY); - Long directoryId = null; - if (directory != null) { - try { - directoryId = Long.parseLong(directory); - } catch (NumberFormatException e) { - // do nothing - } - } - info.userType = ContactsUtils.determineUserType(directoryId, contactId); - - info.nameAlternative = - ContactInfoHelper.lookUpDisplayNameAlternative( - context, info.lookupKeyOrNull, info.userType, directoryId); + if (cursor == null || !cursor.moveToFirst()) { + return info; + } + + // TODO: photo_id is always available but not taken + // care of here. Maybe we should store it in the + // CallerInfo object as well. + + long contactId = 0L; + int columnIndex; + + // If the cursor has the phone number column, find the one that matches the lookup number in the + // URI. + columnIndex = cursor.getColumnIndex(PhoneLookup.NUMBER); + if (columnIndex != -1 && contactRef != null) { + cursor = PhoneNumberHelper.getCursorMatchForContactLookupUri(cursor, columnIndex, contactRef); + if (cursor != null) { + info.phoneNumber = cursor.getString(columnIndex); + } else { + return info; } - cursor.close(); } - info.needUpdate = false; - info.name = normalize(info.name); - info.contactRefUri = contactRef; + // Look for the name + columnIndex = cursor.getColumnIndex(PhoneLookup.DISPLAY_NAME); + if (columnIndex != -1) { + info.name = normalize(cursor.getString(columnIndex)); + } + + // Look for the normalized number + columnIndex = cursor.getColumnIndex(PhoneLookup.NORMALIZED_NUMBER); + if (columnIndex != -1) { + info.normalizedNumber = cursor.getString(columnIndex); + } + + // Look for the label/type combo + columnIndex = cursor.getColumnIndex(PhoneLookup.LABEL); + if (columnIndex != -1) { + int typeColumnIndex = cursor.getColumnIndex(PhoneLookup.TYPE); + if (typeColumnIndex != -1) { + info.numberType = cursor.getInt(typeColumnIndex); + info.numberLabel = cursor.getString(columnIndex); + info.phoneLabel = + Phone.getTypeLabel(context.getResources(), info.numberType, info.numberLabel) + .toString(); + } + } + + // cache the lookup key for later use to create lookup URIs + columnIndex = cursor.getColumnIndex(PhoneLookup.LOOKUP_KEY); + if (columnIndex != -1) { + info.lookupKeyOrNull = cursor.getString(columnIndex); + } + + // Look for the person_id. + columnIndex = getColumnIndexForPersonId(contactRef, cursor); + if (columnIndex != -1) { + contactId = cursor.getLong(columnIndex); + // QuickContacts in M doesn't support enterprise contact id + if (contactId != 0 + && (VERSION.SDK_INT >= VERSION_CODES.N || !Contacts.isEnterpriseContactId(contactId))) { + info.contactIdOrZero = contactId; + Log.v(TAG, "==> got info.contactIdOrZero: " + info.contactIdOrZero); + } + } else { + // No valid columnIndex, so we can't look up person_id. + Log.v(TAG, "Couldn't find contactId column for " + contactRef); + // Watch out: this means that anything that depends on + // person_id will be broken (like contact photo lookups in + // the in-call UI, for example.) + } + + // Display photo URI. + columnIndex = cursor.getColumnIndex(PhoneLookup.PHOTO_URI); + if ((columnIndex != -1) && (cursor.getString(columnIndex) != null)) { + info.contactDisplayPhotoUri = Uri.parse(cursor.getString(columnIndex)); + } else { + info.contactDisplayPhotoUri = null; + } + + // look for the custom ringtone, create from the string stored + // in the database. + columnIndex = cursor.getColumnIndex(PhoneLookup.CUSTOM_RINGTONE); + if ((columnIndex != -1) && (cursor.getString(columnIndex) != null)) { + if (TextUtils.isEmpty(cursor.getString(columnIndex))) { + // make it consistent with frameworks/base/.../CallerInfo.java + info.contactRingtoneUri = Uri.EMPTY; + } else { + info.contactRingtoneUri = Uri.parse(cursor.getString(columnIndex)); + } + } else { + info.contactRingtoneUri = null; + } + + // look for the send to voicemail flag, set it to true only + // under certain circumstances. + columnIndex = cursor.getColumnIndex(PhoneLookup.SEND_TO_VOICEMAIL); + info.shouldSendToVoicemail = (columnIndex != -1) && ((cursor.getInt(columnIndex)) == 1); + info.contactExists = true; + + // Determine userType by directoryId and contactId + final String directory = + contactRef == null + ? null + : contactRef.getQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY); + Long directoryId = null; + if (directory != null) { + try { + directoryId = Long.parseLong(directory); + } catch (NumberFormatException e) { + // do nothing + } + } + info.userType = ContactsUtils.determineUserType(directoryId, contactId); + + info.nameAlternative = + ContactInfoHelper.lookUpDisplayNameAlternative( + context, info.lookupKeyOrNull, info.userType, directoryId); + cursor.close(); return info; } |