Fix collapsing to do fuzzy phone number matching.

-Also use Collapser to collapse the numbers shown in the context menu
from the A-Z list.

Fixes http://b/issue?id=2047514 and http://b/issue?id=2144616

Change-Id: Ice18ecc306c2f30fd1525418bc9f7408c4435a50
diff --git a/src/com/android/contacts/Collapser.java b/src/com/android/contacts/Collapser.java
index db1da1f..3872dfd 100644
--- a/src/com/android/contacts/Collapser.java
+++ b/src/com/android/contacts/Collapser.java
@@ -39,38 +39,42 @@
      */
     public interface Collapsible<T> {
         public boolean collapseWith(T t);
-        public String getCollapseKey();
+        public boolean shouldCollapseWith(T t);
     }
 
     /**
      * Collapses a list of Collapsible items into a list of collapsed items. Items are collapsed
-     * if they produce equal collapseKeys {@Link Collapsible#getCollapseKey()}, and are collapsed
-     * through the {@Link Collapsible#doCollapseWith(Object)} function implemented by the data item.
+     * if {@link Collapsible#shouldCollapseWith(Object) return strue, and are collapsed
+     * through the {@Link Collapsible#collapseWith(Object)} function implemented by the data item.
      *
      * @param list ArrayList of Objects of type <T extends Collapsible<T>> to be collapsed.
      */
     public static <T extends Collapsible<T>> void collapseList(ArrayList<T> list) {
-        HashMap<String, T> collapseMap = new HashMap<String, T>();
-        ArrayList<String> collapseKeys = new ArrayList<String>();
 
         int listSize = list.size();
-        for (int j = 0; j < listSize; j++) {
-            T entry = list.get(j);
-            String collapseKey = entry.getCollapseKey();
-            if (!collapseMap.containsKey(collapseKey)) {
-                collapseMap.put(collapseKey, entry);
-                collapseKeys.add(collapseKey);
-            } else {
-                collapseMap.get(collapseKey).collapseWith(entry);
+
+        for (int i = 0; i < listSize; i++) {
+            T iItem = list.get(i);
+            if (iItem != null) {
+                for (int j = i + 1; j < listSize; j++) {
+                    T jItem = list.get(j);
+                    if (jItem != null) {
+                        if (iItem.shouldCollapseWith(jItem)) {
+                            iItem.collapseWith(jItem);
+                            list.set(j, null);
+                        }
+                    }
+                }
             }
         }
 
-        if (collapseKeys.size() < listSize) {
-            list.clear();
-            Iterator<String> itr = collapseKeys.iterator();
-            while (itr.hasNext()) {
-                list.add(collapseMap.get(itr.next()));
+        // Remove the null items
+        Iterator<T> itr = list.iterator();
+        while (itr.hasNext()) {
+            if (itr.next() == null) {
+                itr.remove();
             }
         }
+
     }
 }
diff --git a/src/com/android/contacts/ContactsListActivity.java b/src/com/android/contacts/ContactsListActivity.java
index cc2f02f..fac461f 100644
--- a/src/com/android/contacts/ContactsListActivity.java
+++ b/src/com/android/contacts/ContactsListActivity.java
@@ -1754,7 +1754,7 @@
                             getColumnIndex(Phone.IS_SUPER_PRIMARY)) != 0) {
                         // Found super primary, call it.
                         phone = phonesCursor.
-                        getString(phonesCursor.getColumnIndex(Phone.NUMBER));
+                                getString(phonesCursor.getColumnIndex(Phone.NUMBER));
                         break;
                     }
                 }
diff --git a/src/com/android/contacts/PhoneDisambigDialog.java b/src/com/android/contacts/PhoneDisambigDialog.java
index 58d3721..b727c77 100644
--- a/src/com/android/contacts/PhoneDisambigDialog.java
+++ b/src/com/android/contacts/PhoneDisambigDialog.java
@@ -17,8 +17,9 @@
 package com.android.contacts;
 
 import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
