Merge "Take into consideration special characters when we do contact match."
diff --git a/java/com/android/dialer/app/calllog/CallLogGroupBuilder.java b/java/com/android/dialer/app/calllog/CallLogGroupBuilder.java
index 513c8aa..a48de0f 100644
--- a/java/com/android/dialer/app/calllog/CallLogGroupBuilder.java
+++ b/java/com/android/dialer/app/calllog/CallLogGroupBuilder.java
@@ -189,13 +189,26 @@
     mGroupCreator.addGroup(count - groupSize, groupSize);
   }
 
+  /**
+   * Returns true when the two input numbers can be considered identical enough for caller ID
+   * purposes and put in a call log group.
+   */
   @VisibleForTesting
   boolean equalNumbers(@Nullable String number1, @Nullable String number2) {
     if (PhoneNumberHelper.isUriNumber(number1) || PhoneNumberHelper.isUriNumber(number2)) {
       return compareSipAddresses(number1, number2);
-    } else {
-      return PhoneNumberUtils.compare(number1, number2);
     }
+
+    // PhoneNumberUtils.compare(String, String) ignores special characters such as '#'. For example,
+    // it thinks "123" and "#123" are identical enough for caller ID purposes.
+    // When either input number contains special characters, we put the two in the same group iff
+    // their raw numbers are exactly the same.
+    if (PhoneNumberHelper.numberHasSpecialChars(number1)
+        || PhoneNumberHelper.numberHasSpecialChars(number2)) {
+      return PhoneNumberHelper.sameRawNumbers(number1, number2);
+    }
+
+    return PhoneNumberUtils.compare(number1, number2);
   }
 
   private boolean isSameAccount(String name1, String name2, String id1, String id2) {
diff --git a/java/com/android/dialer/phonenumbercache/ContactInfoHelper.java b/java/com/android/dialer/phonenumbercache/ContactInfoHelper.java
index 39e3866..1fd2bff 100644
--- a/java/com/android/dialer/phonenumbercache/ContactInfoHelper.java
+++ b/java/com/android/dialer/phonenumbercache/ContactInfoHelper.java
@@ -355,6 +355,17 @@
         return ContactInfo.EMPTY;
       }
 
+      // The Contacts provider ignores special characters in phone numbers when searching for a
+      // contact. For example, number "123" is considered a match with a contact with number "#123".
+      // We need to check whether the result contains a number that truly matches the query and move
+      // the cursor to that position before building a ContactInfo.
+      boolean hasNumberMatch =
+          PhoneNumberHelper.updateCursorToMatchContactLookupUri(
+              phoneLookupCursor, PhoneQuery.MATCHED_NUMBER, uri);
+      if (!hasNumberMatch) {
+        return ContactInfo.EMPTY;
+      }
+
       String lookupKey = phoneLookupCursor.getString(PhoneQuery.LOOKUP_KEY);
       ContactInfo contactInfo = createPhoneLookupContactInfo(phoneLookupCursor, lookupKey);
       fillAdditionalContactInfo(mContext, contactInfo);
diff --git a/java/com/android/dialer/phonenumberutil/PhoneNumberHelper.java b/java/com/android/dialer/phonenumberutil/PhoneNumberHelper.java
index be1b062..12e1469 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.NonNull;
@@ -27,6 +29,7 @@
 import android.text.BidiFormatter;
 import android.text.TextDirectionHeuristics;
 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;
@@ -50,6 +53,91 @@
   }
 
   /**
+   * Move the given cursor to a position where the number it points to matches the number in a
+   * contact lookup URI.
+   *
+   * <p>We assume the cursor is one returned by the Contacts Provider when the URI asks for a
+   * specific number. This method's behavior is undefined when the cursor doesn't meet the
+   * assumption.
+   *
+   * <p>When determining whether two phone numbers are identical enough for caller ID purposes, the
+   * Contacts Provider ignores special characters such as '#'. 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>In the following description, we use E to denote a number the cursor points to (an existing
+   * contact number), and L to denote the number in the contact lookup URI.
+   *
+   * <p>If neither E nor L contains special characters, return true to indicate a match is found.
+   *
+   * <p>If either E or L contains special characters, return true when the raw numbers of E and L
+   * are the same. Otherwise, move the cursor to its next position and start over.
+   *
+   * <p>Return false in all other circumstances to indicate that no match can be found.
+   *
+   * <p>When no match can be found, the cursor is after the last result when the method returns.
+   *
+   * @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 true if a match can be found.
+   */
+  public static boolean updateCursorToMatchContactLookupUri(
+      @Nullable Cursor cursor, int columnIndexForNumber, @Nullable Uri contactLookupUri) {
+    if (cursor == null || contactLookupUri == null) {
+      return false;
+    }
+
+    if (!cursor.moveToFirst()) {
+      return false;
+    }
+
+    Assert.checkArgument(
+        0 <= columnIndexForNumber && columnIndexForNumber < cursor.getColumnCount());
+
+    String lookupNumber = contactLookupUri.getLastPathSegment();
+    if (TextUtils.isEmpty(lookupNumber)) {
+      return false;
+    }
+
+    boolean lookupNumberHasSpecialChars = numberHasSpecialChars(lookupNumber);
+
+    do {
+      String existingContactNumber = cursor.getString(columnIndexForNumber);
+      boolean existingContactNumberHasSpecialChars = numberHasSpecialChars(existingContactNumber);
+
+      if ((!lookupNumberHasSpecialChars && !existingContactNumberHasSpecialChars)
+          || sameRawNumbers(existingContactNumber, lookupNumber)) {
+        return true;
+      }
+
+    } while (cursor.moveToNext());
+
+    return false;
+  }
+
+  /** Returns true if the input phone number contains special characters. */
+  public static boolean numberHasSpecialChars(String number) {
+    return !TextUtils.isEmpty(number) && number.contains("#");
+  }
+
+  /** Returns true if the raw numbers of the two input phone numbers are the same. */
+  public static boolean sameRawNumbers(String number1, String number2) {
+    String rawNumber1 =
+        PhoneNumberUtils.stripSeparators(PhoneNumberUtils.convertKeypadLettersToDigits(number1));
+    String rawNumber2 =
+        PhoneNumberUtils.stripSeparators(PhoneNumberUtils.convertKeypadLettersToDigits(number2));
+
+    return rawNumber1.equals(rawNumber2);
+  }
+
+  /**
    * 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 5c43b4f..d3b12c1 100644
--- a/java/com/android/incallui/CallerInfo.java
+++ b/java/com/android/incallui/CallerInfo.java
@@ -223,18 +223,28 @@
     long contactId = 0L;
     int columnIndex;
 
+    // Look for the number
+    columnIndex = cursor.getColumnIndex(PhoneLookup.NUMBER);
+    if (columnIndex != -1) {
+      // The Contacts provider ignores special characters in phone numbers when searching for a
+      // contact. For example, number "123" is considered a match with a contact with number "#123".
+      // We need to check whether the result contains a number that truly matches the query and move
+      // the cursor to that position before filling in the fields in CallerInfo.
+      boolean hasNumberMatch =
+          PhoneNumberHelper.updateCursorToMatchContactLookupUri(cursor, columnIndex, contactRef);
+      if (hasNumberMatch) {
+        info.phoneNumber = cursor.getString(columnIndex);
+      } else {
+        return info;
+      }
+    }
+
     // Look for the name
     columnIndex = cursor.getColumnIndex(PhoneLookup.DISPLAY_NAME);
     if (columnIndex != -1) {
       info.name = normalize(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) {