Enforce a deadline for calls to IsolatedService.

Enforced for request, rendering and event methods, not download.

Bug: 304383747
Test: TH
Change-Id: Ida11e931d5d654814af7acbbce54367098310358
diff --git a/src/com/android/ondevicepersonalization/services/Flags.java b/src/com/android/ondevicepersonalization/services/Flags.java
index d620b78..86615ac 100644
--- a/src/com/android/ondevicepersonalization/services/Flags.java
+++ b/src/com/android/ondevicepersonalization/services/Flags.java
@@ -50,6 +50,11 @@
      */
     boolean PERSONALIZATION_STATUS_OVERRIDE_VALUE = false;
 
+    /**
+     * Deadline for calls from ODP to isolated services.
+     */
+    int ISOLATED_SERVICE_DEADLINE_SECONDS = 30;
+
     default boolean getGlobalKillSwitch() {
         return GLOBAL_KILL_SWITCH;
     }
@@ -65,4 +70,8 @@
     default boolean getPersonalizationStatusOverrideValue() {
         return PERSONALIZATION_STATUS_OVERRIDE_VALUE;
     }
+
+    default int getIsolatedServiceDeadlineSeconds() {
+        return ISOLATED_SERVICE_DEADLINE_SECONDS;
+    }
 }
diff --git a/src/com/android/ondevicepersonalization/services/OnDevicePersonalizationExecutors.java b/src/com/android/ondevicepersonalization/services/OnDevicePersonalizationExecutors.java
index 837e5eb..3d03f78 100644
--- a/src/com/android/ondevicepersonalization/services/OnDevicePersonalizationExecutors.java
+++ b/src/com/android/ondevicepersonalization/services/OnDevicePersonalizationExecutors.java
@@ -24,6 +24,7 @@
 import android.os.StrictMode.ThreadPolicy;
 
 import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.ListeningScheduledExecutorService;
 import com.google.common.util.concurrent.MoreExecutors;
 import com.google.common.util.concurrent.ThreadFactoryBuilder;
 
@@ -52,6 +53,12 @@
                     createThreadFactory("Blocking Thread", Process.THREAD_PRIORITY_BACKGROUND
                             + Process.THREAD_PRIORITY_LESS_FAVORABLE, Optional.empty())));
 
+    private static final ListeningScheduledExecutorService sScheduledExecutor =
+            MoreExecutors.listeningDecorator(Executors.newScheduledThreadPool(
+                    /* nThreads */ 4,
+                    createThreadFactory("SCH Thread", Process.THREAD_PRIORITY_BACKGROUND,
+                            Optional.of(getIoThreadPolicy()))));
+
     private static final HandlerThread sHandlerThread = createHandlerThread();
 
     private static final Handler sHandler = new Handler(sHandlerThread.getLooper());
@@ -86,6 +93,14 @@
     }
 
     /**
+     * Returns an executor that can start tasks after a delay.
+     */
+    @NonNull
+    public static ListeningScheduledExecutorService getScheduledExecutor() {
+        return sScheduledExecutor;
+    }
+
+    /**
      * Returns a Handler that can post messages to a HandlerThread.
      */
     public static Handler getHandler() {
diff --git a/src/com/android/ondevicepersonalization/services/PhFlags.java b/src/com/android/ondevicepersonalization/services/PhFlags.java
index a682d8c..b253e04 100644
--- a/src/com/android/ondevicepersonalization/services/PhFlags.java
+++ b/src/com/android/ondevicepersonalization/services/PhFlags.java
@@ -29,13 +29,16 @@
     static final String KEY_GLOBAL_KILL_SWITCH = "global_kill_switch";
 
     static final String KEY_ENABLE_ONDEVICEPERSONALIZATION_APIS =
-                    "enable_ondevicepersonalization_apis";
+            "enable_ondevicepersonalization_apis";
 
     static final String KEY_ENABLE_PERSONALIZATION_STATUS_OVERRIDE =
-                    "enable_personalization_status_override";
+            "enable_personalization_status_override";
 
     static final String KEY_PERSONALIZATION_STATUS_OVERRIDE_VALUE =
-                    "personalization_status_override_value";
+            "personalization_status_override_value";
+
+    static final String KEY_ISOLATED_SERVICE_DEADLINE_SECONDS =
+            "isolated_service_deadline_seconds";
 
     // OnDevicePersonalization Namespace String from DeviceConfig class
     static final String NAMESPACE_ON_DEVICE_PERSONALIZATION = "on_device_personalization";
@@ -92,4 +95,13 @@
                 /* name= */ KEY_PERSONALIZATION_STATUS_OVERRIDE_VALUE,
                 /* defaultValue= */ PERSONALIZATION_STATUS_OVERRIDE_VALUE);
     }
