Add timeout to getAdServicesCommonState IPC call

Bug: 362771868
Test: atest UserPrivacyStatusTest PhFlagsTest -c, manual test the flows
Flag: EXEMPT (bug 337358613)
Change-Id: I5b17153acdc745952cf1e77763e545e76d8f003b
diff --git a/src/com/android/ondevicepersonalization/services/Flags.java b/src/com/android/ondevicepersonalization/services/Flags.java
index 2a4bc90..6543834 100644
--- a/src/com/android/ondevicepersonalization/services/Flags.java
+++ b/src/com/android/ondevicepersonalization/services/Flags.java
@@ -337,4 +337,13 @@
     default int getMaxIntValuesLimit() {
         return DEFAULT_MAX_INT_VALUES;
     }
+
+    /**
+     * Default max wait time until timeout for AdServices IPC call
+     */
+    long DEFAULT_ADSERVICES_IPC_CALL_TIMEOUT_IN_MILLIS = 5000L;
+
+    default long getAdservicesIpcCallTimeoutInMillis() {
+        return DEFAULT_ADSERVICES_IPC_CALL_TIMEOUT_IN_MILLIS;
+    }
 }
diff --git a/src/com/android/ondevicepersonalization/services/PhFlags.java b/src/com/android/ondevicepersonalization/services/PhFlags.java
index 73143a3..1111b0a 100644
--- a/src/com/android/ondevicepersonalization/services/PhFlags.java
+++ b/src/com/android/ondevicepersonalization/services/PhFlags.java
@@ -113,6 +113,9 @@
 
     public static final String MAX_INT_VALUES_LIMIT = "max_int_values_limit";
 
+    public static final String KEY_ADSERVICES_IPC_CALL_TIMEOUT_IN_MILLIS =
+            "adservices_ipc_call_timeout_in_millis";
+
     // OnDevicePersonalization Namespace String from DeviceConfig class
     public static final String NAMESPACE_ON_DEVICE_PERSONALIZATION = "on_device_personalization";
 
@@ -470,4 +473,12 @@
                 /* name= */ MAX_INT_VALUES_LIMIT,
                 /* defaultValue= */ DEFAULT_MAX_INT_VALUES);
     }
+
+    @Override
+    public long getAdservicesIpcCallTimeoutInMillis() {
+        return DeviceConfig.getLong(
+                /* namespace= */ NAMESPACE_ON_DEVICE_PERSONALIZATION,
+                /* name= */ KEY_ADSERVICES_IPC_CALL_TIMEOUT_IN_MILLIS,
+                /* defaultValue= */ DEFAULT_ADSERVICES_IPC_CALL_TIMEOUT_IN_MILLIS);
+    }
 }
diff --git a/src/com/android/ondevicepersonalization/services/data/user/AdServicesCommonStatesWrapperImpl.java b/src/com/android/ondevicepersonalization/services/data/user/AdServicesCommonStatesWrapperImpl.java
index fa0eef4..d08c8ab 100644
--- a/src/com/android/ondevicepersonalization/services/data/user/AdServicesCommonStatesWrapperImpl.java
+++ b/src/com/android/ondevicepersonalization/services/data/user/AdServicesCommonStatesWrapperImpl.java
@@ -22,9 +22,12 @@
 import android.adservices.common.AdServicesOutcomeReceiver;
 import android.annotation.NonNull;
 import android.content.Context;
+import android.os.Binder;
 
 import androidx.concurrent.futures.CallbackToFutureAdapter;
 
+import com.android.ondevicepersonalization.internal.util.LoggerFactory;
+import com.android.ondevicepersonalization.services.FlagsFactory;
 import com.android.ondevicepersonalization.services.OnDevicePersonalizationExecutors;
 
 import com.google.common.util.concurrent.FluentFuture;
@@ -33,12 +36,15 @@
 import com.google.common.util.concurrent.MoreExecutors;
 
 import java.util.Objects;
