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: I73e8d8602a54e3134ef01b3d5167d828b26fa5ea
diff --git a/src/com/android/dialer/calllog/CallLogAdapter.java b/src/com/android/dialer/calllog/CallLogAdapter.java
index 3dccf27..ae20e49 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.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.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 @@
      *  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<Long,Integer> mDayGroups = new HashMap<Long, Integer>();
+    private HashMap<Long, Integer> 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 @@
         CallTypeHelper callTypeHelper = new CallTypeHelper(resources);
 
         mTelecomCallLogCache = new TelecomCallLogCache(mContext);
-        mBlockedIdCache = new HashMap<NumberWithCountryIso, Integer>();
+        mBlockedIdCache = new HashMap<>();
         PhoneCallDetailsHelper phoneCallDetailsHelper =
                 new PhoneCallDetailsHelper(mContext, resources, mTelecomCallLogCache);
         mCallLogListItemHelper =
@@ -264,6 +267,7 @@
                 new FilteredNumberAsyncQueryHandler(mContext.getContentResolver());
 
         mPrefs = PreferenceManager.getDefaultSharedPreferences(context);
+        mContactsPreferences = new ContactsPreferences(mContext);
         maybeShowVoicemailPromoCard();
     }
 
@@ -311,6 +315,7 @@
         if (PermissionsUtil.hasPermission(mContext, android.Manifest.permission.READ_CONTACTS)) {
             mContactInfoCache.start();
         }
+        mContactsPreferences.refreshValue(ContactsPreferences.DISPLAY_ORDER_KEY);
     }
 
     public void onPause() {
@@ -460,9 +465,10 @@
             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 @@
         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 369730d..f84ffd5 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.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 @@
                 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 357c832..30f60d9 100644
--- a/src/com/android/dialer/calllog/ContactInfo.java
+++ b/src/com/android/dialer/calllog/ContactInfo.java
@@ -34,6 +34,7 @@
      */
     public String lookupKey;
     public String name;
+    public String nameAlternative;
     public int type;
     public String label;
     public String number;
@@ -70,6 +71,7 @@
         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 @@
 
     @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 2f97bc5..4b9d553 100644
--- a/src/com/android/dialer/calllog/ContactInfoHelper.java
+++ b/src/com/android/dialer/calllog/ContactInfoHelper.java
@@ -41,8 +41,6 @@
 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 @@
      * 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();
-            }
-        } else {
-            // Failed to fetch the data, ignore this request.
-            info = null;
+        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;
+            }
+            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.
      * <p>
@@ -367,7 +388,6 @@
      */
     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 @@
         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 7190522..200b5e1 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 @@
     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 568f488..1e24579 100644
--- a/src/com/android/dialer/contactinfo/ContactInfoCache.java
+++ b/src/com/android/dialer/contactinfo/ContactInfoCache.java
@@ -162,7 +162,7 @@
                 // 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 @@
      * 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 0000000..6d3e860
--- /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);
+    }
+}