+import java.util.List;
+
+import com.android.contacts.Collapser.Collapsible;
 
 import android.app.AlertDialog;
 import android.content.ContentUris;
@@ -28,12 +29,13 @@
 import android.database.Cursor;
 import android.provider.ContactsContract.Data;
 import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.telephony.PhoneNumberUtils;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.widget.ArrayAdapter;
 import android.widget.CheckBox;
 import android.widget.CompoundButton;
-import android.widget.SimpleCursorAdapter;
+import android.widget.ListAdapter;
 
 /**
  * Class used for displaying a dialog with a list of phone numbers of which
@@ -47,6 +49,8 @@
     private AlertDialog mDialog;
     private boolean mSendSms;
     private Cursor mPhonesCursor;
+    private ListAdapter mPhonesAdapter;
+    private ArrayList<PhoneItem> mPhoneItemList;
 
     public PhoneDisambigDialog(Context context, Cursor phonesCursor) {
         this(context, phonesCursor, false /*make call*/);
@@ -57,6 +61,11 @@
         mSendSms = sendSms;
         mPhonesCursor = phonesCursor;
 
+        mPhoneItemList = makePhoneItemsList(phonesCursor);
+        Collapser.collapseList(mPhoneItemList);
+
+        mPhonesAdapter = new PhonesAdapter(mContext, mPhoneItemList);
+
         LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
                 Context.LAYOUT_INFLATER_SERVICE);
         View setPrimaryView = inflater.
@@ -66,9 +75,10 @@
 
         // Need to show disambig dialogue.
         AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(mContext).
-            setCursor(mPhonesCursor, this, Phone.NUMBER).
-                    setTitle(sendSms ? R.string.sms_disambig_title : R.string.call_disambig_title).
-                    setView(setPrimaryView);
+                setAdapter(mPhonesAdapter, this).
+                        setTitle(sendSms ?
+                                R.string.sms_disambig_title : R.string.call_disambig_title).
+                        setView(setPrimaryView);
 
         mDialog = dialogBuilder.create();
     }
