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) {