Implement CequintPhoneLookup

Bug: 70989584
Test: CequintPhoneLookupTest
PiperOrigin-RevId: 194493484
Change-Id: I59f9b334f0a218e7ba1c34948db4ddbf11490df3
diff --git a/java/com/android/dialer/app/calllog/CallLogFragment.java b/java/com/android/dialer/app/calllog/CallLogFragment.java
index 4e968f0..1e55c63 100644
--- a/java/com/android/dialer/app/calllog/CallLogFragment.java
+++ b/java/com/android/dialer/app/calllog/CallLogFragment.java
@@ -449,7 +449,7 @@
     super.onStart();
     CequintCallerIdManager cequintCallerIdManager = null;
     if (CequintCallerIdManager.isCequintCallerIdEnabled(getContext())) {
-      cequintCallerIdManager = CequintCallerIdManager.createInstanceForCallLog();
+      cequintCallerIdManager = new CequintCallerIdManager();
     }
     contactInfoCache.setCequintCallerIdManager(cequintCallerIdManager);
   }
diff --git a/java/com/android/dialer/oem/CequintCallerIdManager.java b/java/com/android/dialer/oem/CequintCallerIdManager.java
index 55cafc1..53f5352 100644
--- a/java/com/android/dialer/oem/CequintCallerIdManager.java
+++ b/java/com/android/dialer/oem/CequintCallerIdManager.java
@@ -24,6 +24,7 @@
 import android.support.annotation.AnyThread;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
+import android.support.annotation.VisibleForTesting;
 import android.support.annotation.WorkerThread;
 import android.telephony.PhoneNumberUtils;
 import android.text.TextUtils;
