Improved support for post dial digits in new call log.

-Don't ever coalesce rows with different post-dial digits
-Made matching of unparsable numbers a little more intelligent by comparing national/postdial portions which have undialable characters removed (rather than exact string match)
-Read and append the post-dial digits from the system call log when building DialerPhoneNumbers to place in the AnnotatedCallLog. Note: PhoneNumberUtil will parse numbers with exactly one post-dial character, but not more than one.
-Use post-dial digits when building the AnnotatedCallLog's FORMATTED_NUMBER value
-Display the formatted number in CallDetails when the name is missing, instead of the unformatted number
-Don't set the displayNumber in CallDetails when the name is missing, because we are showing the (formatted) number via the nameOrNumber field.
-Treat numbers with post-dial digits as invalid in PartitionedNumbers; batch operations are not possible with these numbers because their normalized representations strip the post-dial digits (and they are significant for contact matching)

Bug: 70989632
Test: unit and manual
PiperOrigin-RevId: 182557754
Change-Id: Idcdefce0946a189e5b350a53ec2a16a96a8d4552
diff --git a/java/com/android/dialer/calllog/database/Coalescer.java b/java/com/android/dialer/calllog/database/Coalescer.java
index 3c2585e..b10dea2 100644
--- a/java/com/android/dialer/calllog/database/Coalescer.java
+++ b/java/com/android/dialer/calllog/database/Coalescer.java
@@ -156,6 +156,10 @@
       return false;
     }
 
+    if (!meetsAssistedDialingCriteria(row1, row2)) {
+      return false;
+    }
+
     DialerPhoneNumber number1;
     DialerPhoneNumber number2;
     try {
@@ -172,15 +176,6 @@
     } catch (InvalidProtocolBufferException e) {
       throw Assert.createAssertionFailException("error parsing DialerPhoneNumber proto", e);
     }
-
-    if (!number1.hasDialerInternalPhoneNumber() || !number2.hasDialerInternalPhoneNumber()) {
-      // An empty number should not be combined with any other number.
-      return false;
-    }
-
-    if (!meetsAssistedDialingCriteria(row1, row2)) {
-      return false;
-    }
     return dialerPhoneNumberUtil.isMatch(number1, number2);
   }
 
diff --git a/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java b/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java
index 34e6069..ee169e1 100644
--- a/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java
+++ b/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java
@@ -235,7 +235,7 @@
                 new String[] {
                   Calls._ID,
                   Calls.DATE,
-                  Calls.LAST_MODIFIED,
+                  Calls.LAST_MODIFIED, // TODO(a bug): Not available in M
                   Calls.NUMBER,
                   Calls.TYPE,
                   Calls.COUNTRY_ISO,
@@ -248,8 +248,10 @@
                   Calls.GEOCODED_LOCATION,
                   Calls.PHONE_ACCOUNT_COMPONENT_NAME,
                   Calls.PHONE_ACCOUNT_ID,
-                  Calls.FEATURES
+                  Calls.FEATURES,
+                  Calls.POST_DIAL_DIGITS, // TODO(a bug): Not available in M
                 },