+
+    @Override
+    public int getIsolatedServiceDeadlineSeconds() {
+        // The priority of applying the flag values: PH (DeviceConfig), then user hard-coded value.
+        return DeviceConfig.getInt(
+                /* namespace= */ NAMESPACE_ON_DEVICE_PERSONALIZATION,
+                /* name= */ KEY_ISOLATED_SERVICE_DEADLINE_SECONDS,
+                /* defaultValue= */ ISOLATED_SERVICE_DEADLINE_SECONDS);
+    }
 }
diff --git a/src/com/android/ondevicepersonalization/services/display/OdpWebViewClient.java b/src/com/android/ondevicepersonalization/services/display/OdpWebViewClient.java
index df146ac..5b83c6c 100644
--- a/src/com/android/ondevicepersonalization/services/display/OdpWebViewClient.java
+++ b/src/com/android/ondevicepersonalization/services/display/OdpWebViewClient.java
@@ -34,6 +34,8 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.ondevicepersonalization.internal.util.LoggerFactory;
+import com.android.ondevicepersonalization.services.Flags;
+import com.android.ondevicepersonalization.services.FlagsFactory;
 import com.android.ondevicepersonalization.services.OnDevicePersonalizationExecutors;
 import com.android.ondevicepersonalization.services.data.DataAccessServiceImpl;
 import com.android.ondevicepersonalization.services.data.events.Event;
@@ -54,12 +56,14 @@
 import com.google.common.util.concurrent.FluentFuture;
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.ListeningScheduledExecutorService;
 
 import java.io.ByteArrayInputStream;
 import java.io.InputStream;
 import java.net.HttpURLConnection;
 import java.util.Collections;
-import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
 
 class OdpWebViewClient extends WebViewClient {
     private static final LoggerFactory.Logger sLogger = LoggerFactory.getLogger();
@@ -68,7 +72,7 @@
 
     @VisibleForTesting
     static class Injector {
-        Executor getExecutor() {
+        ListeningExecutorService getExecutor() {
             return OnDevicePersonalizationExecutors.getBackgroundExecutor();
         }
 
@@ -84,6 +88,14 @@
         Clock getClock() {
             return MonotonicClock.getInstance();
         }
+
+        Flags getFlags() {
+            return FlagsFactory.getFlags();
+        }
+
+        ListeningScheduledExecutorService getScheduledExecutor() {
+            return OnDevicePersonalizationExecutors.getScheduledExecutor();
+        }
     }
 
     @NonNull private final Context mContext;
@@ -275,7 +287,12 @@
             var unused = FluentFuture.from(getEventOutput(eventUrlPayload))
                     .transformAsync(
                         result -> writeEvent(result),
-                        mInjector.getExecutor());
+                        mInjector.getExecutor())
+                    .withTimeout(
+                        mInjector.getFlags().getIsolatedServiceDeadlineSeconds(),
+                        TimeUnit.SECONDS,
+                        mInjector.getScheduledExecutor()
+                    );
 
         } catch (Exception e) {
             sLogger.e(TAG + ": Failed to handle Event", e);
diff --git a/src/com/android/ondevicepersonalization/services/federatedcompute/OdpExampleStoreService.java b/src/com/android/ondevicepersonalization/services/federatedcompute/OdpExampleStoreService.java
index c230491..9e236e7 100644
--- a/src/com/android/ondevicepersonalization/services/federatedcompute/OdpExampleStoreService.java
+++ b/src/com/android/ondevicepersonalization/services/federatedcompute/OdpExampleStoreService.java
@@ -30,6 +30,8 @@
 
 import com.android.ondevicepersonalization.internal.util.ByteArrayParceledListSlice;
 import com.android.ondevicepersonalization.internal.util.LoggerFactory;
+import com.android.ondevicepersonalization.services.Flags;
+import com.android.ondevicepersonalization.services.FlagsFactory;
 import com.android.ondevicepersonalization.services.OnDevicePersonalizationExecutors;
 import com.android.ondevicepersonalization.services.data.DataAccessServiceImpl;
 import com.android.ondevicepersonalization.services.data.events.EventState;
@@ -48,9 +50,11 @@
 import com.google.common.util.concurrent.FutureCallback;
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningScheduledExecutorService;
 
 import java.util.ArrayList;
 import java.util.Objects;
+import java.util.concurrent.TimeUnit;
 
 /** Implementation of ExampleStoreService for OnDevicePersonalization */
 public final class OdpExampleStoreService extends ExampleStoreService {
@@ -63,6 +67,14 @@
         Clock getClock() {
             return MonotonicClock.getInstance();
         }
+
+        Flags getFlags() {
+            return FlagsFactory.getFlags();
+        }
+
+        ListeningScheduledExecutorService getScheduledExecutor() {
+            return OnDevicePersonalizationExecutors.getScheduledExecutor();
+        }
     }
 
     private final Injector mInjector = new Injector();
@@ -156,7 +168,11 @@
                                                 Constants.EXTRA_RESULT,
                                                 TrainingExampleOutputParcel.class);
                                     },