+import java.util.concurrent.TimeUnit;
 
 /**
  * A wrapper for the AdServicesCommonStates API. Used by UserPrivacyStatus to
  * fetch common states from AdServices.
  */
 class AdServicesCommonStatesWrapperImpl implements AdServicesCommonStatesWrapper {
+    private static final String TAG = AdServicesCommonStatesWrapperImpl.class.getSimpleName();
+    private static final LoggerFactory.Logger sLogger = LoggerFactory.getLogger();
     private final Context mContext;
 
     AdServicesCommonStatesWrapperImpl(Context context) {
@@ -49,7 +55,18 @@
         try {
             AdServicesCommonManager manager =
                     Objects.requireNonNull(getAdServicesCommonManager());
-            return FluentFuture.from(getAdServicesResponse(manager))
+            sLogger.d(TAG + ": IPC getAdServicesCommonStates() started");
+            long origId = Binder.clearCallingIdentity();
+            long timeoutInMillis = FlagsFactory.getFlags().getAdservicesIpcCallTimeoutInMillis();
+            Binder.restoreCallingIdentity(origId);
+            ListenableFuture<AdServicesCommonStatesResponse> futureWithTimeout =
+                    Futures.withTimeout(
+                            getAdServicesResponse(manager),
+                            timeoutInMillis,
+                            TimeUnit.MILLISECONDS,
+                            OnDevicePersonalizationExecutors.getScheduledExecutor());
+
+            return FluentFuture.from(futureWithTimeout)
                     .transform(
                             v -> getResultFromResponse(v),
                             MoreExecutors.newDirectExecutorService());
@@ -83,11 +100,15 @@
                                     Exception>() {
                                 @Override
                                 public void onResult(AdServicesCommonStatesResponse result) {
+                                    sLogger.d(
+                                            TAG + ": IPC getAdServicesCommonStates() success");
                                     completer.set(result);
                                 }
 
                                 @Override
                                 public void onError(Exception error) {
+                                    sLogger.e(error,
+                                            TAG + ": IPC getAdServicesCommonStates() error");
                                     completer.setException(error);
                                 }
                             });
diff --git a/src/com/android/ondevicepersonalization/services/data/user/UserPrivacyStatus.java b/src/com/android/ondevicepersonalization/services/data/user/UserPrivacyStatus.java
index c4b91f0..15b698d 100644
--- a/src/com/android/ondevicepersonalization/services/data/user/UserPrivacyStatus.java
+++ b/src/com/android/ondevicepersonalization/services/data/user/UserPrivacyStatus.java
@@ -22,6 +22,7 @@
 import static android.adservices.ondevicepersonalization.Constants.STATUS_METHOD_NOT_FOUND;
 import static android.adservices.ondevicepersonalization.Constants.STATUS_REMOTE_EXCEPTION;
 import static android.adservices.ondevicepersonalization.Constants.STATUS_SUCCESS;
+import static android.adservices.ondevicepersonalization.Constants.STATUS_TIMEOUT;
 
 import static com.android.ondevicepersonalization.services.PhFlags.KEY_ENABLE_PERSONALIZATION_STATUS_OVERRIDE;
 import static com.android.ondevicepersonalization.services.PhFlags.KEY_PERSONALIZATION_STATUS_OVERRIDE_VALUE;
@@ -39,6 +40,8 @@
 import com.android.ondevicepersonalization.services.util.StatsUtils;
 
 import java.util.Objects;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
 
 /**
  * A singleton class that stores all user privacy statuses in memory.
@@ -242,8 +245,8 @@
             int updatedMeasurementState = commonStates.getMeasurementState();
             updateUserControlCache(updatedProtectedAudienceState, updatedMeasurementState);
         } catch (Exception e) {
-            sLogger.e(TAG + ": fetchStateFromAdServices error", e);
             int statusCode = getExceptionStatus(e);
+            sLogger.e(e, TAG + ": fetchStateFromAdServices error, status code %d", statusCode);
             StatsUtils.writeServiceRequestMetrics(
                     API_NAME_ADSERVICES_GET_COMMON_STATES,
                     packageName,
@@ -262,6 +265,9 @@
 
     @VisibleForTesting
     int getExceptionStatus(Exception e) {
+        if (e instanceof ExecutionException && e.getCause() instanceof TimeoutException) {
+            return STATUS_TIMEOUT;
+        }
         if (e instanceof NoSuchMethodException) {
             return STATUS_METHOD_NOT_FOUND;
         }
diff --git a/tests/servicetests/src/com/android/ondevicepersonalization/services/PhFlagsTest.java b/tests/servicetests/src/com/android/ondevicepersonalization/services/PhFlagsTest.java
index d967044..49f3c48 100644
--- a/tests/servicetests/src/com/android/ondevicepersonalization/services/PhFlagsTest.java
+++ b/tests/servicetests/src/com/android/ondevicepersonalization/services/PhFlagsTest.java
@@ -19,6 +19,7 @@
 import static com.android.adservices.shared.common.flags.ModuleSharedFlags.BACKGROUND_JOB_SAMPLING_LOGGING_RATE;
 import static com.android.adservices.shared.common.flags.ModuleSharedFlags.DEFAULT_JOB_SCHEDULING_LOGGING_SAMPLING_RATE;
 import static com.android.ondevicepersonalization.services.Flags.APP_REQUEST_FLOW_DEADLINE_SECONDS;
+import static com.android.ondevicepersonalization.services.Flags.DEFAULT_ADSERVICES_IPC_CALL_TIMEOUT_IN_MILLIS;
 import static com.android.ondevicepersonalization.services.Flags.DEFAULT_AGGREGATED_ERROR_REPORTING_ENABLED;
 import static com.android.ondevicepersonalization.services.Flags.DEFAULT_AGGREGATED_ERROR_REPORTING_INTERVAL_HOURS;
 import static com.android.ondevicepersonalization.services.Flags.DEFAULT_AGGREGATED_ERROR_REPORTING_THRESHOLD;
@@ -43,6 +44,7 @@
 import static com.android.ondevicepersonalization.services.Flags.WEB_TRIGGER_FLOW_DEADLINE_SECONDS;
 import static com.android.ondevicepersonalization.services.Flags.WEB_VIEW_FLOW_DEADLINE_SECONDS;
 import static com.android.ondevicepersonalization.services.PhFlags.APP_INSTALL_HISTORY_TTL;
+import static com.android.ondevicepersonalization.services.PhFlags.KEY_ADSERVICES_IPC_CALL_TIMEOUT_IN_MILLIS;
 import static com.android.ondevicepersonalization.services.PhFlags.KEY_AGGREGATED_ERROR_REPORTING_INTERVAL_HOURS;
 import static com.android.ondevicepersonalization.services.PhFlags.KEY_AGGREGATED_ERROR_REPORTING_PATH;
 import static com.android.ondevicepersonalization.services.PhFlags.KEY_AGGREGATED_ERROR_REPORTING_THRESHOLD;
@@ -731,4 +733,26 @@
         assertThat(FlagsFactory.getFlags().getAggregatedErrorReportingIntervalInHours())
                 .isEqualTo(DEFAULT_AGGREGATED_ERROR_REPORTING_INTERVAL_HOURS);
     }
+
+    @Test
+    public void testGetAdservicesIpcCallTimeoutInMillis() {
+        long testTimeoutValue = 100L;
+
+        DeviceConfig.setProperty(
+                DeviceConfig.NAMESPACE_ON_DEVICE_PERSONALIZATION,
+                KEY_ADSERVICES_IPC_CALL_TIMEOUT_IN_MILLIS,
+                Long.toString(testTimeoutValue),
+                /* makeDefault */ false);
+
+        assertThat(FlagsFactory.getFlags().getAdservicesIpcCallTimeoutInMillis())
+                .isEqualTo(testTimeoutValue);
+
+        DeviceConfig.setProperty(
+                DeviceConfig.NAMESPACE_ON_DEVICE_PERSONALIZATION,
+                KEY_ADSERVICES_IPC_CALL_TIMEOUT_IN_MILLIS,
+                Long.toString(DEFAULT_ADSERVICES_IPC_CALL_TIMEOUT_IN_MILLIS),
+                /* makeDefault */ false);
+        assertThat(FlagsFactory.getFlags().getAdservicesIpcCallTimeoutInMillis())
+                .isEqualTo(DEFAULT_ADSERVICES_IPC_CALL_TIMEOUT_IN_MILLIS);
+    }
 }
