Handling timeout in PeopleApiPhoneLookup & Cp2ExtendedDirectoryPhoneLookup

Bug: 70989631
Test: PeopleApiPhoneLookupTest, Cp2ExtendedDirectoryPhoneLookupTest
PiperOrigin-RevId: 199365461
Change-Id: I017cca8bdb469236ac60382e5fbe7f55976a12b9
diff --git a/java/com/android/dialer/logging/dialer_impression.proto b/java/com/android/dialer/logging/dialer_impression.proto
index 55cef9a..96775ca 100644
--- a/java/com/android/dialer/logging/dialer_impression.proto
+++ b/java/com/android/dialer/logging/dialer_impression.proto
@@ -12,7 +12,7 @@
   // Event enums to be used for Impression Logging in Dialer.
   // It's perfectly acceptable for this enum to be large
   // Values should be from 1000 to 100000.
-  // Next Tag: 1397
+  // Next Tag: 1399
   enum Type {
     UNKNOWN_AOSP_EVENT_TYPE = 1000;
 
@@ -779,5 +779,9 @@
     IN_CALL_SWITCH_AUDIO_ROUTE_WIRED_HEADSET = 1394;
     IN_CALL_SWITCH_AUDIO_ROUTE_EARPIECE = 1395;
     IN_CALL_SWITCH_AUDIO_ROUTE_BLUETOOTH = 1396;
+
+    // Impressions for PhoneLookup
+    PEOPLE_API_PHONE_LOOKUP_TIMEOUT = 1397;
+    CP2_EXTENDED_DIRECTORY_PHONE_LOOKUP_TIMEOUT = 1398;
   }
 }
diff --git a/java/com/android/dialer/phonelookup/cp2/Cp2ExtendedDirectoryPhoneLookup.java b/java/com/android/dialer/phonelookup/cp2/Cp2ExtendedDirectoryPhoneLookup.java
index 5289593..bbbd186 100644
--- a/java/com/android/dialer/phonelookup/cp2/Cp2ExtendedDirectoryPhoneLookup.java
+++ b/java/com/android/dialer/phonelookup/cp2/Cp2ExtendedDirectoryPhoneLookup.java
@@ -26,7 +26,11 @@
 import com.android.dialer.common.LogUtil;
 import com.android.dialer.common.concurrent.Annotations.BackgroundExecutor;
 import com.android.dialer.common.concurrent.Annotations.LightweightExecutor;
+import com.android.dialer.common.concurrent.Annotations.NonUiSerial;
+import com.android.dialer.configprovider.ConfigProvider;
 import com.android.dialer.inject.ApplicationContext;
+import com.android.dialer.logging.DialerImpression;
+import com.android.dialer.logging.Logger;
 import com.android.dialer.phonelookup.PhoneLookup;
 import com.android.dialer.phonelookup.PhoneLookupInfo;
 import com.android.dialer.phonelookup.PhoneLookupInfo.Cp2Info;
@@ -39,6 +43,9 @@
 import com.google.common.util.concurrent.ListeningExecutorService;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
 import java.util.function.Predicate;
 import javax.inject.Inject;
 
@@ -51,20 +58,31 @@
 @SuppressWarnings("AndroidApiChecker") // Use of Java 8 APIs.
 public final class Cp2ExtendedDirectoryPhoneLookup implements PhoneLookup<Cp2Info> {
 
+  /** Config flag for timeout (in ms). */
+  @VisibleForTesting
+  static final String CP2_EXTENDED_DIRECTORY_PHONE_LOOKUP_TIMEOUT_MILLIS =
+      "cp2_extended_directory_phone_lookup_timout_millis";
+
   private final Context appContext;
+  private final ConfigProvider configProvider;
   private final ListeningExecutorService backgroundExecutorService;
   private final ListeningExecutorService lightweightExecutorService;
   private final MissingPermissionsOperations missingPermissionsOperations;
+  private final ScheduledExecutorService scheduledExecutorService;
 
   @Inject
   Cp2ExtendedDirectoryPhoneLookup(
       @ApplicationContext Context appContext,
       @BackgroundExecutor ListeningExecutorService backgroundExecutorService,
       @LightweightExecutor ListeningExecutorService lightweightExecutorService,
+      @NonUiSerial ScheduledExecutorService scheduledExecutorService,
+      ConfigProvider configProvider,
       MissingPermissionsOperations missingPermissionsOperations) {
     this.appContext = appContext;
     this.backgroundExecutorService = backgroundExecutorService;
     this.lightweightExecutorService = lightweightExecutorService;
+    this.scheduledExecutorService = scheduledExecutorService;
+    this.configProvider = configProvider;
     this.missingPermissionsOperations = missingPermissionsOperations;
   }
 
@@ -73,10 +91,33 @@
     if (!PermissionsUtil.hasContactsReadPermissions(appContext)) {
       return Futures.immediateFuture(Cp2Info.getDefaultInstance());
     }
-    return Futures.transformAsync(
-        queryCp2ForExtendedDirectoryIds(),
-        directoryIds -> queryCp2ForDirectoryContact(dialerPhoneNumber, directoryIds),
-        lightweightExecutorService);
+
+    ListenableFuture<Cp2Info> cp2InfoFuture =
+        Futures.transformAsync(
+            queryCp2ForExtendedDirectoryIds(),
+            directoryIds -> queryCp2ForDirectoryContact(dialerPhoneNumber, directoryIds),
+            lightweightExecutorService);
+
+    long timeoutMillis =
+        configProvider.getLong(CP2_EXTENDED_DIRECTORY_PHONE_LOOKUP_TIMEOUT_MILLIS, Long.MAX_VALUE);
+
+    // Do not pass Long.MAX_VALUE to Futures.withTimeout as it will cause the internal
+    // ScheduledExecutorService for timing to keep waiting even after "cp2InfoFuture" is done.
+    // Do not pass 0 or a negative value to Futures.withTimeout either as it will cause the timeout
+    // event to be triggered immediately.
+    return timeoutMillis == Long.MAX_VALUE
+        ? cp2InfoFuture
+        : Futures.catching(
+            Futures.withTimeout(
+                cp2InfoFuture, timeoutMillis, TimeUnit.MILLISECONDS, scheduledExecutorService),
+            TimeoutException.class,
+            unused -> {
+              LogUtil.w("Cp2ExtendedDirectoryPhoneLookup.lookup", "Time out!");
+              Logger.get(appContext)
+                  .logImpression(DialerImpression.Type.CP2_EXTENDED_DIRECTORY_PHONE_LOOKUP_TIMEOUT);
+              return Cp2Info.getDefaultInstance();
+            },
+            lightweightExecutorService);
   }
 
   private ListenableFuture<List<Long>> queryCp2ForExtendedDirectoryIds() {