Search now supports alternative sort and display orders for contact names.

This means that names like "Bob Dylan" can be displayed as "Dylan, Bob" and
the names are now able to be sorted by last name as well.

This change also adds content descriptions to search icon for a11y.

Bug: 68293751
Test: NewSearchFragmentTest
PiperOrigin-RevId: 176382228
Change-Id: Ia890ad20e9e484fa9c4622c4b899b33f5b6e3990
diff --git a/java/com/android/dialer/searchfragment/common/Projections.java b/java/com/android/dialer/searchfragment/common/Projections.java
index 63fac4c..cebe5c9 100644
--- a/java/com/android/dialer/searchfragment/common/Projections.java
+++ b/java/com/android/dialer/searchfragment/common/Projections.java
@@ -39,8 +39,10 @@
   @SuppressWarnings("unused")
   public static final int SORT_KEY = 11;
 
-  public static final int COMPANY_NAME = 12;
-  public static final int NICKNAME = 13;
+  public static final int SORT_ALTERNATIVE = 12;
+
+  public static final int COMPANY_NAME = 13;
+  public static final int NICKNAME = 14;
 
   public static final String[] CP2_PROJECTION =
       new String[] {
@@ -56,8 +58,29 @@
         Data.CONTACT_ID, // 9
         Data.MIMETYPE, // 10
         Data.SORT_KEY_PRIMARY, // 11
-        Organization.COMPANY, // 12
-        Nickname.NAME // 13
+        Data.SORT_KEY_ALTERNATIVE, // 12
+        Organization.COMPANY, // 13
+        Nickname.NAME // 14
+      };
+
+  // Uses alternative display names (i.e. "Bob Dylan" becomes "Dylan, Bob").
+  public static final String[] CP2_PROJECTION_ALTERNATIVE =
+      new String[] {
+        Data._ID, // 0
+        Phone.TYPE, // 1
+        Phone.LABEL, // 2
+        Phone.NUMBER, // 3
+        Data.DISPLAY_NAME_ALTERNATIVE, // 4
+        Data.PHOTO_ID, // 5
+        Data.PHOTO_THUMBNAIL_URI, // 6
+        Data.LOOKUP_KEY, // 7
+        Data.CARRIER_PRESENCE, // 8
+        Data.CONTACT_ID, // 9
+        Data.MIMETYPE, // 10
+        Data.SORT_KEY_PRIMARY, // 11
+        Data.SORT_KEY_ALTERNATIVE, // 12
+        Organization.COMPANY, // 13
+        Nickname.NAME // 14
       };
 
   public static final String[] DATA_PROJECTION =
diff --git a/java/com/android/dialer/searchfragment/cp2/ContactFilterCursor.java b/java/com/android/dialer/searchfragment/cp2/ContactFilterCursor.java
index dc16f9d..23c0d6e 100644
--- a/java/com/android/dialer/searchfragment/cp2/ContactFilterCursor.java
+++ b/java/com/android/dialer/searchfragment/cp2/ContactFilterCursor.java
@@ -32,6 +32,7 @@
 import android.support.annotation.Nullable;
 import android.support.v4.util.ArraySet;
 import android.text.TextUtils;
+import android.util.ArrayMap;
 import com.android.dialer.searchfragment.common.Projections;
 import com.android.dialer.searchfragment.common.QueryFilteringUtil;
 import java.lang.annotation.Retention;