diff --git a/tests/servicetests/src/com/android/ondevicepersonalization/services/data/user/UserPrivacyStatusTest.java b/tests/servicetests/src/com/android/ondevicepersonalization/services/data/user/UserPrivacyStatusTest.java
index abc91b7..8e8edeb 100644
--- a/tests/servicetests/src/com/android/ondevicepersonalization/services/data/user/UserPrivacyStatusTest.java
+++ b/tests/servicetests/src/com/android/ondevicepersonalization/services/data/user/UserPrivacyStatusTest.java
@@ -20,6 +20,7 @@
 import static android.adservices.ondevicepersonalization.Constants.STATUS_INTERNAL_ERROR;
 import static android.adservices.ondevicepersonalization.Constants.STATUS_METHOD_NOT_FOUND;
 import static android.adservices.ondevicepersonalization.Constants.STATUS_REMOTE_EXCEPTION;
+import static android.adservices.ondevicepersonalization.Constants.STATUS_TIMEOUT;
 import static android.app.job.JobScheduler.RESULT_SUCCESS;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -57,6 +58,9 @@
 import org.mockito.Spy;
 import org.mockito.quality.Strictness;
 
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+
 @RunWith(JUnit4.class)
 public final class UserPrivacyStatusTest {
     private UserPrivacyStatus mUserPrivacyStatus;
@@ -165,6 +169,9 @@
 
     @Test
     public void testGetStatusCode() {
+        assertThat(mUserPrivacyStatus.getExceptionStatus(
+                new ExecutionException("timeout testing", new TimeoutException())))
+                .isEqualTo(STATUS_TIMEOUT);
         assertThat(mUserPrivacyStatus.getExceptionStatus(new NoSuchMethodException()))
                 .isEqualTo(STATUS_METHOD_NOT_FOUND);
         assertThat(mUserPrivacyStatus.getExceptionStatus(new SecurityException()))