+                // TODO(a bug): LAST_MODIFIED not available on M
                 Calls.LAST_MODIFIED + " > ? AND " + Voicemails.DELETED + " = 0",
                 new String[] {String.valueOf(previousTimestampProcessed)},
                 Calls.LAST_MODIFIED + " DESC LIMIT 1000")) {
@@ -282,6 +284,7 @@
             cursor.getColumnIndexOrThrow(Calls.PHONE_ACCOUNT_COMPONENT_NAME);
         int phoneAccountIdColumn = cursor.getColumnIndexOrThrow(Calls.PHONE_ACCOUNT_ID);
         int featuresColumn = cursor.getColumnIndexOrThrow(Calls.FEATURES);
+        int postDialDigitsColumn = cursor.getColumnIndexOrThrow(Calls.POST_DIAL_DIGITS);
 
         // The cursor orders by LAST_MODIFIED DESC, so the first result is the most recent timestamp
         // processed.
@@ -302,19 +305,24 @@
           String phoneAccountComponentName = cursor.getString(phoneAccountComponentColumn);
           String phoneAccountId = cursor.getString(phoneAccountIdColumn);
           int features = cursor.getInt(featuresColumn);
+          String postDialDigits = cursor.getString(postDialDigitsColumn);
 
           ContentValues contentValues = new ContentValues();
           contentValues.put(AnnotatedCallLog.TIMESTAMP, date);
 
           if (!TextUtils.isEmpty(numberAsStr)) {
+            String numberWithPostDialDigits =
+                postDialDigits == null ? numberAsStr : numberAsStr + postDialDigits;
             DialerPhoneNumber dialerPhoneNumber =
-                dialerPhoneNumberUtil.parse(numberAsStr, countryIso);
+                dialerPhoneNumberUtil.parse(numberWithPostDialDigits, countryIso);
 
             contentValues.put(AnnotatedCallLog.NUMBER, dialerPhoneNumber.toByteArray());
-            contentValues.put(
-                AnnotatedCallLog.FORMATTED_NUMBER,
-                PhoneNumberUtils.formatNumber(numberAsStr, countryIso));
-            // TODO(zachh): Need to handle post-dial digits; different on N and M.
+            String formattedNumber =
+                PhoneNumberUtils.formatNumber(numberWithPostDialDigits, countryIso);
+            if (formattedNumber == null) {
+              formattedNumber = numberWithPostDialDigits;
+            }
+            contentValues.put(AnnotatedCallLog.FORMATTED_NUMBER, formattedNumber);
           } else {
             contentValues.put(
                 AnnotatedCallLog.NUMBER, DialerPhoneNumber.getDefaultInstance().toByteArray());
diff --git a/java/com/android/dialer/calllog/ui/menu/Modules.java b/java/com/android/dialer/calllog/ui/menu/Modules.java
index fd0606e..67e5168 100644
--- a/java/com/android/dialer/calllog/ui/menu/Modules.java
+++ b/java/com/android/dialer/calllog/ui/menu/Modules.java
@@ -130,17 +130,17 @@
 
     if (!row.numberAttributes().getName().isEmpty()) {
       dialerContactBuilder.setNameOrNumber(row.numberAttributes().getName());
-    } else if (!TextUtils.isEmpty(originalNumber)) {
-      dialerContactBuilder.setNameOrNumber(originalNumber);
+      if (row.formattedNumber() != null) {
+        dialerContactBuilder.setDisplayNumber(row.formattedNumber());
+      }
+    } else if (!TextUtils.isEmpty(row.formattedNumber())) {
+      dialerContactBuilder.setNameOrNumber(row.formattedNumber());
     }
 
     dialerContactBuilder.setNumberLabel(row.numberAttributes().getNumberTypeLabel());
     dialerContactBuilder.setPhotoUri(row.numberAttributes().getPhotoUri());
     dialerContactBuilder.setContactUri(row.numberAttributes().getLookupUri());
 
-    if (row.formattedNumber() != null) {
-      dialerContactBuilder.setDisplayNumber(row.formattedNumber());
-    }
     return dialerContactBuilder.build();
   }
 }
diff --git a/java/com/android/dialer/phonenumberproto/DialerPhoneNumberUtil.java b/java/com/android/dialer/phonenumberproto/DialerPhoneNumberUtil.java
index d37f7fa..4e7d300 100644
--- a/java/com/android/dialer/phonenumberproto/DialerPhoneNumberUtil.java
+++ b/java/com/android/dialer/phonenumberproto/DialerPhoneNumberUtil.java
@@ -103,9 +103,19 @@
   }
 
   /**
-   * Returns true if the two numbers were parseable by libphonenumber and are a {@link
-   * MatchType#SHORT_NSN_MATCH} or {@link MatchType#NSN_MATCH} or {@link MatchType#EXACT_MATCH} or
-   * if they have the same raw input.
+   * Returns true if the two numbers:
+   *
+   * <ul>
+   *   <li>were parseable by libphonenumber (see {@link #parse(String, String)}),
+   *   <li>are a {@link MatchType#SHORT_NSN_MATCH}, {@link MatchType#NSN_MATCH}, or {@link
+   *       MatchType#EXACT_MATCH}, and
+   *   <li>have the same post-dial digits.
+   * </ul>
+   *
+   * <p>If either number is not parseable, returns true if their raw inputs have the same network
+   * and post-dial portions.
+   *
+   * <p>An empty number is never considered to match another number.
    *
    * @see PhoneNumberUtil#isNumberMatch(PhoneNumber, PhoneNumber)
    */