-                                    OnDevicePersonalizationExecutors.getBackgroundExecutor());
+                                    OnDevicePersonalizationExecutors.getBackgroundExecutor())
+                            .withTimeout(
+                                    mInjector.getFlags().getIsolatedServiceDeadlineSeconds(),
+                                    TimeUnit.SECONDS,
+                                    mInjector.getScheduledExecutor());
 
             Futures.addCallback(
                     resultFuture,
diff --git a/src/com/android/ondevicepersonalization/services/request/AppRequestFlow.java b/src/com/android/ondevicepersonalization/services/request/AppRequestFlow.java
index a27fde1..43d4b1d 100644
--- a/src/com/android/ondevicepersonalization/services/request/AppRequestFlow.java
+++ b/src/com/android/ondevicepersonalization/services/request/AppRequestFlow.java
@@ -34,6 +34,8 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.ondevicepersonalization.internal.util.LoggerFactory;
+import com.android.ondevicepersonalization.services.Flags;
+import com.android.ondevicepersonalization.services.FlagsFactory;
 import com.android.ondevicepersonalization.services.OnDevicePersonalizationExecutors;
 import com.android.ondevicepersonalization.services.data.DataAccessServiceImpl;
 import com.android.ondevicepersonalization.services.data.events.Event;
@@ -59,10 +61,12 @@
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
 import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.ListeningScheduledExecutorService;
 
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
+import java.util.concurrent.TimeUnit;
 
 /**
  * Handles a surface package request from an app or SDK.
@@ -94,6 +98,14 @@
         Clock getClock() {
             return MonotonicClock.getInstance();
         }
+
+        Flags getFlags() {
+            return FlagsFactory.getFlags();
+        }
+
+        ListeningScheduledExecutorService getScheduledExecutor() {
+            return OnDevicePersonalizationExecutors.getScheduledExecutor();
+        }
     }
 
     @NonNull
@@ -173,13 +185,19 @@
                     .transformAsync(input -> logQuery(input), mInjector.getExecutor());
 
             ListenableFuture<List<String>> slotResultTokensFuture =
-                    Futures.whenAllSucceed(resultFuture, queryIdFuture)
-                            .callAsync(new AsyncCallable<List<String>>() {
-                                @Override
-                                public ListenableFuture<List<String>> call() {
-                                    return createTokens(resultFuture, queryIdFuture);
-                                }
-                            }, mInjector.getExecutor());
+                    FluentFuture.from(
+                            Futures.whenAllSucceed(resultFuture, queryIdFuture)
+                                .callAsync(new AsyncCallable<List<String>>() {
+                                    @Override
+                                    public ListenableFuture<List<String>> call() {
+                                        return createTokens(resultFuture, queryIdFuture);
+                                    }
+                                }, mInjector.getExecutor()))
+                            .withTimeout(
+                                mInjector.getFlags().getIsolatedServiceDeadlineSeconds(),
+                                TimeUnit.SECONDS,
+                                mInjector.getScheduledExecutor()
+                            );
 
             Futures.addCallback(
                     slotResultTokensFuture,
diff --git a/src/com/android/ondevicepersonalization/services/request/RenderFlow.java b/src/com/android/ondevicepersonalization/services/request/RenderFlow.java
index 43656f4..a4d788c 100644
--- a/src/com/android/ondevicepersonalization/services/request/RenderFlow.java
+++ b/src/com/android/ondevicepersonalization/services/request/RenderFlow.java
@@ -31,6 +31,8 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.ondevicepersonalization.internal.util.LoggerFactory;
+import com.android.ondevicepersonalization.services.Flags;
+import com.android.ondevicepersonalization.services.FlagsFactory;
 import com.android.ondevicepersonalization.services.OnDevicePersonalizationExecutors;
 import com.android.ondevicepersonalization.services.data.DataAccessServiceImpl;
 import com.android.ondevicepersonalization.services.display.DisplayHelper;
@@ -49,8 +51,10 @@
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
 import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.ListeningScheduledExecutorService;
 
 import java.util.Objects;
+import java.util.concurrent.TimeUnit;
 
 /**
  * Handles a surface package request from an app or SDK.
@@ -73,6 +77,14 @@
         Clock getClock() {
             return MonotonicClock.getInstance();
         }
+
+        Flags getFlags() {
+            return FlagsFactory.getFlags();
+        }
+
+        ListeningScheduledExecutorService getScheduledExecutor() {
+            return OnDevicePersonalizationExecutors.getScheduledExecutor();
+        }
     }
 
     @NonNull
@@ -152,7 +164,12 @@
                         mContext, mServicePackageName));
 
             ListenableFuture<SurfacePackage> surfacePackageFuture =
-                    renderContentForSlot(slotWrapper);
+                    FluentFuture.from(renderContentForSlot(slotWrapper))
+                    .withTimeout(
+                            mInjector.getFlags().getIsolatedServiceDeadlineSeconds(),
+                            TimeUnit.SECONDS,
+                            mInjector.getScheduledExecutor()
+                    );
 
             Futures.addCallback(
                     surfacePackageFuture,
diff --git a/tests/servicetests/src/com/android/ondevicepersonalization/services/display/OdpWebViewClientTests.java b/tests/servicetests/src/com/android/ondevicepersonalization/services/display/OdpWebViewClientTests.java
index c8c1b0c..0834c93 100644
--- a/tests/servicetests/src/com/android/ondevicepersonalization/services/display/OdpWebViewClientTests.java
+++ b/tests/servicetests/src/com/android/ondevicepersonalization/services/display/OdpWebViewClientTests.java
@@ -48,6 +48,7 @@
 import com.android.ondevicepersonalization.services.data.events.Query;
 import com.android.ondevicepersonalization.services.fbs.EventFields;
 
+import com.google.common.util.concurrent.ListeningExecutorService;
 import com.google.common.util.concurrent.MoreExecutors;
 
 import org.junit.After;
@@ -63,7 +64,6 @@
 import java.nio.charset.StandardCharsets;
 import java.util.Map;
 import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.Executor;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 @RunWith(JUnit4.class)
@@ -223,8 +223,8 @@
     }
 
     class TestInjector extends OdpWebViewClient.Injector {
-        Executor getExecutor() {
-            return MoreExecutors.directExecutor();
+        ListeningExecutorService getExecutor() {
+            return MoreExecutors.newDirectExecutorService();
         }
 
         void openUrl(String url, Context context) {