From 327fb5bb609a6bee44a62888d671c951b19782fd Mon Sep 17 00:00:00 2001 From: Brandon Maxwell Date: Tue, 20 Oct 2015 14:49:47 -0700 Subject: Call log respects display name order preferences - Updated ContactInfoHelper to retrieve DISPLAY_NAME_ALTERNATIVE (name in last name first order) - Stored alternative name in ContactInfo object - Updated CallLogAdapter to choose between first name first and last name first when showing contact name - Added tests for ContactInfoHelper.lookupContactFromUri (changed to public method) - Fixed bug with ContactsPreferences so ChangeListener works Bug:19364093 Change-Id: I188d8fc2eccb87edbe56625c9a7537b3d5f0e19e --- src/com/android/dialer/calllog/CallLogAdapter.java | 30 ++++-- .../android/dialer/calllog/CallLogFragment.java | 3 +- src/com/android/dialer/calllog/ContactInfo.java | 10 +- .../android/dialer/calllog/ContactInfoHelper.java | 88 +++++++++------- src/com/android/dialer/calllog/PhoneQuery.java | 19 +++- .../dialer/contactinfo/ContactInfoCache.java | 5 +- .../dialer/calllog/ContactInfoHelperTest.java | 112 +++++++++++++++++++++ 7 files changed, 214 insertions(+), 53 deletions(-) create mode 100644 tests/src/com/android/dialer/calllog/ContactInfoHelperTest.java diff --git a/src/com/android/dialer/calllog/CallLogAdapter.java b/src/com/android/dialer/calllog/CallLogAdapter.java index 3dccf27bb..ae20e4943 100644 --- a/src/com/android/dialer/calllog/CallLogAdapter.java +++ b/src/com/android/dialer/calllog/CallLogAdapter.java @@ -16,18 +16,20 @@ package com.android.dialer.calllog; +import com.google.common.annotations.VisibleForTesting; + import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.res.Resources; import android.database.Cursor; import android.net.Uri; -import android.provider.ContactsContract.CommonDataKinds.Phone; -import android.support.v7.widget.RecyclerView; import android.os.Bundle; import android.os.Trace; import android.preference.PreferenceManager; import android.provider.CallLog; +import android.provider.ContactsContract.CommonDataKinds.Phone; +import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView.ViewHolder; import android.telecom.PhoneAccountHandle; import android.telephony.PhoneNumberUtils; @@ -39,6 +41,7 @@ import android.view.View.AccessibilityDelegate; import android.view.ViewGroup; import android.view.accessibility.AccessibilityEvent; +import com.android.contacts.common.preference.ContactsPreferences; import com.android.contacts.common.util.PermissionsUtil; import com.android.dialer.PhoneCallDetails; import com.android.dialer.R; @@ -51,8 +54,6 @@ import com.android.dialer.filterednumber.FilterNumberDialogFragment; import com.android.dialer.util.PhoneNumberUtil; import com.android.dialer.voicemail.VoicemailPlaybackPresenter; -import com.google.common.annotations.VisibleForTesting; - import java.util.HashMap; import java.util.Map; @@ -118,12 +119,14 @@ public class CallLogAdapter extends GroupingListAdapter * its day group. This hashmap provides a means of determining the previous day group without * having to reverse the cursor to the start of the previous day call log entry. */ - private HashMap mDayGroups = new HashMap(); + private HashMap mDayGroups = new HashMap<>(); private boolean mLoading = true; private SharedPreferences mPrefs; + private ContactsPreferences mContactsPreferences; + protected boolean mShowVoicemailPromoCard = false; /** Instance of helper class for managing views. */ @@ -254,7 +257,7 @@ public class CallLogAdapter extends GroupingListAdapter CallTypeHelper callTypeHelper = new CallTypeHelper(resources); mTelecomCallLogCache = new TelecomCallLogCache(mContext); - mBlockedIdCache = new HashMap(); + mBlockedIdCache = new HashMap<>(); PhoneCallDetailsHelper phoneCallDetailsHelper = new PhoneCallDetailsHelper(mContext, resources, mTelecomCallLogCache); mCallLogListItemHelper = @@ -264,6 +267,7 @@ public class CallLogAdapter extends GroupingListAdapter new FilteredNumberAsyncQueryHandler(mContext.getContentResolver()); mPrefs = PreferenceManager.getDefaultSharedPreferences(context); + mContactsPreferences = new ContactsPreferences(mContext); maybeShowVoicemailPromoCard(); } @@ -311,6 +315,7 @@ public class CallLogAdapter extends GroupingListAdapter if (PermissionsUtil.hasPermission(mContext, android.Manifest.permission.READ_CONTACTS)) { mContactInfoCache.start(); } + mContactsPreferences.refreshValue(ContactsPreferences.DISPLAY_ORDER_KEY); } public void onPause() { @@ -460,9 +465,10 @@ public class CallLogAdapter extends GroupingListAdapter details.dataUsage = c.getLong(CallLogQuery.DATA_USAGE); } - if (!TextUtils.isEmpty(info.name)) { + String preferredName = getPreferredDisplayName(info); + if (!TextUtils.isEmpty(preferredName)) { details.contactUri = info.lookupUri; - details.name = info.name; + details.name = preferredName; details.numberType = info.type; details.numberLabel = info.label; details.photoUri = info.photoUri; @@ -532,6 +538,14 @@ public class CallLogAdapter extends GroupingListAdapter mCallLogListItemHelper.setPhoneCallDetails(views, details); } + private String getPreferredDisplayName(ContactInfo contactInfo) { + if (mContactsPreferences.getDisplayOrder() == ContactsPreferences.DISPLAY_ORDER_PRIMARY || + TextUtils.isEmpty(contactInfo.nameAlternative)) { + return contactInfo.name; + } + return contactInfo.nameAlternative; + } + @Override public int getItemCount() { return super.getItemCount() + (mShowVoicemailPromoCard ? 1 : 0) diff --git a/src/com/android/dialer/calllog/CallLogFragment.java b/src/com/android/dialer/calllog/CallLogFragment.java index 369730d96..f84ffd5da 100644 --- a/src/com/android/dialer/calllog/CallLogFragment.java +++ b/src/com/android/dialer/calllog/CallLogFragment.java @@ -33,8 +33,8 @@ import android.provider.CallLog; import android.provider.CallLog.Calls; import android.provider.ContactsContract; import android.provider.VoicemailContract.Status; -import android.support.v7.widget.RecyclerView; import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -309,7 +309,6 @@ public class CallLogFragment extends Fragment implements CallLogQueryHandler.Lis mVoicemailPlaybackPresenter, mIsCallLogActivity); mRecyclerView.setAdapter(mAdapter); - fetchCalls(); return view; } diff --git a/src/com/android/dialer/calllog/ContactInfo.java b/src/com/android/dialer/calllog/ContactInfo.java index 357c832cf..30f60d9d3 100644 --- a/src/com/android/dialer/calllog/ContactInfo.java +++ b/src/com/android/dialer/calllog/ContactInfo.java @@ -34,6 +34,7 @@ public class ContactInfo { */ public String lookupKey; public String name; + public String nameAlternative; public int type; public String label; public String number; @@ -70,6 +71,7 @@ public class ContactInfo { ContactInfo other = (ContactInfo) obj; if (!UriUtils.areEqual(lookupUri, other.lookupUri)) return false; if (!TextUtils.equals(name, other.name)) return false; + if (!TextUtils.equals(nameAlternative, other.nameAlternative)) return false; if (type != other.type) return false; if (!TextUtils.equals(label, other.label)) return false; if (!TextUtils.equals(number, other.number)) return false; @@ -83,9 +85,11 @@ public class ContactInfo { @Override public String toString() { - return Objects.toStringHelper(this).add("lookupUri", lookupUri).add("name", name).add( - "type", type).add("label", label).add("number", number).add("formattedNumber", - formattedNumber).add("normalizedNumber", normalizedNumber).add("photoId", photoId) + return Objects.toStringHelper(this).add("lookupUri", lookupUri).add("name", name) + .add("nameAlternative", nameAlternative) + .add("type", type).add("label", label) + .add("number", number).add("formattedNumber",formattedNumber) + .add("normalizedNumber", normalizedNumber).add("photoId", photoId) .add("photoUri", photoUri).add("objectId", objectId).toString(); } } diff --git a/src/com/android/dialer/calllog/ContactInfoHelper.java b/src/com/android/dialer/calllog/ContactInfoHelper.java index 2f97bc569..4b9d5532a 100644 --- a/src/com/android/dialer/calllog/ContactInfoHelper.java +++ b/src/com/android/dialer/calllog/ContactInfoHelper.java @@ -41,8 +41,6 @@ import com.android.dialerbind.ObjectFactory; import org.json.JSONException; import org.json.JSONObject; -import java.util.List; - /** * Utility class to look up the contact information for a given number. */ @@ -151,47 +149,70 @@ public class ContactInfoHelper { * The {@link ContactInfo#formattedNumber} field is always set to {@code null} in the returned * value. */ - private ContactInfo lookupContactFromUri(Uri uri) { + public ContactInfo lookupContactFromUri(Uri uri) { if (uri == null) { return null; } if (!PermissionsUtil.hasContactsPermissions(mContext)) { return ContactInfo.EMPTY; } - final ContactInfo info; - Cursor phonesCursor = - mContext.getContentResolver().query(uri, PhoneQuery._PROJECTION, null, null, null); - - if (phonesCursor != null) { - try { - if (phonesCursor.moveToFirst()) { - info = new ContactInfo(); - long contactId = phonesCursor.getLong(PhoneQuery.PERSON_ID); - String lookupKey = phonesCursor.getString(PhoneQuery.LOOKUP_KEY); - info.lookupKey = lookupKey; - info.lookupUri = Contacts.getLookupUri(contactId, lookupKey); - info.name = phonesCursor.getString(PhoneQuery.NAME); - info.type = phonesCursor.getInt(PhoneQuery.PHONE_TYPE); - info.label = phonesCursor.getString(PhoneQuery.LABEL); - info.number = phonesCursor.getString(PhoneQuery.MATCHED_NUMBER); - info.normalizedNumber = phonesCursor.getString(PhoneQuery.NORMALIZED_NUMBER); - info.photoId = phonesCursor.getLong(PhoneQuery.PHOTO_ID); - info.photoUri = - UriUtils.parseUriOrNull(phonesCursor.getString(PhoneQuery.PHOTO_URI)); - info.formattedNumber = null; - } else { - info = ContactInfo.EMPTY; - } - } finally { - phonesCursor.close(); + + Cursor phoneLookupCursor = mContext.getContentResolver().query(uri, + PhoneQuery.PHONE_LOOKUP_PROJECTION, null, null, null); + + if (phoneLookupCursor == null) { + return null; + } + + try { + if (!phoneLookupCursor.moveToFirst()) { + return ContactInfo.EMPTY; } - } else { - // Failed to fetch the data, ignore this request. - info = null; + String lookupKey = phoneLookupCursor.getString(PhoneQuery.LOOKUP_KEY); + ContactInfo contactInfo = createPhoneLookupContactInfo(phoneLookupCursor, lookupKey); + contactInfo.nameAlternative = lookUpDisplayNameAlternative(lookupKey); + return contactInfo; + } finally { + phoneLookupCursor.close(); } + } + + private ContactInfo createPhoneLookupContactInfo(Cursor phoneLookupCursor, String lookupKey) { + ContactInfo info = new ContactInfo(); + info.lookupKey = lookupKey; + info.lookupUri = Contacts.getLookupUri(phoneLookupCursor.getLong(PhoneQuery.PERSON_ID), + lookupKey); + info.name = phoneLookupCursor.getString(PhoneQuery.NAME); + info.type = phoneLookupCursor.getInt(PhoneQuery.PHONE_TYPE); + info.label = phoneLookupCursor.getString(PhoneQuery.LABEL); + info.number = phoneLookupCursor.getString(PhoneQuery.MATCHED_NUMBER); + info.normalizedNumber = phoneLookupCursor.getString(PhoneQuery.NORMALIZED_NUMBER); + info.photoId = phoneLookupCursor.getLong(PhoneQuery.PHOTO_ID); + info.photoUri = UriUtils.parseUriOrNull(phoneLookupCursor.getString(PhoneQuery.PHOTO_URI)); + info.formattedNumber = null; return info; } + private String lookUpDisplayNameAlternative(String lookupKey) { + Uri uri = Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey); + + Cursor cursor = mContext.getContentResolver().query(uri, + PhoneQuery.DISPLAY_NAME_ALTERNATIVE_PROJECTION, null, null, null); + + if (cursor == null) { + return null; + } + + try { + if (!cursor.moveToFirst()) { + return null; + } + return cursor.getString(PhoneQuery.NAME_ALTERNATIVE); + } finally { + cursor.close(); + } + } + /** * Determines the contact information for the given phone number. *

@@ -367,7 +388,6 @@ public class ContactInfoHelper { */ public static ContactInfo getContactInfo(Cursor c) { ContactInfo info = new ContactInfo(); - info.lookupUri = UriUtils.parseUriOrNull(c.getString(CallLogQuery.CACHED_LOOKUP_URI)); info.name = c.getString(CallLogQuery.CACHED_NAME); info.type = c.getInt(CallLogQuery.CACHED_NUMBER_TYPE); @@ -406,6 +426,4 @@ public class ContactInfoHelper { return mCachedNumberLookupService != null && mCachedNumberLookupService.canReportAsInvalid(sourceType, objectId); } - - } diff --git a/src/com/android/dialer/calllog/PhoneQuery.java b/src/com/android/dialer/calllog/PhoneQuery.java index 719052204..200b5e1f4 100644 --- a/src/com/android/dialer/calllog/PhoneQuery.java +++ b/src/com/android/dialer/calllog/PhoneQuery.java @@ -16,13 +16,19 @@ package com.android.dialer.calllog; +import android.provider.ContactsContract.Contacts; import android.provider.ContactsContract.PhoneLookup; /** - * The query to look up the {@link ContactInfo} for a given number in the Call Log. + * The queries to look up the {@link ContactInfo} for a given number in the Call Log. */ final class PhoneQuery { - public static final String[] _PROJECTION = new String[] { + + /** + * Projection to look up the ContactInfo. Does not include DISPLAY_NAME_ALTERNATIVE as that + * column isn't available in ContactsCommon.PhoneLookup + */ + public static final String[] PHONE_LOOKUP_PROJECTION = new String[] { PhoneLookup._ID, PhoneLookup.DISPLAY_NAME, PhoneLookup.TYPE, @@ -42,4 +48,13 @@ final class PhoneQuery { public static final int PHOTO_ID = 6; public static final int LOOKUP_KEY = 7; public static final int PHOTO_URI = 8; + + /** + * Projection to look up a contact's DISPLAY_NAME_ALTERNATIVE + */ + public static final String[] DISPLAY_NAME_ALTERNATIVE_PROJECTION = new String[] { + Contacts.DISPLAY_NAME_ALTERNATIVE, + }; + + public static final int NAME_ALTERNATIVE = 0; } diff --git a/src/com/android/dialer/contactinfo/ContactInfoCache.java b/src/com/android/dialer/contactinfo/ContactInfoCache.java index 568f48886..1e2457957 100644 --- a/src/com/android/dialer/contactinfo/ContactInfoCache.java +++ b/src/com/android/dialer/contactinfo/ContactInfoCache.java @@ -162,7 +162,7 @@ public class ContactInfoCache { // The contact info is no longer up to date, we should request it. However, we // do not need to request them immediately. enqueueRequest(number, countryIso, cachedContactInfo, false); - } else if (!callLogInfoMatches(cachedContactInfo, info)) { + } else if (!callLogInfoMatches(cachedContactInfo, info)) { // The call log information does not match the one we have, look it up again. // We could simply update the call log directly, but that needs to be done in a // background thread, so it is easier to simply request a new lookup, which will, as @@ -309,8 +309,7 @@ public class ContactInfoCache { * Checks whether the contact info from the call log matches the one from the contacts db. */ private boolean callLogInfoMatches(ContactInfo callLogInfo, ContactInfo info) { - // The call log only contains a subset of the fields in the contacts db. - // Only check those. + // The call log only contains a subset of the fields in the contacts db. Only check those. return TextUtils.equals(callLogInfo.name, info.name) && callLogInfo.type == info.type && TextUtils.equals(callLogInfo.label, info.label); diff --git a/tests/src/com/android/dialer/calllog/ContactInfoHelperTest.java b/tests/src/com/android/dialer/calllog/ContactInfoHelperTest.java new file mode 100644 index 000000000..6d3e86042 --- /dev/null +++ b/tests/src/com/android/dialer/calllog/ContactInfoHelperTest.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.dialer.calllog; + +import android.net.Uri; +import android.provider.ContactsContract.Contacts; +import android.provider.ContactsContract.PhoneLookup; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.MediumTest; + +import com.android.contacts.common.test.mocks.ContactsMockContext; +import com.android.contacts.common.test.mocks.MockContentProvider.Query; + +import junit.framework.Assert; + +@MediumTest +public class ContactInfoHelperTest extends AndroidTestCase { + + private static final String TEST_COUNTRY_ISO = "US"; + private static final String TEST_DISPLAY_NAME = "Display Name"; + private static final String TEST_DISPLAY_NAME_ALTERNATIVE = "Name, Display"; + private static final String[] TEST_DISPLAY_NAME_ALTERNATIVE_ROW = new String[]{null, + TEST_DISPLAY_NAME_ALTERNATIVE}; + private static final String TEST_LOOKUP_KEY = "lookupKey"; + private static final String[] TEST_LOOKUP_ROW = new String[]{null, TEST_DISPLAY_NAME, + null, null, null, null, null, TEST_LOOKUP_KEY, null}; + + private Uri displayNameAlternativeUri; + private ContactsMockContext mContext; + private ContactInfo mContactInfo; + private ContactInfoHelper mContactInfoHelper; + + @Override + public void setUp() throws Exception { + super.setUp(); + + displayNameAlternativeUri = Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, + TEST_LOOKUP_KEY); + mContext = new ContactsMockContext(getContext()); + mContactInfo = new ContactInfo(); + mContactInfo.name = TEST_DISPLAY_NAME; + mContactInfo.nameAlternative = TEST_DISPLAY_NAME_ALTERNATIVE; + mContactInfoHelper = new ContactInfoHelper(mContext, TEST_COUNTRY_ISO); + } + + public void testLookupContactFromUriNullUri() { + Assert.assertNull(mContactInfoHelper.lookupContactFromUri(null)); + } + + public void testLookupContactFromUriNoResults() { + setUpQueryExpectations(PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI, + PhoneQuery.PHONE_LOOKUP_PROJECTION); + + Assert.assertEquals(ContactInfo.EMPTY, mContactInfoHelper.lookupContactFromUri( + PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI)); + mContext.verify(); + } + + public void testLookupContactFromUriNoDisplayNameAlternative() { + setUpQueryExpectations(PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI, + PhoneQuery.PHONE_LOOKUP_PROJECTION, TEST_LOOKUP_ROW); + setUpQueryExpectations(displayNameAlternativeUri, + PhoneQuery.DISPLAY_NAME_ALTERNATIVE_PROJECTION); + + ContactInfo contactInfo = mContactInfoHelper.lookupContactFromUri( + PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI); + Assert.assertEquals(TEST_DISPLAY_NAME, contactInfo.name); + Assert.assertNull(contactInfo.nameAlternative); + mContext.verify(); + } + + public void testLookupContactFromUriWithDisplayNameAlternative() { + setUpQueryExpectations(PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI, + PhoneQuery.PHONE_LOOKUP_PROJECTION, TEST_LOOKUP_ROW); + setUpQueryExpectations(displayNameAlternativeUri, + PhoneQuery.DISPLAY_NAME_ALTERNATIVE_PROJECTION, TEST_DISPLAY_NAME_ALTERNATIVE_ROW); + + ContactInfo contactInfo = mContactInfoHelper.lookupContactFromUri( + PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI); + Assert.assertEquals(TEST_DISPLAY_NAME, contactInfo.name); + Assert.assertEquals(TEST_DISPLAY_NAME_ALTERNATIVE, contactInfo.nameAlternative); + mContext.verify(); + } + + /* + * Sets up query expectations to return the given row for all queries for the given + * uri and projection. If row is null, an empty cursor is returned for query calls + */ + private void setUpQueryExpectations(Uri uri, String[] projection, String...row) { + Query query = mContext.getContactsProvider().expectQuery(uri) + .withProjection(projection).withAnySelection().withAnySortOrder(); + if (row == null || row.length == 0) { + query.returnEmptyCursor(); + return; + } + query.returnRow(row); + } +} -- cgit v1.2.3