@@ -115,15 +125,34 @@
     Assert.isWorkerThread();
     if (!Assert.isNotNull(firstNumberIn).hasDialerInternalPhoneNumber()
         || !Assert.isNotNull(secondNumberIn).hasDialerInternalPhoneNumber()) {
-      return firstNumberIn.getRawInput().equals(secondNumberIn.getRawInput());
+      // An empty number should not be combined with any other number.
+      if (firstNumberIn.getRawInput().getNumber().isEmpty()
+          || secondNumberIn.getRawInput().getNumber().isEmpty()) {
+        return false;
+      }
+      // Both the network and post-dial portions of the number should match.
+      return sameNetworkPortion(firstNumberIn, secondNumberIn)
+          && samePostDialPortion(firstNumberIn, secondNumberIn);
     }
     MatchType matchType =
         isNumberMatch(
             firstNumberIn.getDialerInternalPhoneNumber(),
             secondNumberIn.getDialerInternalPhoneNumber());
-    return matchType == MatchType.SHORT_NSN_MATCH
-        || matchType == MatchType.NSN_MATCH
-        || matchType == MatchType.EXACT_MATCH;
+
+    return (matchType == MatchType.SHORT_NSN_MATCH
+            || matchType == MatchType.NSN_MATCH
+            || matchType == MatchType.EXACT_MATCH)
+        && samePostDialPortion(firstNumberIn, secondNumberIn);
+  }
+
+  private static boolean sameNetworkPortion(DialerPhoneNumber number1, DialerPhoneNumber number2) {
+    return PhoneNumberUtils.extractNetworkPortion(number1.getRawInput().getNumber())
+        .equals(PhoneNumberUtils.extractNetworkPortion(number2.getRawInput().getNumber()));
+  }
+
+  private static boolean samePostDialPortion(DialerPhoneNumber number1, DialerPhoneNumber number2) {
+    return PhoneNumberUtils.extractPostDialPortion(number1.getRawInput().getNumber())
+        .equals(PhoneNumberUtils.extractPostDialPortion(number2.getRawInput().getNumber()));
   }
 
   /**
diff --git a/java/com/android/dialer/phonenumberproto/PartitionedNumbers.java b/java/com/android/dialer/phonenumberproto/PartitionedNumbers.java
index 4c8ac2f..0a4aafa 100644
--- a/java/com/android/dialer/phonenumberproto/PartitionedNumbers.java
+++ b/java/com/android/dialer/phonenumberproto/PartitionedNumbers.java
@@ -20,6 +20,7 @@
 import android.support.annotation.WorkerThread;
 import android.support.v4.util.ArrayMap;
 import android.support.v4.util.ArraySet;
+import android.telephony.PhoneNumberUtils;
 import com.android.dialer.DialerPhoneNumber;
 import com.android.dialer.common.Assert;
 import com.google.common.base.Optional;
@@ -49,7 +50,18 @@
 
     for (DialerPhoneNumber dialerPhoneNumber : dialerPhoneNumbers) {
       Optional<String> optValidE164 = dialerPhoneNumberUtil.formatToValidE164(dialerPhoneNumber);
-      if (optValidE164.isPresent()) {
+      /*
+       * Numbers with post-dial digits are considered valid and can be converted to E164, but their
+       * post dial digits are lost in the process. Similarly, if a contact's number has a post-dial
+       * digits, the normalized version of it stored in the contacts database does not include the
+       * post dial digits.
+       *
+       * A number with post-dial digits should not match a contact whose number does not have
+       * post-dial digits, which means that we cannot normalize such numbers for use in bulk lookup.
+       * Treat them as invalid which will cause them to be processed individually using
+       * ContactsContract.PHONE_LOOKUP.
+       */
+      if (optValidE164.isPresent() && !hasPostDialDigits(dialerPhoneNumber)) {
         String validE164 = optValidE164.get();
         Set<DialerPhoneNumber> currentNumbers = e164MapBuilder.get(validE164);
         if (currentNumbers == null) {
@@ -72,6 +84,11 @@
     invalidNumbersToDialerPhoneNumbers = makeImmutable(invalidMapBuilder);
   }
 
+  private boolean hasPostDialDigits(DialerPhoneNumber dialerPhoneNumber) {
+    return !PhoneNumberUtils.extractPostDialPortion(dialerPhoneNumber.getRawInput().getNumber())
+        .isEmpty();
+  }
+
   /** Returns the set of invalid numbers from the original DialerPhoneNumbers */
   @NonNull
   public ImmutableSet<String> invalidNumbers() {