@@ -77,18 +87,25 @@
      * Show the dialog.
      */
     public void show() {
+        if (mPhoneItemList.size() == 1) {
+            // If there is only one after collapse, just select it, and close;
+            onClick(mDialog, 0);
+            return;
+        }
         mDialog.show();
     }
 
     public void onClick(DialogInterface dialog, int which) {
-        if (mPhonesCursor.moveToPosition(which)) {
-            long id = mPhonesCursor.getLong(mPhonesCursor.getColumnIndex(Data._ID));
-            String phone = mPhonesCursor.getString(mPhonesCursor.getColumnIndex(Phone.NUMBER));
+        if (mPhoneItemList.size() > which && which >= 0) {
+            PhoneItem phoneItem = mPhoneItemList.get(which);
+            long id = phoneItem.id;
+            String phone = phoneItem.phoneNumber;
+
             if (mMakePrimary) {
                 ContentValues values = new ContentValues(1);
                 values.put(Data.IS_SUPER_PRIMARY, 1);
-                mContext.getContentResolver().update(ContentUris.withAppendedId(Data.CONTENT_URI, id),
-                        values, null, null);
+                mContext.getContentResolver().update(ContentUris.
+                        withAppendedId(Data.CONTENT_URI, id), values, null, null);
             }
 
             if (mSendSms) {
@@ -108,4 +125,56 @@
     public void onDismiss(DialogInterface dialog) {
         mPhonesCursor.close();
     }
+
+    private static class PhonesAdapter extends ArrayAdapter<PhoneItem> {
+
+        public PhonesAdapter(Context context, List<PhoneItem> objects) {
+            super(context, android.R.layout.simple_dropdown_item_1line,
+                    android.R.id.text1, objects);
+        }
+    }
+
+    private class PhoneItem implements Collapsible<PhoneItem> {
+
+        String phoneNumber;
+        long id;
+
+        public PhoneItem(String newPhoneNumber, long newId) {
+            phoneNumber = newPhoneNumber;
+            id = newId;
+        }
+
+        public boolean collapseWith(PhoneItem phoneItem) {
+            if (!shouldCollapseWith(phoneItem)) {
+                return false;
+            }
+            // Just keep the number and id we already have.
+            return true;
+        }
+
+        public boolean shouldCollapseWith(PhoneItem phoneItem) {
+            if (PhoneNumberUtils.compare(PhoneDisambigDialog.this.mContext,
+                    phoneNumber, phoneItem.phoneNumber)) {
+                return true;
+            }
+            return false;
+        }
+
+        public String toString() {
+            return phoneNumber;
+        }
+    }
+
+    private ArrayList<PhoneItem> makePhoneItemsList(Cursor phonesCursor) {
+        ArrayList<PhoneItem> phoneList = new ArrayList<PhoneItem>();
+
+        phonesCursor.moveToPosition(-1);
+        while (phonesCursor.moveToNext()) {
+            long id = phonesCursor.getLong(phonesCursor.getColumnIndex(Data._ID));
+            String phone = phonesCursor.getString(phonesCursor.getColumnIndex(Phone.NUMBER));
+            phoneList.add(new PhoneItem(phone, id));
+        }
+
+        return phoneList;
+    }
 }
diff --git a/src/com/android/contacts/ViewContactActivity.java b/src/com/android/contacts/ViewContactActivity.java
index 8275686..b1910d6 100644
--- a/src/com/android/contacts/ViewContactActivity.java
+++ b/src/com/android/contacts/ViewContactActivity.java
@@ -1154,7 +1154,7 @@
     /**
      * A basic structure with the data for a contact entry in the list.
      */
-    static class ViewEntry extends ContactEntryAdapter.Entry implements Collapsible<ViewEntry> {
+    class ViewEntry extends ContactEntryAdapter.Entry implements Collapsible<ViewEntry> {
         public String resPackageName = null;
         public int actionIcon = -1;
         public boolean isPrimary = false;
@@ -1169,7 +1169,7 @@
 
         public boolean collapseWith(ViewEntry entry) {
             // assert equal collapse keys
-            if (!getCollapseKey().equals(entry.getCollapseKey())) {
+            if (!shouldCollapseWith(entry)) {
                 return false;
             }
 
@@ -1201,16 +1201,43 @@
             return true;
         }
 
-        public String getCollapseKey() {
-            StringBuilder hashSb = new StringBuilder();
-            hashSb.append(data);
-            hashSb.append(mimetype);
-            hashSb.append((intent != null && intent.getAction() != null)
-                    ? intent.getAction() : "");
-            hashSb.append((secondaryIntent != null && secondaryIntent.getAction() != null)
-                    ? secondaryIntent.getAction() : "");
-            hashSb.append(actionIcon);
-            return hashSb.toString();
+        public boolean shouldCollapseWith(ViewEntry entry) {
+            if (entry == null) {
+                return false;
+            }
+
+            if (Phone.CONTENT_ITEM_TYPE.equals(mimetype)
+                    && Phone.CONTENT_ITEM_TYPE.equals(entry.mimetype)) {
+                if (!PhoneNumberUtils.compare(ViewContactActivity.this, data, entry.data)) {
+                    return false;
+                }
+            } else {
+                if (!equals(data, entry.data)) {
+                    return false;
+                }
+            }
+
+            if (!equals(mimetype, entry.mimetype)
+                    || !intentCollapsible(intent, entry.intent)
+                    || !intentCollapsible(secondaryIntent, entry.secondaryIntent)
+                    || actionIcon != entry.actionIcon) {
+                return false;
+            }
+
+            return true;
+        }
+
+        private boolean equals(Object a, Object b) {
+            return a==b || (a != null && a.equals(b));
+        }
+
+        private boolean intentCollapsible(Intent a, Intent b) {
+            if (a == b) {
+                return true;
+            } else if ((a != null && b != null) && equals(a.getAction(), b.getAction())) {
+                return true;
+            }
+            return false;
         }
     }