@@ -40,6 +41,7 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Locale;
+import java.util.Map;
 import java.util.Set;
 
 /**
@@ -108,25 +110,24 @@
   private static Cursor createCursor(Cursor cursor) {
     // Convert cursor rows into Cp2Contacts
     List<Cp2Contact> cp2Contacts = new ArrayList<>();
-    Set<Integer> contactIds = new ArraySet<>();
+    Map<Integer, Integer> contactIdsToPosition = new ArrayMap<>();
     cursor.moveToPosition(-1);
     while (cursor.moveToNext()) {
       Cp2Contact contact = Cp2Contact.fromCursor(cursor);
       cp2Contacts.add(contact);
-      contactIds.add(contact.contactId());
+      contactIdsToPosition.put(contact.contactId(), cursor.getPosition());
     }
     cursor.close();
 
     // Group then combine contact data
     List<Cp2Contact> coalescedContacts = new ArrayList<>();
-    for (Integer contactId : contactIds) {
+    for (Integer contactId : contactIdsToPosition.keySet()) {
       List<Cp2Contact> duplicateContacts = getAllContactsWithContactId(contactId, cp2Contacts);
       coalescedContacts.addAll(coalesceContacts(duplicateContacts));
     }
 
-    // Sort by display name, then build new cursor from coalesced contacts.
-    // We sort the contacts so that they are displayed to the user in lexicographic order.
-    Collections.sort(coalescedContacts, (o1, o2) -> o1.displayName().compareTo(o2.displayName()));
+    // Sort the contacts back into the exact same order they were inside of {@code cursor}
+    Collections.sort(coalescedContacts, (o1, o2) -> compare(contactIdsToPosition, o1, o2));
     MatrixCursor newCursor = new MatrixCursor(Projections.CP2_PROJECTION, coalescedContacts.size());
     for (Cp2Contact contact : coalescedContacts) {
       newCursor.addRow(contact.toCursorRow());
@@ -166,6 +167,13 @@
     return coalescedContacts;
   }
 
+  private static int compare(
+      Map<Integer, Integer> contactIdsToPosition, Cp2Contact o1, Cp2Contact o2) {
+    int position1 = contactIdsToPosition.get(o1.contactId());
+    int position2 = contactIdsToPosition.get(o2.contactId());
+    return Integer.compare(position1, position2);
+  }
+
   private static void removeDuplicatePhoneNumbers(List<Cp2Contact> phoneContacts) {
     for (int i = 0; i < phoneContacts.size(); i++) {
       Cp2Contact contact1 = phoneContacts.get(i);
diff --git a/java/com/android/dialer/searchfragment/cp2/SearchContactViewHolder.java b/java/com/android/dialer/searchfragment/cp2/SearchContactViewHolder.java
index 386ab3a..e36df4b 100644
--- a/java/com/android/dialer/searchfragment/cp2/SearchContactViewHolder.java
+++ b/java/com/android/dialer/searchfragment/cp2/SearchContactViewHolder.java
@@ -171,6 +171,8 @@
         callToActionView.setVisibility(View.VISIBLE);
         callToActionView.setImageDrawable(
             context.getDrawable(com.android.contacts.common.R.drawable.ic_phone_attach));
+        callToActionView.setContentDescription(
+            context.getString(R.string.description_search_call_and_share));
         callToActionView.setOnClickListener(this);
         break;
       case CallToAction.DUO_CALL:
@@ -178,6 +180,8 @@
         callToActionView.setVisibility(View.VISIBLE);
         callToActionView.setImageDrawable(
             context.getDrawable(R.drawable.quantum_ic_videocam_white_24));
+        callToActionView.setContentDescription(
+            context.getString(R.string.description_search_video_call));
         callToActionView.setOnClickListener(this);
         break;
       default:
diff --git a/java/com/android/dialer/searchfragment/cp2/SearchContactsCursorLoader.java b/java/com/android/dialer/searchfragment/cp2/SearchContactsCursorLoader.java
index 2b7af11..7624bc7 100644
--- a/java/com/android/dialer/searchfragment/cp2/SearchContactsCursorLoader.java
+++ b/java/com/android/dialer/searchfragment/cp2/SearchContactsCursorLoader.java
@@ -24,6 +24,7 @@
 import android.provider.ContactsContract.CommonDataKinds.Phone;
 import android.provider.ContactsContract.Data;
 import android.support.annotation.Nullable;
+import com.android.contacts.common.preference.ContactsPreferences;
 import com.android.dialer.searchfragment.common.Projections;
 
 /** Cursor Loader for CP2 contacts. */
@@ -41,6 +42,14 @@
         null,
         Phone.SORT_KEY_PRIMARY + " ASC");
     this.query = query;
+
+    ContactsPreferences preferences = new ContactsPreferences(getContext());
+    if (preferences.getSortOrder() == ContactsPreferences.SORT_ORDER_ALTERNATIVE) {
+      setSortOrder(Phone.SORT_KEY_ALTERNATIVE + " ASC");
+    }
+    if (preferences.getDisplayOrder() == ContactsPreferences.DISPLAY_ORDER_ALTERNATIVE) {
+      setProjection(Projections.CP2_PROJECTION_ALTERNATIVE);
+    }
   }
 
   /**