@@ -45,7 +46,8 @@
 @TargetApi(VERSION_CODES.N)
 public class CequintCallerIdManager {
 
-  private static final String CONFIG_CALLER_ID_ENABLED = "config_caller_id_enabled";
+  @VisibleForTesting
+  public static final String CONFIG_CALLER_ID_ENABLED = "config_caller_id_enabled";
 
   private static final int CALLER_ID_LOOKUP_USER_PROVIDED_CID = 0x0001;
   private static final int CALLER_ID_LOOKUP_SYSTEM_PROVIDED_CID = 0x0002;
@@ -53,36 +55,43 @@
 
   private static final String[] EMPTY_PROJECTION = new String[] {};
 
-  // Column names in Cequint provider.
-  private static final String CITY_NAME = "cid_pCityName";
-  private static final String STATE_NAME = "cid_pStateName";
-  private static final String STATE_ABBR = "cid_pStateAbbr";
-  private static final String COUNTRY_NAME = "cid_pCountryName";
-  private static final String COMPANY = "cid_pCompany";
-  private static final String NAME = "cid_pName";
-  private static final String FIRST_NAME = "cid_pFirstName";
-  private static final String LAST_NAME = "cid_pLastName";
-  private static final String IMAGE = "cid_pLogo";
-  private static final String DISPLAY_NAME = "cid_pDisplayName";
+  /** Column names in Cequint content provider. */
+  @VisibleForTesting
+  public static final class CequintColumnNames {
+    public static final String CITY_NAME = "cid_pCityName";
+    public static final String STATE_NAME = "cid_pStateName";
+    public static final String STATE_ABBR = "cid_pStateAbbr";
+    public static final String COUNTRY_NAME = "cid_pCountryName";
+    public static final String COMPANY = "cid_pCompany";
+    public static final String NAME = "cid_pName";
+    public static final String FIRST_NAME = "cid_pFirstName";
+    public static final String LAST_NAME = "cid_pLastName";
+    public static final String PHOTO_URI = "cid_pLogo";
+    public static final String DISPLAY_NAME = "cid_pDisplayName";
+  }
 
   private static boolean hasAlreadyCheckedCequintCallerIdPackage;
   private static String cequintProviderAuthority;
 
-  // TODO(wangqi): Revisit it and maybe remove it if it's not necessary.
-  private final ConcurrentHashMap<String, CequintCallerIdContact> callLogCache;
+  // TODO(a bug): Revisit it and maybe remove it if it's not necessary.
+  private final ConcurrentHashMap<String, CequintCallerIdContact> callLogCache =
+      new ConcurrentHashMap<>();
 
   /** Cequint caller ID contact information. */
   @AutoValue
   public abstract static class CequintCallerIdContact {
 
+    @Nullable
     public abstract String name();
 
     /**
      * Description of the geolocation (e.g., "Mountain View, CA"), which is for display purpose
      * only.
      */
+    @Nullable
     public abstract String geolocation();
 
+    @Nullable
     public abstract String photoUri();
 
     static Builder builder() {
@@ -91,11 +100,11 @@
 
     @AutoValue.Builder
     abstract static class Builder {
-      abstract Builder setName(String name);
+      abstract Builder setName(@Nullable String name);
 
-      abstract Builder setGeolocation(String geolocation);
+      abstract Builder setGeolocation(@Nullable String geolocation);
 
-      abstract Builder setPhotoUri(String photoUri);
+      abstract Builder setPhotoUri(@Nullable String photoUri);
 
       abstract CequintCallerIdContact build();
     }
@@ -125,17 +134,14 @@
     return cequintProviderAuthority != null;
   }
 
-  public static CequintCallerIdManager createInstanceForCallLog() {
-    return new CequintCallerIdManager();
-  }
-
+  /** Returns a {@link CequintCallerIdContact} for a call. */
   @WorkerThread
   @Nullable
-  public static CequintCallerIdContact getCequintCallerIdContactForInCall(
+  public static CequintCallerIdContact getCequintCallerIdContactForCall(
       Context context, String number, String cnapName, boolean isIncoming) {
     Assert.isWorkerThread();
     LogUtil.d(
-        "CequintCallerIdManager.getCequintCallerIdContactForInCall",
+        "CequintCallerIdManager.getCequintCallerIdContactForCall",
         "number: %s, cnapName: %s, isIncoming: %b",
         LogUtil.sanitizePhoneNumber(number),
         LogUtil.sanitizePii(cnapName),
@@ -151,29 +157,51 @@
     return lookup(context, getIncallLookupUri(), number, flags);
   }
 
+  /**
+   * Returns a cached {@link CequintCallerIdContact} associated with the provided number. If no
+   * contact can be found in the cache, look up the number using the Cequint content provider.
+   *
+   * @deprecated This method is for the old call log only. New code should use {@link
+   *     #getCequintCallerIdContactForNumber(Context, String)}.
+   */
+  @Deprecated
   @WorkerThread
   @Nullable
-  public CequintCallerIdContact getCequintCallerIdContact(Context context, String number) {
+  public CequintCallerIdContact getCachedCequintCallerIdContact(Context context, String number) {
     Assert.isWorkerThread();
     LogUtil.d(
-        "CequintCallerIdManager.getCequintCallerIdContact",
+        "CequintCallerIdManager.getCachedCequintCallerIdContact",
         "number: %s",
         LogUtil.sanitizePhoneNumber(number));
     if (callLogCache.containsKey(number)) {
       return callLogCache.get(number);
     }
     CequintCallerIdContact cequintCallerIdContact =
-        lookup(
-            context,
-            getLookupUri(),
-            PhoneNumberUtils.stripSeparators(number),
-            new String[] {"system"});
+        getCequintCallerIdContactForNumber(context, number);
     if (cequintCallerIdContact != null) {
       callLogCache.put(number, cequintCallerIdContact);
     }
     return cequintCallerIdContact;
   }
 
+  /**
+   * Returns a {@link CequintCallerIdContact} associated with the provided number by looking it up
+   * using the Cequint content provider.
+   */
+  @WorkerThread
+  @Nullable
+  public static CequintCallerIdContact getCequintCallerIdContactForNumber(
+      Context context, String number) {
+    Assert.isWorkerThread();
+    LogUtil.d(
+        "CequintCallerIdManager.getCequintCallerIdContactForNumber",
+        "number: %s",
+        LogUtil.sanitizePhoneNumber(number));
+
+    return lookup(
+        context, getLookupUri(), PhoneNumberUtils.stripSeparators(number), new String[] {"system"});
+  }
+
   @WorkerThread
   @Nullable
   private static CequintCallerIdContact lookup(
@@ -185,16 +213,17 @@
     try (Cursor cursor =
         context.getContentResolver().query(uri, EMPTY_PROJECTION, number, flags, null)) {
       if (cursor != null && cursor.moveToFirst()) {
-        String city = getString(cursor, cursor.getColumnIndex(CITY_NAME));
-        String state = getString(cursor, cursor.getColumnIndex(STATE_NAME));
-        String stateAbbr = getString(cursor, cursor.getColumnIndex(STATE_ABBR));
-        String country = getString(cursor, cursor.getColumnIndex(COUNTRY_NAME));
-        String company = getString(cursor, cursor.getColumnIndex(COMPANY));
-        String name = getString(cursor, cursor.getColumnIndex(NAME));
-        String firstName = getString(cursor, cursor.getColumnIndex(FIRST_NAME));
-        String lastName = getString(cursor, cursor.getColumnIndex(LAST_NAME));
-        String photoUri = getString(cursor, cursor.getColumnIndex(IMAGE));
-        String displayName = getString(cursor, cursor.getColumnIndex(DISPLAY_NAME));
+        String city = getString(cursor, cursor.getColumnIndex(CequintColumnNames.CITY_NAME));
+        String state = getString(cursor, cursor.getColumnIndex(CequintColumnNames.STATE_NAME));
+        String stateAbbr = getString(cursor, cursor.getColumnIndex(CequintColumnNames.STATE_ABBR));
+        String country = getString(cursor, cursor.getColumnIndex(CequintColumnNames.COUNTRY_NAME));
+        String company = getString(cursor, cursor.getColumnIndex(CequintColumnNames.COMPANY));
+        String name = getString(cursor, cursor.getColumnIndex(CequintColumnNames.NAME));
+        String firstName = getString(cursor, cursor.getColumnIndex(CequintColumnNames.FIRST_NAME));
+        String lastName = getString(cursor, cursor.getColumnIndex(CequintColumnNames.LAST_NAME));
+        String photoUri = getString(cursor, cursor.getColumnIndex(CequintColumnNames.PHOTO_URI));
+        String displayName =
+            getString(cursor, cursor.getColumnIndex(CequintColumnNames.DISPLAY_NAME));
 
         String contactName =
             TextUtils.isEmpty(displayName)
@@ -293,8 +322,4 @@
   private static Uri getIncallLookupUri() {
     return Uri.parse("content://" + cequintProviderAuthority + "/incalllookup");
   }
-
-  private CequintCallerIdManager() {
-    callLogCache = new ConcurrentHashMap<>();
-  }
 }
diff --git a/java/com/android/dialer/phonelookup/cequint/CequintPhoneLookup.java b/java/com/android/dialer/phonelookup/cequint/CequintPhoneLookup.java
index ce2cd18..36d0be4 100644
--- a/java/com/android/dialer/phonelookup/cequint/CequintPhoneLookup.java
+++ b/java/com/android/dialer/phonelookup/cequint/CequintPhoneLookup.java
@@ -18,33 +18,85 @@
 
 import android.content.Context;
 import android.telecom.Call;
+import android.text.TextUtils;
 import com.android.dialer.DialerPhoneNumber;
+import com.android.dialer.common.Assert;
+import com.android.dialer.common.concurrent.Annotations.BackgroundExecutor;
+import com.android.dialer.common.concurrent.Annotations.LightweightExecutor;
+import com.android.dialer.inject.ApplicationContext;
+import com.android.dialer.location.GeoUtil;
+import com.android.dialer.oem.CequintCallerIdManager;
+import com.android.dialer.oem.CequintCallerIdManager.CequintCallerIdContact;
 import com.android.dialer.phonelookup.PhoneLookup;
 import com.android.dialer.phonelookup.PhoneLookupInfo;
 import com.android.dialer.phonelookup.PhoneLookupInfo.CequintInfo;
+import com.android.dialer.phonenumberproto.DialerPhoneNumberUtil;
+import com.android.dialer.telecom.TelecomCallUtil;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
 import javax.inject.Inject;
 
 /** PhoneLookup implementation for Cequint. */
 public class CequintPhoneLookup implements PhoneLookup<CequintInfo> {
 
+  private final Context appContext;
+  private final ListeningExecutorService backgroundExecutorService;
+  private final ListeningExecutorService lightweightExecutorService;
+
   @Inject
-  CequintPhoneLookup() {}
+  CequintPhoneLookup(
+      @ApplicationContext Context appContext,
+      @BackgroundExecutor ListeningExecutorService backgroundExecutorService,
+      @LightweightExecutor ListeningExecutorService lightweightExecutorService) {
+    this.appContext = appContext;
+    this.backgroundExecutorService = backgroundExecutorService;
+    this.lightweightExecutorService = lightweightExecutorService;
+  }
 
   @Override
   public ListenableFuture<CequintInfo> lookup(Context appContext, Call call) {
-    // TODO(a bug): Override the default implementation in the PhoneLookup interface
-    // as a Cequint lookup requires info in the provided call.
-    return Futures.immediateFuture(CequintInfo.getDefaultInstance());
+    if (!CequintCallerIdManager.isCequintCallerIdEnabled(appContext)) {
+      return Futures.immediateFuture(CequintInfo.getDefaultInstance());
+    }
+
+    ListenableFuture<DialerPhoneNumber> dialerPhoneNumberFuture =
+        backgroundExecutorService.submit(
+            () -> {
+              DialerPhoneNumberUtil dialerPhoneNumberUtil = new DialerPhoneNumberUtil();
+              return dialerPhoneNumberUtil.parse(
+                  TelecomCallUtil.getNumber(call), GeoUtil.getCurrentCountryIso(appContext));
+            });
+    String callerDisplayName = call.getDetails().getCallerDisplayName();
+    boolean isIncomingCall = (call.getState() == Call.STATE_RINGING);
+
+    return Futures.transformAsync(
+        dialerPhoneNumberFuture,
+        dialerPhoneNumber ->
+            backgroundExecutorService.submit(
+                () ->
+                    buildCequintInfo(
+                        CequintCallerIdManager.getCequintCallerIdContactForCall(
+                            appContext,
+                            Assert.isNotNull(dialerPhoneNumber).getNormalizedNumber(),
+                            callerDisplayName,
+                            isIncomingCall))),
+        lightweightExecutorService);
   }
 
   @Override
   public ListenableFuture<CequintInfo> lookup(DialerPhoneNumber dialerPhoneNumber) {
-    // TODO(a bug): Implement this method.
-    return Futures.immediateFuture(CequintInfo.getDefaultInstance());
+    if (!CequintCallerIdManager.isCequintCallerIdEnabled(appContext)) {
+      return Futures.immediateFuture(CequintInfo.getDefaultInstance());
+    }
+
+    return backgroundExecutorService.submit(
+        () ->
+            buildCequintInfo(
+                CequintCallerIdManager.getCequintCallerIdContactForNumber(
+                    appContext, dialerPhoneNumber.getNormalizedNumber())));
   }
 
   @Override
@@ -75,16 +127,38 @@
 
   @Override
   public void registerContentObservers() {
-    // No content observers for Cequint info.
+    // No need to register a content observer as the Cequint content provider doesn't support batch
+    // queries.
   }
 
   @Override
   public void unregisterContentObservers() {
-    // No content observers for Cequint info.
+    // Nothing to be done as no content observer is registered.
   }
 
   @Override
   public ListenableFuture<Void> clearData() {
     return Futures.immediateFuture(null);
   }
+
+  /**
+   * Builds a {@link CequintInfo} proto based on the given {@link CequintCallerIdContact} returned
+   * by {@link CequintCallerIdManager}.
+   */
+  private static CequintInfo buildCequintInfo(CequintCallerIdContact cequintCallerIdContact) {
+    CequintInfo.Builder cequintInfoBuilder = CequintInfo.newBuilder();
+
+    // Every field in CequintCallerIdContact can be null.
+    if (!TextUtils.isEmpty(cequintCallerIdContact.name())) {
+      cequintInfoBuilder.setName(cequintCallerIdContact.name());
+    }
+    if (!TextUtils.isEmpty(cequintCallerIdContact.geolocation())) {
+      cequintInfoBuilder.setGeolocation(cequintCallerIdContact.geolocation());
+    }
+    if (!TextUtils.isEmpty(cequintCallerIdContact.photoUri())) {
+      cequintInfoBuilder.setPhotoUri(cequintCallerIdContact.photoUri());
+    }
+
+    return cequintInfoBuilder.build();
+  }
 }
diff --git a/java/com/android/dialer/phonenumbercache/ContactInfoHelper.java b/java/com/android/dialer/phonenumbercache/ContactInfoHelper.java
index 4302436..6179b5d 100644
--- a/java/com/android/dialer/phonenumbercache/ContactInfoHelper.java
+++ b/java/com/android/dialer/phonenumbercache/ContactInfoHelper.java
@@ -621,7 +621,7 @@
       return;
     }
     CequintCallerIdContact cequintCallerIdContact =
-        cequintCallerIdManager.getCequintCallerIdContact(context, number);
+        cequintCallerIdManager.getCachedCequintCallerIdContact(context, number);
     if (cequintCallerIdContact == null) {
       return;
     }
diff --git a/java/com/android/incallui/ContactInfoCache.java b/java/com/android/incallui/ContactInfoCache.java
index eefd483..81c7b72 100644
--- a/java/com/android/incallui/ContactInfoCache.java
+++ b/java/com/android/incallui/ContactInfoCache.java
@@ -514,7 +514,7 @@
       return;
     }
     CequintCallerIdContact cequintCallerIdContact =
-        CequintCallerIdManager.getCequintCallerIdContactForInCall(
+        CequintCallerIdManager.getCequintCallerIdContactForCall(
             context, callerInfo.phoneNumber, cnapName, isIncoming);
 
     if (cequintCallerIdContact == null) {