Add missing ABSL includes. am: 0c5056789e
Original change: https://android-review.googlesource.com/c/platform/packages/modules/OnDevicePersonalization/+/3348651
Change-Id: I0c466eaaf6c9ef4f0b2097c9fad7c9b0bf54686a
Signed-off-by: Automerger Merge Worker <[email protected]>
diff --git a/Android.bp b/Android.bp
index 1914698..7e661a5 100644
--- a/Android.bp
+++ b/Android.bp
@@ -110,6 +110,7 @@
],
static_libs: [
"androidx.concurrent_concurrent-futures",
+ "federated-compute-java-proto-lite",
"guava",
"kotlin-stdlib",
"kotlinx_coroutines",
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 5c57e47..77027b6 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -27,7 +27,7 @@
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
- <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
+ <uses-permission android:name="android.permission.READ_BASIC_PHONE_STATE"/>
<!-- Required for the app to find all packages onboarded to ODP -->
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
@@ -62,11 +62,6 @@
<action android:name="android.OnDevicePersonalizationService" />
</intent-filter>
</service>
- <service android:name=".OnDevicePersonalizationConfigServiceImpl" android:exported="true">
- <intent-filter>
- <action android:name="android.OnDevicePersonalizationConfigService"/>
- </intent-filter>
- </service>
<service android:name=".OnDevicePersonalizationDebugServiceImpl" android:exported="true">
<intent-filter>
<action android:name="android.OnDevicePersonalizationDebugService"/>
@@ -105,6 +100,11 @@
android:exported="false"
android:permission="android.permission.BIND_JOB_SERVICE">
</service>
+ <service
+ android:name="com.android.ondevicepersonalization.services.data.errors.AggregateErrorDataReportingService"
+ android:exported="false"
+ android:permission="android.permission.BIND_JOB_SERVICE">
+ </service>
<service android:name="com.android.ondevicepersonalization.libraries.plugin.internal.PluginExecutorService"
android:isolatedProcess="true"
android:process=":plugin_disable_art_image_"
diff --git a/OWNERS b/OWNERS
index 58aac2c..1e6d84a 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,15 +1,16 @@
# Bug component: 1117807
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
diff --git a/TEST_MAPPING b/TEST_MAPPING
index aeab832..d91e2fc 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -80,6 +80,9 @@
},
{
"name": "CtsOnDevicePersonalizationE2ETests"
+ },
+ {
+ "name": "CtsOnDevicePersonalizationConfigTests"
}
]
}
diff --git a/federatedcompute/src/com/android/federatedcompute/services/common/FileUtils.java b/common/java/com/android/odp/module/common/FileUtils.java
similarity index 87%
rename from federatedcompute/src/com/android/federatedcompute/services/common/FileUtils.java
rename to common/java/com/android/odp/module/common/FileUtils.java
index 7549029..9e9a810 100644
--- a/federatedcompute/src/com/android/federatedcompute/services/common/FileUtils.java
+++ b/common/java/com/android/odp/module/common/FileUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,19 +14,21 @@
* limitations under the License.
*/
-package com.android.federatedcompute.services.common;
+package com.android.odp.module.common;
import android.os.ParcelFileDescriptor;
import com.android.federatedcompute.internal.util.LogUtil;
import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.io.OutputStream;
/** Utils related to {@link File} and {@link ParcelFileDescriptor}. */
public class FileUtils {
@@ -60,11 +62,18 @@
/** Write the provided data to the file. */
public static void writeToFile(String fileName, byte[] data) throws IOException {
- FileOutputStream out = new FileOutputStream(fileName);
+ OutputStream out = new BufferedOutputStream(new FileOutputStream(fileName));
out.write(data);
out.close();
}
+ /** Write the provided data to the file. */
+ public static long writeToFile(String fileName, InputStream inputStream) throws IOException {
+ try (OutputStream out = new BufferedOutputStream(new FileOutputStream(fileName))) {
+ return inputStream.transferTo(out);
+ }
+ }
+
/** Read the input file content to a byte array. */
public static byte[] readFileAsByteArray(String filePath) throws IOException {
File file = new File(filePath);
diff --git a/common/java/com/android/odp/module/common/HttpClient.java b/common/java/com/android/odp/module/common/HttpClient.java
new file mode 100644
index 0000000..aae4f8a
--- /dev/null
+++ b/common/java/com/android/odp/module/common/HttpClient.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.odp.module.common;
+
+import static com.android.odp.module.common.HttpClientUtils.HTTP_OK_STATUS;
+
+import android.annotation.NonNull;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+
+import java.io.IOException;
+import java.util.concurrent.Callable;
+
+/**
+ * The HTTP client to be used by FederatedCompute and ODP services/jobs to communicate with remote
+ * servers.
+ */
+public class HttpClient {
+
+ interface HttpIOSupplier<T> {
+ T get() throws IOException; // Declared to throw IOException
+ }
+
+ private final int mRetryLimit;
+
+ /** The executor to use for making http requests. */
+ private final ListeningExecutorService mBlockingExecutor;
+
+ public HttpClient(int retryLimit, ListeningExecutorService blockingExecutor) {
+ mRetryLimit = retryLimit;
+ mBlockingExecutor = blockingExecutor;
+ }
+
+ /**
+ * Perform HTTP requests based on given {@link OdpHttpRequest} asynchronously with configured
+ * number of retries.
+ *
+ * <p>Retry limit provided during construction is used in case http does not return {@code OK}
+ * response code.
+ */
+ @NonNull
+ public ListenableFuture<OdpHttpResponse> performRequestAsyncWithRetry(OdpHttpRequest request) {
+ return performCallableAsync(
+ () -> performRequestWithRetry(() -> HttpClientUtils.performRequest(request)));
+ }
+
+ /**
+ * Perform HTTP requests based on given information asynchronously with retries in case http
+ * will return not OK response code. Payload will be saved directly into the file.
+ */
+ @NonNull
+ public ListenableFuture<OdpHttpResponse> performRequestIntoFileAsyncWithRetry(
+ OdpHttpRequest request) {
+ return performCallableAsync(
+ () -> performRequestWithRetry(() -> HttpClientUtils.performRequest(request, true)));
+ }
+
+ /**
+ * Perform HTTP requests based on given information asynchronously with retries in case http
+ * will return not OK response code.
+ */
+ @NonNull
+ private ListenableFuture<OdpHttpResponse> performCallableAsync(
+ Callable<OdpHttpResponse> callable) {
+ try {
+ return mBlockingExecutor.submit(callable);
+ } catch (Exception e) {
+ return Futures.immediateFailedFuture(e);
+ }
+ }
+
+ /** Perform HTTP requests based on given information with retries. */
+ @NonNull
+ @VisibleForTesting
+ OdpHttpResponse performRequestWithRetry(HttpIOSupplier<OdpHttpResponse> supplier)
+ throws IOException {
+ OdpHttpResponse response = null;
+ int retryLimit = mRetryLimit;
+ while (retryLimit > 0) {
+ try {
+ response = supplier.get();
+ if (HTTP_OK_STATUS.contains(response.getStatusCode())) {
+ return response;
+ }
+ // we want to continue retry in case it is IO exception.
+ } catch (IOException e) {
+ // propagate IO exception after RETRY_LIMIT times attempt.
+ if (retryLimit <= 1) {
+ throw e;
+ }
+ } finally {
+ retryLimit--;
+ }
+ }
+ return response;
+ }
+}
diff --git a/common/java/com/android/odp/module/common/HttpClientUtils.java b/common/java/com/android/odp/module/common/HttpClientUtils.java
new file mode 100644
index 0000000..f6b5138
--- /dev/null
+++ b/common/java/com/android/odp/module/common/HttpClientUtils.java
@@ -0,0 +1,334 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.odp.module.common;
+
+import static com.android.odp.module.common.FileUtils.createTempFile;
+import static com.android.odp.module.common.FileUtils.writeToFile;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.federatedcompute.internal.util.LogUtil;
+import com.android.internal.annotations.VisibleForTesting;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.internal.federatedcompute.v1.ResourceCapabilities;
+import com.google.internal.federatedcompute.v1.ResourceCompressionFormat;
+import com.google.protobuf.ByteString;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.GZIPOutputStream;
+
+/** Shared utilities for http connections used by fcp and odp server requests. */
+public class HttpClientUtils {
+ private static final String TAG = HttpClientUtils.class.getSimpleName();
+
+ @VisibleForTesting
+ static final int NETWORK_CONNECT_TIMEOUT_MS = (int) TimeUnit.SECONDS.toMillis(5);
+
+ @VisibleForTesting
+ static final int NETWORK_READ_TIMEOUT_MS = (int) TimeUnit.SECONDS.toMillis(30);
+
+ public static final String CONTENT_ENCODING_HDR = "Content-Encoding";
+
+ public static final String ACCEPT_ENCODING_HDR = "Accept-Encoding";
+ public static final String CONTENT_LENGTH_HDR = "Content-Length";
+ public static final String GZIP_ENCODING_HDR = "gzip";
+ public static final String CONTENT_TYPE_HDR = "Content-Type";
+ public static final String PROTOBUF_CONTENT_TYPE = "application/x-protobuf";
+ public static final String OCTET_STREAM = "application/octet-stream";
+ public static final ImmutableSet<Integer> HTTP_OK_STATUS = ImmutableSet.of(200, 201);
+
+
+ public static final int DEFAULT_BUFFER_SIZE = 1024;
+ public static final byte[] EMPTY_BODY = new byte[0];
+
+ /** Returns the full URI based on the provided base URL and suffix. */
+ public static String joinBaseUriWithSuffix(String baseUri, String suffix) {
+ if (suffix.isEmpty() || !suffix.startsWith("/")) {
+ throw new IllegalArgumentException("uri_suffix be empty or must have a leading '/'");
+ }
+
+ if (baseUri.endsWith("/")) {
+ baseUri = baseUri.substring(0, baseUri.length() - 1);
+ }
+ suffix = suffix.substring(1);
+ return String.join("/", baseUri, suffix);
+ }
+
+ interface HttpURLConnectionSupplier {
+ HttpURLConnection get() throws IOException; // Declared to throw IOException
+ }
+
+ /** Get the current client capabilities. */
+ public static ResourceCapabilities getResourceCapabilities() {
+ // Compression formats supported for resources downloaded via `Resource.uri`.
+ // All clients are assumed to support uncompressed payloads.
+ return ResourceCapabilities.newBuilder()
+ .addSupportedCompressionFormats(
+ ResourceCompressionFormat.RESOURCE_COMPRESSION_FORMAT_GZIP)
+ .build();
+ }
+
+ /** Compresses the input data using Gzip. */
+ public static byte[] compressWithGzip(byte[] uncompressedData) {
+ try (ByteString.Output outputStream = ByteString.newOutput(uncompressedData.length);
+ GZIPOutputStream gzipOutputStream = new GZIPOutputStream(outputStream)) {
+ gzipOutputStream.write(uncompressedData);
+ gzipOutputStream.finish();
+ return outputStream.toByteString().toByteArray();
+ } catch (IOException e) {
+ LogUtil.e(TAG, "Failed to compress using Gzip");
+ throw new IllegalStateException("Failed to compress using Gzip", e);
+ }
+ }
+
+ /** Un-compresses the input data using Gzip. */
+ public static byte[] uncompressWithGzip(byte[] data) {
+ try (ByteArrayInputStream inputStream = new ByteArrayInputStream(data);
+ GZIPInputStream gzip = new GZIPInputStream(inputStream);
+ ByteArrayOutputStream result = new ByteArrayOutputStream()) {
+ int length;
+ byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
+ while ((length = gzip.read(buffer, 0, DEFAULT_BUFFER_SIZE)) > 0) {
+ result.write(buffer, 0, length);
+ }
+ return result.toByteArray();
+ } catch (Exception e) {
+ LogUtil.e(TAG, e, "Failed to decompress the data.");
+ throw new IllegalStateException("Failed to un-compress using Gzip", e);
+ }
+ }
+
+ /** Calculates total bytes are sent via network based on provided http request. */
+ public static long getTotalSentBytes(OdpHttpRequest request) {
+ long totalBytes = 0;
+ totalBytes +=
+ request.getHttpMethod().name().length()
+ + " ".length()
+ + request.getUri().length()
+ + " HTTP/1.1\r\n".length();
+ for (String key : request.getExtraHeaders().keySet()) {
+ totalBytes +=
+ key.length()
+ + ": ".length()
+ + request.getExtraHeaders().get(key).length()
+ + "\r\n".length();
+ }
+ if (request.getExtraHeaders().containsKey(CONTENT_LENGTH_HDR)) {
+ totalBytes += Long.parseLong(request.getExtraHeaders().get(CONTENT_LENGTH_HDR));
+ }
+ return totalBytes;
+ }
+
+ /** Calculates total bytes are received via network based on provided http response. */
+ public static long getTotalReceivedBytes(OdpHttpResponse response) {
+ long totalBytes = 0;
+ boolean foundContentLengthHdr = false;
+ for (Map.Entry<String, List<String>> header : response.getHeaders().entrySet()) {
+ if (header.getKey() == null) {
+ continue;
+ }
+ for (String headerValue : header.getValue()) {
+ totalBytes += header.getKey().length() + ": ".length();
+ totalBytes += headerValue == null ? 0 : headerValue.length();
+ }
+ // Uses Content-Length header to estimate total received bytes which is the most
+ // accurate.
+ if (header.getKey().equals(CONTENT_LENGTH_HDR)) {
+ totalBytes += Long.parseLong(header.getValue().get(0));
+ foundContentLengthHdr = true;
+ }
+ }
+ if (!foundContentLengthHdr) {
+ if (response.getPayload() != null) {
+ totalBytes += response.getPayload().length;
+ } else if (response.getPayloadFileName() != null) {
+ totalBytes += response.getDownloadedPayloadSize();
+ }
+ }
+ return totalBytes;
+ }
+
+ /** Opens a {@link URLConnection} to the specified URL with default timeouts. */
+ @VisibleForTesting
+ @NonNull
+ static URLConnection setup(@NonNull URL url) throws IOException {
+ Objects.requireNonNull(url);
+ URLConnection urlConnection = url.openConnection();
+ urlConnection.setConnectTimeout(NETWORK_CONNECT_TIMEOUT_MS);
+ urlConnection.setReadTimeout(NETWORK_READ_TIMEOUT_MS);
+ return urlConnection;
+ }
+
+ /** Perform HTTP requests based on given information and returns the {@link OdpHttpResponse}. */
+ @NonNull
+ public static OdpHttpResponse performRequest(OdpHttpRequest request) throws IOException {
+ return performRequest(request, /* savePayloadIntoFile= */ false);
+ }
+
+ /** Perform HTTP requests based on given information and returns the {@link OdpHttpResponse}. */
+ @NonNull
+ public static OdpHttpResponse performRequest(
+ OdpHttpRequest request, boolean savePayloadIntoFile) throws IOException {
+ if (request.getUri() == null || request.getHttpMethod() == null) {
+ LogUtil.e(TAG, "Endpoint or http method is empty");
+ throw new IllegalArgumentException("Endpoint or http method is empty");
+ }
+
+ URL url;
+ try {
+ url = new URL(request.getUri());
+ } catch (MalformedURLException e) {
+ LogUtil.e(TAG, e, "Malformed registration target URL");
+ throw new IllegalArgumentException("Malformed registration target URL", e);
+ }
+
+ return performRequest(request, () -> (HttpURLConnection) setup(url), savePayloadIntoFile);
+ }
+
+ @NonNull
+ @VisibleForTesting
+ static OdpHttpResponse performRequest(
+ OdpHttpRequest request,
+ HttpURLConnectionSupplier urlConnectionProvider,
+ boolean savePayloadIntoFile)
+ throws IOException {
+ HttpURLConnection urlConnection;
+ try {
+ urlConnection = urlConnectionProvider.get();
+ } catch (Exception e) {
+ LogUtil.e(TAG, e, "Failed to open target URL");
+ throw new IOException("Failed to open target URL", e);
+ }
+
+ try {
+ urlConnection.setRequestMethod(request.getHttpMethod().name());
+ urlConnection.setInstanceFollowRedirects(true);
+
+ if (request.getExtraHeaders() != null && !request.getExtraHeaders().isEmpty()) {
+ for (Map.Entry<String, String> entry : request.getExtraHeaders().entrySet()) {
+ urlConnection.setRequestProperty(entry.getKey(), entry.getValue());
+ }
+ }
+
+ if (request.getBody() != null && request.getBody().length > 0) {
+ urlConnection.setDoOutput(true);
+ try (BufferedOutputStream out =
+ new BufferedOutputStream(urlConnection.getOutputStream())) {
+ out.write(request.getBody());
+ }
+ }
+
+ int responseCode = urlConnection.getResponseCode();
+ if (HTTP_OK_STATUS.contains(responseCode)) {
+ OdpHttpResponse.Builder builder =
+ new OdpHttpResponse.Builder()
+ .setHeaders(urlConnection.getHeaderFields())
+ .setStatusCode(responseCode);
+ if (savePayloadIntoFile) {
+ String inputFile = createTempFile("input", ".tmp");
+ long downloadedSize =
+ saveIntoFile(
+ inputFile,
+ urlConnection.getInputStream(),
+ urlConnection.getContentLengthLong());
+ if (downloadedSize != 0) {
+ builder.setPayloadFileName(inputFile);
+ builder.setDownloadedPayloadSize(downloadedSize);
+ }
+ } else {
+ builder.setPayload(
+ getByteArray(
+ urlConnection.getInputStream(),
+ urlConnection.getContentLengthLong()));
+ }
+ return builder.build();
+ } else {
+ return new OdpHttpResponse.Builder()
+ .setPayload(
+ getByteArray(
+ urlConnection.getErrorStream(),
+ urlConnection.getContentLengthLong()))
+ .setHeaders(urlConnection.getHeaderFields())
+ .setStatusCode(responseCode)
+ .build();
+ }
+ } catch (IOException e) {
+ LogUtil.e(TAG, e, "Failed to get registration response");
+ throw new IOException("Failed to get registration response", e);
+ } finally {
+ if (urlConnection != null) {
+ urlConnection.disconnect();
+ }
+ }
+ }
+
+ private static long saveIntoFile(String fileName, @Nullable InputStream in, long contentLength)
+ throws IOException {
+ if (contentLength == 0) {
+ return 0;
+ }
+ try (InputStream bufIn = new BufferedInputStream(in)) {
+ // Process download resource.
+ long downloadedSize = writeToFile(fileName, bufIn);
+ return downloadedSize;
+ }
+ }
+
+ private static byte[] getByteArray(@Nullable InputStream in, long contentLength)
+ throws IOException {
+ if (contentLength == 0) {
+ return EMPTY_BODY;
+ }
+ try {
+ // TODO(b/297952090): evaluate the large file download.
+ byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ int bytesRead;
+ while ((bytesRead = in.read(buffer)) != -1) {
+ out.write(buffer, 0, bytesRead);
+ }
+ return out.toByteArray();
+ } finally {
+ in.close();
+ }
+ }
+
+ private HttpClientUtils() {}
+
+ /** The supported http methods. */
+ public enum HttpMethod {
+ GET,
+ POST,
+ PUT,
+ }
+}
diff --git a/federatedcompute/src/com/android/federatedcompute/services/http/FederatedComputeHttpRequest.java b/common/java/com/android/odp/module/common/OdpHttpRequest.java
similarity index 72%
rename from federatedcompute/src/com/android/federatedcompute/services/http/FederatedComputeHttpRequest.java
rename to common/java/com/android/odp/module/common/OdpHttpRequest.java
index d83cc69..894ae8c 100644
--- a/federatedcompute/src/com/android/federatedcompute/services/http/FederatedComputeHttpRequest.java
+++ b/common/java/com/android/odp/module/common/OdpHttpRequest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,26 +14,26 @@
* limitations under the License.
*/
-package com.android.federatedcompute.services.http;
+package com.android.odp.module.common;
-import static com.android.federatedcompute.services.http.HttpClientUtil.CONTENT_LENGTH_HDR;
+import static com.android.odp.module.common.HttpClientUtils.CONTENT_LENGTH_HDR;
-import com.android.federatedcompute.services.http.HttpClientUtil.HttpMethod;
+import com.android.odp.module.common.HttpClientUtils.HttpMethod;
import java.util.Map;
-/** Class to hold FederatedCompute http request. */
-public final class FederatedComputeHttpRequest {
+/** Class to hold http requests for federated compute and other odp use-cases. */
+public final class OdpHttpRequest {
private static final String TAG = "FCPHttpRequest";
private static final String HTTPS_SCHEMA = "https://";
private static final String LOCAL_HOST_URI = "http://localhost:";
- private String mUri;
- private HttpMethod mHttpMethod;
- private Map<String, String> mExtraHeaders;
- private byte[] mBody;
+ private final String mUri;
+ private final HttpMethod mHttpMethod;
+ private final Map<String, String> mExtraHeaders;
+ private final byte[] mBody;
- private FederatedComputeHttpRequest(
+ private OdpHttpRequest(
String uri, HttpMethod httpMethod, Map<String, String> extraHeaders, byte[] body) {
this.mUri = uri;
this.mHttpMethod = httpMethod;
@@ -41,8 +41,8 @@
this.mBody = body;
}
- /** Creates a {@link FederatedComputeHttpRequest} based on given inputs. */
- public static FederatedComputeHttpRequest create(
+ /** Creates a {@link OdpHttpRequest} based on given inputs. */
+ public static OdpHttpRequest create(
String uri, HttpMethod httpMethod, Map<String, String> extraHeaders, byte[] body) {
if (!uri.startsWith(HTTPS_SCHEMA) && !uri.startsWith(LOCAL_HOST_URI)) {
throw new IllegalArgumentException("Non-HTTPS URIs are not supported: " + uri);
@@ -57,7 +57,7 @@
}
extraHeaders.put(CONTENT_LENGTH_HDR, String.valueOf(body.length));
}
- return new FederatedComputeHttpRequest(uri, httpMethod, extraHeaders, body);
+ return new OdpHttpRequest(uri, httpMethod, extraHeaders, body);
}
public String getUri() {
diff --git a/federatedcompute/src/com/android/federatedcompute/services/http/FederatedComputeHttpResponse.java b/common/java/com/android/odp/module/common/OdpHttpResponse.java
similarity index 63%
rename from federatedcompute/src/com/android/federatedcompute/services/http/FederatedComputeHttpResponse.java
rename to common/java/com/android/odp/module/common/OdpHttpResponse.java
index 48338f2..494e33c 100644
--- a/federatedcompute/src/com/android/federatedcompute/services/http/FederatedComputeHttpResponse.java
+++ b/common/java/com/android/odp/module/common/OdpHttpResponse.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-package com.android.federatedcompute.services.http;
+package com.android.odp.module.common;
-import static com.android.federatedcompute.services.http.HttpClientUtil.CONTENT_ENCODING_HDR;
-import static com.android.federatedcompute.services.http.HttpClientUtil.GZIP_ENCODING_HDR;
+import static com.android.odp.module.common.HttpClientUtils.CONTENT_ENCODING_HDR;
+import static com.android.odp.module.common.HttpClientUtils.GZIP_ENCODING_HDR;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -26,15 +26,15 @@
import java.util.List;
import java.util.Map;
-/** Class to hold FederatedCompute http response. */
-public class FederatedComputeHttpResponse {
+public class OdpHttpResponse {
private Integer mStatusCode;
private Map<String, List<String>> mHeaders = new HashMap<>();
private byte[] mPayload;
+ private String mPayloadFileName;
+ private long mDownloadedPayloadSize;
- private FederatedComputeHttpResponse() {}
+ private OdpHttpResponse() {}
- @NonNull
public int getStatusCode() {
return mStatusCode;
}
@@ -49,6 +49,15 @@
return mPayload;
}
+ @Nullable
+ public String getPayloadFileName() {
+ return mPayloadFileName;
+ }
+
+ public long getDownloadedPayloadSize() {
+ return mDownloadedPayloadSize;
+ }
+
/** Returns whether http response body is compressed with gzip. */
public boolean isResponseCompressed() {
if (mHeaders.containsKey(CONTENT_ENCODING_HDR)) {
@@ -61,13 +70,13 @@
return false;
}
- /** Builder for FederatedComputeHttpResponse. */
+ /** Builder for {@link OdpHttpResponse}. */
public static final class Builder {
- private final FederatedComputeHttpResponse mHttpResponse;
+ private final OdpHttpResponse mHttpResponse;
- /** Default constructor of {@link FederatedComputeHttpResponse}. */
+ /** Default constructor of {@link OdpHttpResponse}. */
public Builder() {
- mHttpResponse = new FederatedComputeHttpResponse();
+ mHttpResponse = new OdpHttpResponse();
}
/** Set the status code of http response. */
@@ -88,8 +97,20 @@
return this;
}
- /** Build {@link FederatedComputeHttpResponse}. */
- public FederatedComputeHttpResponse build() {
+ /** Set payload file name where payload is saved. */
+ public Builder setPayloadFileName(String fileName) {
+ mHttpResponse.mPayloadFileName = fileName;
+ return this;
+ }
+
+ /** Set payload file name where payload is saved. */
+ public Builder setDownloadedPayloadSize(long downloadedSize) {
+ mHttpResponse.mDownloadedPayloadSize = downloadedSize;
+ return this;
+ }
+
+ /** Build {@link OdpHttpResponse}. */
+ public OdpHttpResponse build() {
if (mHttpResponse.mStatusCode == null) {
throw new IllegalArgumentException("Empty status code.");
}
diff --git a/federatedcompute/apk/AndroidManifest.xml b/federatedcompute/apk/AndroidManifest.xml
index c317e09..595bb58 100644
--- a/federatedcompute/apk/AndroidManifest.xml
+++ b/federatedcompute/apk/AndroidManifest.xml
@@ -75,7 +75,7 @@
</service>
<!-- On BOOT_COMPLETED receiver for registering jobs -->
<receiver android:name=".FederatedComputeBroadcastReceiver"
- android:enabled="true"
+ android:enabled="@bool/config_enableBootReceiver"
android:exported="false">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
diff --git a/federatedcompute/apk/res/values/config.xml b/federatedcompute/apk/res/values/config.xml
new file mode 100644
index 0000000..03d218e
--- /dev/null
+++ b/federatedcompute/apk/res/values/config.xml
@@ -0,0 +1,4 @@
+<resources>
+ <!-- Enable or disable boot receiver of federatedcompute -->
+ <bool name="config_enableBootReceiver">true</bool>
+</resources>
\ No newline at end of file
diff --git a/federatedcompute/src/com/android/federatedcompute/services/common/Flags.java b/federatedcompute/src/com/android/federatedcompute/services/common/Flags.java
index d3800be..a12659d 100644
--- a/federatedcompute/src/com/android/federatedcompute/services/common/Flags.java
+++ b/federatedcompute/src/com/android/federatedcompute/services/common/Flags.java
@@ -243,7 +243,7 @@
long FCP_DEFAULT_MEMORY_SIZE_LIMIT = 50000000L; // 50 MBs in bytes
- /** Provides upper limit for FCP temp files. */
+ /** Provides lower limit for FCP temp files. */
default long getFcpMemorySizeLimit() {
return FCP_DEFAULT_MEMORY_SIZE_LIMIT;
}
@@ -267,4 +267,11 @@
default int getFcpTaskLimitPerPackage() {
return DEFAULT_FCP_TASK_LIMIT_PER_PACKAGE;
}
+
+ int FCP_DEFAULT_CHECKPOINT_FILE_SIZE_LIMIT = 50000000; // 50 MBs in bytes
+
+ /** Provides upper limit for FCP temp files. */
+ default int getFcpCheckpointFileSizeLimit() {
+ return FCP_DEFAULT_CHECKPOINT_FILE_SIZE_LIMIT;
+ }
}
diff --git a/federatedcompute/src/com/android/federatedcompute/services/common/PhFlags.java b/federatedcompute/src/com/android/federatedcompute/services/common/PhFlags.java
index ea2e88a..82df441 100644
--- a/federatedcompute/src/com/android/federatedcompute/services/common/PhFlags.java
+++ b/federatedcompute/src/com/android/federatedcompute/services/common/PhFlags.java
@@ -77,6 +77,7 @@
static final String FCP_MEMORY_SIZE_LIMIT_CONFIG_NAME = "memory_size_limit";
static final String FCP_TASK_LIMIT_PER_PACKAGE_CONFIG_NAME = "task_limit_per_package";
+ static final String FCP_CHECKPOINT_FILE_SIZE_LIMIT_CONFIG_NAME = "checkpoint_file_size_limit";
static final String FCP_ENABLE_CLIENT_ERROR_LOGGING = "fcp_enable_client_error_logging";
static final String FCP_ENABLE_BACKGROUND_JOBS_LOGGING = "fcp_enable_background_jobs_logging";
static final String FCP_BACKGROUND_JOB_LOGGING_SAMPLING_RATE =
@@ -273,18 +274,16 @@
/* defaultValue= */ ENABLE_CLIENT_ERROR_LOGGING);
}
+ /**
+ * {@inheritDoc}
+ *
+ * <p>This method always return {@code true} because the underlying flag is fully launched on
+ * {@code FederatedCompute} but the method cannot be removed (as it's defined on {@code
+ * ModuleSharedFlags}).
+ */
@Override
public boolean getBackgroundJobsLoggingEnabled() {
- // needs stable: execution stats may be less accurate if value changed during job execution
- return (boolean)
- sStableFlags.computeIfAbsent(
- FCP_ENABLE_BACKGROUND_JOBS_LOGGING,
- key -> {
- return DeviceConfig.getBoolean(
- /* namespace= */ NAMESPACE_ON_DEVICE_PERSONALIZATION,
- /* name= */ FCP_ENABLE_BACKGROUND_JOBS_LOGGING,
- /* defaultValue= */ BACKGROUND_JOB_LOGGING_ENABLED);
- });
+ return true;
}
@Override
@@ -366,4 +365,12 @@
/* name= */ FCP_TASK_LIMIT_PER_PACKAGE_CONFIG_NAME,
/* defaultValue= */ DEFAULT_FCP_TASK_LIMIT_PER_PACKAGE);
}
+
+ @Override
+ public int getFcpCheckpointFileSizeLimit() {
+ return DeviceConfig.getInt(
+ /* namespace= */ NAMESPACE_ON_DEVICE_PERSONALIZATION,
+ /* name= */ FCP_CHECKPOINT_FILE_SIZE_LIMIT_CONFIG_NAME,
+ /* defaultValue= */ FCP_DEFAULT_CHECKPOINT_FILE_SIZE_LIMIT);
+ }
}
diff --git a/federatedcompute/src/com/android/federatedcompute/services/common/TrainingEventLogger.java b/federatedcompute/src/com/android/federatedcompute/services/common/TrainingEventLogger.java
index 6fb6be0..f9541a2 100644
--- a/federatedcompute/src/com/android/federatedcompute/services/common/TrainingEventLogger.java
+++ b/federatedcompute/src/com/android/federatedcompute/services/common/TrainingEventLogger.java
@@ -25,15 +25,18 @@
import static com.android.federatedcompute.services.stats.FederatedComputeStatsLog.FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_DOWNLOAD_PLAN_URI_RECEIVED;
import static com.android.federatedcompute.services.stats.FederatedComputeStatsLog.FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_DOWNLOAD_STARTED;
import static com.android.federatedcompute.services.stats.FederatedComputeStatsLog.FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_DOWNLOAD_TURNED_AWAY;
+import static com.android.federatedcompute.services.stats.FederatedComputeStatsLog.FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_DOWNLOAD_TURNED_AWAY_NO_TASK_AVAILABLE;
+import static com.android.federatedcompute.services.stats.FederatedComputeStatsLog.FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_DOWNLOAD_TURNED_AWAY_UNAUTHENTICATED;
+import static com.android.federatedcompute.services.stats.FederatedComputeStatsLog.FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_DOWNLOAD_TURNED_AWAY_UNAUTHORIZED;
import static com.android.federatedcompute.services.stats.FederatedComputeStatsLog.FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_FAILURE_UPLOADED;
import static com.android.federatedcompute.services.stats.FederatedComputeStatsLog.FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_FAILURE_UPLOAD_STARTED;
import static com.android.federatedcompute.services.stats.FederatedComputeStatsLog.FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_INITIATE_REPORT_RESULT_AUTH_SUCCEEDED;
import static com.android.federatedcompute.services.stats.FederatedComputeStatsLog.FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_KEY_ATTESTATION_SUCCEEDED;
-import static com.android.federatedcompute.services.stats.FederatedComputeStatsLog.FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_NOT_STARTED;
import static com.android.federatedcompute.services.stats.FederatedComputeStatsLog.FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_REPORT_RESULT_UNAUTHORIZED;
import static com.android.federatedcompute.services.stats.FederatedComputeStatsLog.FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_RESULT_UPLOADED;
import static com.android.federatedcompute.services.stats.FederatedComputeStatsLog.FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_RESULT_UPLOAD_SERVER_ABORTED;
import static com.android.federatedcompute.services.stats.FederatedComputeStatsLog.FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_RESULT_UPLOAD_STARTED;
+import static com.android.federatedcompute.services.stats.FederatedComputeStatsLog.FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_RUN_FAILED_CONDITIONS_FAILED;
import static com.android.federatedcompute.services.stats.FederatedComputeStatsLog.FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_TASK_ASSIGNMENT_AUTH_SUCCEEDED;
import static com.android.federatedcompute.services.stats.FederatedComputeStatsLog.FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_TASK_ASSIGNMENT_UNAUTHORIZED;
@@ -41,6 +44,8 @@
import com.android.federatedcompute.services.statsd.FederatedComputeStatsdLogger;
import com.android.federatedcompute.services.statsd.TrainingEventReported;
+import com.google.internal.federatedcompute.v1.RejectionInfo;
+
/** The helper function to log {@link TrainingEventReported} in statsd. */
public class TrainingEventLogger {
private static final String TAG = TrainingEventLogger.class.getSimpleName();
@@ -70,7 +75,7 @@
TrainingEventReported.Builder event =
new TrainingEventReported.Builder()
.setEventKind(
- FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_NOT_STARTED);
+ FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_RUN_FAILED_CONDITIONS_FAILED);
logEvent(event);
}
@@ -84,10 +89,28 @@
}
/** Logs when device is turned away from federated training. */
- public void logCheckinRejected(NetworkStats networkStats) {
- logNetworkEvent(
- FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_DOWNLOAD_TURNED_AWAY,
- networkStats);
+ public void logCheckinRejected(RejectionInfo rejectionInfo, NetworkStats networkStats) {
+ switch (rejectionInfo.getReason()) {
+ case UNAUTHORIZED:
+ logNetworkEvent(
+ FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_DOWNLOAD_TURNED_AWAY_UNAUTHORIZED,
+ networkStats);
+ break;
+ case UNAUTHENTICATED:
+ logNetworkEvent(
+ FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_DOWNLOAD_TURNED_AWAY_UNAUTHENTICATED,
+ networkStats);
+ break;
+ case NO_TASK_AVAILABLE:
+ logNetworkEvent(
+ FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_DOWNLOAD_TURNED_AWAY_NO_TASK_AVAILABLE,
+ networkStats);
+ break;
+ default:
+ logNetworkEvent(
+ FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_DOWNLOAD_TURNED_AWAY,
+ networkStats);
+ }
}
/**
diff --git a/federatedcompute/src/com/android/federatedcompute/services/data/FederatedComputeDbHelper.java b/federatedcompute/src/com/android/federatedcompute/services/data/FederatedComputeDbHelper.java
index a7be7b6..f82a9f1 100644
--- a/federatedcompute/src/com/android/federatedcompute/services/data/FederatedComputeDbHelper.java
+++ b/federatedcompute/src/com/android/federatedcompute/services/data/FederatedComputeDbHelper.java
@@ -148,14 +148,12 @@
* only.
*/
@VisibleForTesting
- public static FederatedComputeDbHelper getInstanceForTest(Context context) {
- synchronized (FederatedComputeDbHelper.class) {
- if (sInstance == null) {
- // Use null database name to make it in-memory
- sInstance = new FederatedComputeDbHelper(context, null);
- }
- return sInstance;
+ public static synchronized FederatedComputeDbHelper getInstanceForTest(Context context) {
+ if (sInstance == null) {
+ // Use null database name to make it in-memory
+ sInstance = new FederatedComputeDbHelper(context, null);
}
+ return sInstance;
}
/**
@@ -263,12 +261,10 @@
/** It's only public to testing. */
@VisibleForTesting
- public static void resetInstance() {
- synchronized (FederatedComputeDbHelper.class) {
- if (sInstance != null) {
- sInstance.close();
- sInstance = null;
- }
+ public static synchronized void resetInstance() {
+ if (sInstance != null) {
+ sInstance.close();
+ sInstance = null;
}
}
diff --git a/federatedcompute/src/com/android/federatedcompute/services/data/FederatedComputeEncryptionKeyContract.java b/federatedcompute/src/com/android/federatedcompute/services/data/FederatedComputeEncryptionKeyContract.java
index f5c6d07..81148ec 100644
--- a/federatedcompute/src/com/android/federatedcompute/services/data/FederatedComputeEncryptionKeyContract.java
+++ b/federatedcompute/src/com/android/federatedcompute/services/data/FederatedComputeEncryptionKeyContract.java
@@ -21,7 +21,7 @@
private FederatedComputeEncryptionKeyContract() {}
- public static final class FederatedComputeEncryptionColumns {
+ static final class FederatedComputeEncryptionColumns {
private FederatedComputeEncryptionColumns() {}
/**
diff --git a/federatedcompute/src/com/android/federatedcompute/services/data/FederatedComputeEncryptionKeyDao.java b/federatedcompute/src/com/android/federatedcompute/services/data/FederatedComputeEncryptionKeyDao.java
index 33282de..82cf969 100644
--- a/federatedcompute/src/com/android/federatedcompute/services/data/FederatedComputeEncryptionKeyDao.java
+++ b/federatedcompute/src/com/android/federatedcompute/services/data/FederatedComputeEncryptionKeyDao.java
@@ -35,7 +35,7 @@
import java.util.ArrayList;
import java.util.List;
-/** DAO for accessing encryption key table */
+/** DAO for accessing encryption key table that stores {@link FederatedComputeEncryptionKey}s. */
public class FederatedComputeEncryptionKeyDao {
private static final String TAG = FederatedComputeEncryptionKeyDao.class.getSimpleName();
@@ -50,9 +50,7 @@
mClock = clock;
}
- /**
- * @return an instance of FederatedComputeEncryptionKeyDao given a context
- */
+ /** Returns an instance of {@link FederatedComputeEncryptionKeyDao} given a context. */
@NonNull
public static FederatedComputeEncryptionKeyDao getInstance(Context context) {
if (sSingletonInstance == null) {
@@ -68,7 +66,11 @@
return sSingletonInstance;
}
- /** It is only public to unit test. */
+ /**
+ * Helper method to get instance of {@link FederatedComputeEncryptionKeyDao} for use in tests.
+ *
+ * <p>Public for use in unit tests.
+ */
@VisibleForTesting
public static FederatedComputeEncryptionKeyDao getInstanceForTest(Context context) {
if (sSingletonInstance == null) {
@@ -84,7 +86,12 @@
return sSingletonInstance;
}
- /** Insert a key to the encryption_key table. */
+ /**
+ * Insert a key to the encryption_key table.
+ *
+ * @param key the {@link FederatedComputeEncryptionKey} to insert into DB.
+ * @return Whether the key was inserted successfully.
+ */
public boolean insertEncryptionKey(FederatedComputeEncryptionKey key) {
SQLiteDatabase db = mDbHelper.safeGetWritableDatabase();
if (db == null) {
@@ -98,16 +105,16 @@
values.put(FederatedComputeEncryptionColumns.CREATION_TIME, key.getCreationTime());
values.put(FederatedComputeEncryptionColumns.EXPIRY_TIME, key.getExpiryTime());
- long jobId =
+ long insertedRowId =
db.insertWithOnConflict(
ENCRYPTION_KEY_TABLE, "", values, SQLiteDatabase.CONFLICT_REPLACE);
- return jobId != -1;
+ return insertedRowId != -1;
}
/**
- * Read from encryption key table given selection, order and limit conidtions.
+ * Read from encryption key table given selection, order and limit conditions.
*
- * @return a list of {@link FederatedComputeEncryptionKey}.
+ * @return a list of matching {@link FederatedComputeEncryptionKey}s.
*/
@VisibleForTesting
public List<FederatedComputeEncryptionKey> readFederatedComputeEncryptionKeysFromDatabase(
diff --git a/federatedcompute/src/com/android/federatedcompute/services/encryption/BackgroundKeyFetchJobService.java b/federatedcompute/src/com/android/federatedcompute/services/encryption/BackgroundKeyFetchJobService.java
index d79743a..91ad4ef 100644
--- a/federatedcompute/src/com/android/federatedcompute/services/encryption/BackgroundKeyFetchJobService.java
+++ b/federatedcompute/src/com/android/federatedcompute/services/encryption/BackgroundKeyFetchJobService.java
@@ -49,6 +49,7 @@
private static final int ENCRYPTION_KEY_FETCH_JOB_ID =
FederatedComputeJobInfo.ENCRYPTION_KEY_FETCH_JOB_ID;
+ @VisibleForTesting
static class Injector {
ListeningExecutorService getExecutor() {
return FederatedComputeExecutors.getBackgroundExecutor();
@@ -66,7 +67,7 @@
private final Injector mInjector;
public BackgroundKeyFetchJobService() {
- mInjector = new Injector();
+ this(new Injector());
}
@VisibleForTesting
diff --git a/federatedcompute/src/com/android/federatedcompute/services/encryption/Encrypter.java b/federatedcompute/src/com/android/federatedcompute/services/encryption/Encrypter.java
index 7398e1f..3c4adaa 100644
--- a/federatedcompute/src/com/android/federatedcompute/services/encryption/Encrypter.java
+++ b/federatedcompute/src/com/android/federatedcompute/services/encryption/Encrypter.java
@@ -20,7 +20,7 @@
public interface Encrypter {
/**
- * encrypt {@code plainText} to cipher text {@code byte[]}.
+ * Encrypt the {@code plainText} to cipher text {@code byte[]}.
*
* @param publicKey the public key used for encryption
* @param plainText the plain text string to encrypt
@@ -28,5 +28,4 @@
* @return the encrypted ciphertext
*/
byte[] encrypt(byte[] publicKey, byte[] plainText, byte[] associatedData);
-
}
diff --git a/federatedcompute/src/com/android/federatedcompute/services/encryption/FederatedComputeEncryptionKeyManager.java b/federatedcompute/src/com/android/federatedcompute/services/encryption/FederatedComputeEncryptionKeyManager.java
index eef3371..af728d9 100644
--- a/federatedcompute/src/com/android/federatedcompute/services/encryption/FederatedComputeEncryptionKeyManager.java
+++ b/federatedcompute/src/com/android/federatedcompute/services/encryption/FederatedComputeEncryptionKeyManager.java
@@ -24,12 +24,13 @@
import com.android.federatedcompute.services.common.FlagsFactory;
import com.android.federatedcompute.services.data.FederatedComputeEncryptionKey;
import com.android.federatedcompute.services.data.FederatedComputeEncryptionKeyDao;
-import com.android.federatedcompute.services.http.FederatedComputeHttpRequest;
-import com.android.federatedcompute.services.http.FederatedComputeHttpResponse;
-import com.android.federatedcompute.services.http.HttpClient;
import com.android.federatedcompute.services.http.HttpClientUtil;
import com.android.odp.module.common.Clock;
+import com.android.odp.module.common.HttpClient;
+import com.android.odp.module.common.HttpClientUtils;
import com.android.odp.module.common.MonotonicClock;
+import com.android.odp.module.common.OdpHttpRequest;
+import com.android.odp.module.common.OdpHttpResponse;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
@@ -91,24 +92,21 @@
mBackgroundExecutor = backgroundExecutor;
}
- /**
- * @return a singleton instance for key manager
- */
+ /** Returns a singleton instance for the {@link FederatedComputeEncryptionKeyManager}. */
public static FederatedComputeEncryptionKeyManager getInstance(Context context) {
if (sBackgroundKeyManager == null) {
synchronized (FederatedComputeEncryptionKeyManager.class) {
if (sBackgroundKeyManager == null) {
FederatedComputeEncryptionKeyDao encryptionKeyDao =
FederatedComputeEncryptionKeyDao.getInstance(context);
- HttpClient client = new HttpClient();
- Clock clock = MonotonicClock.getInstance();
- Flags flags = FlagsFactory.getFlags();
sBackgroundKeyManager =
new FederatedComputeEncryptionKeyManager(
- clock,
+ MonotonicClock.getInstance(),
encryptionKeyDao,
- flags,
- client,
+ FlagsFactory.getFlags(),
+ new HttpClient(
+ FlagsFactory.getFlags().getHttpRequestRetryLimit(),
+ FederatedComputeExecutors.getBlockingExecutor()),
FederatedComputeExecutors.getBackgroundExecutor());
}
}
@@ -118,7 +116,7 @@
/** For testing only, returns an instance of key manager for test. */
@VisibleForTesting
- public static FederatedComputeEncryptionKeyManager getInstanceForTest(
+ static FederatedComputeEncryptionKeyManager getInstanceForTest(
Clock clock,
FederatedComputeEncryptionKeyDao encryptionKeyDao,
Flags flags,
@@ -140,7 +138,7 @@
* Fetch the active key from the server, persists the fetched key to encryption_key table, and
* deletes expired keys
*/
- public FluentFuture<List<FederatedComputeEncryptionKey>> fetchAndPersistActiveKeys(
+ FluentFuture<List<FederatedComputeEncryptionKey>> fetchAndPersistActiveKeys(
@FederatedComputeEncryptionKey.KeyType int keyType, boolean isScheduledJob) {
String fetchUri = mFlags.getEncryptionKeyFetchUrl();
if (fetchUri == null) {
@@ -148,13 +146,13 @@
new IllegalArgumentException("Url to fetch active encryption keys is null")));
}
- FederatedComputeHttpRequest request;
+ OdpHttpRequest request;
try {
request =
- FederatedComputeHttpRequest.create(
+ OdpHttpRequest.create(
fetchUri,
- HttpClientUtil.HttpMethod.GET,
- new HashMap<String, String>(),
+ HttpClientUtils.HttpMethod.GET,
+ new HashMap<>(),
HttpClientUtil.EMPTY_BODY);
} catch (Exception e) {
return FluentFuture.from(Futures.immediateFailedFuture(e));
@@ -180,7 +178,7 @@
}
private ImmutableList<FederatedComputeEncryptionKey> parseFetchEncryptionKeyPayload(
- FederatedComputeHttpResponse keyFetchResponse,
+ OdpHttpResponse keyFetchResponse,
@FederatedComputeEncryptionKey.KeyType int keyType,
Long fetchTime) {
String payload = new String(Objects.requireNonNull(keyFetchResponse.getPayload()));
@@ -224,7 +222,7 @@
/**
* Parse the "age" and "cache-control" of response headers. Calculate the ttl of the current key
- * maxage (in cache-control) - age.
+ * max-age (in cache-control) - age.
*
* @return the ttl in seconds of the keys.
*/
@@ -242,7 +240,6 @@
cacheControl = field.get(0).toLowerCase(Locale.ENGLISH);
remainingHeaders -= 1;
}
-
} else if (key.equalsIgnoreCase(
EncryptionKeyResponseContract.RESPONSE_HEADER_AGE_LABEL)) {
List<String> field = headers.get(key);
@@ -292,9 +289,11 @@
return maxAge - cachedAge;
}
- /** Get active keys, if there is no active key, then force a fetch from the key service.
- * In the case of key fetching from the key service, the http call
- * is executed on a BlockingExecutor.
+ /**
+ * Get active keys, if there is no active key, then force a fetch from the key service. In the
+ * case of key fetching from the key service, the http call is executed on a {@code
+ * BlockingExecutor}.
+ *
* @return The list of active keys.
*/
public List<FederatedComputeEncryptionKey> getOrFetchActiveKeys(int keyType, int keyCount) {
diff --git a/federatedcompute/src/com/android/federatedcompute/services/examplestore/ExampleStoreServiceProvider.java b/federatedcompute/src/com/android/federatedcompute/services/examplestore/ExampleStoreServiceProvider.java
index 849d91e..f8f1d55 100644
--- a/federatedcompute/src/com/android/federatedcompute/services/examplestore/ExampleStoreServiceProvider.java
+++ b/federatedcompute/src/com/android/federatedcompute/services/examplestore/ExampleStoreServiceProvider.java
@@ -17,6 +17,10 @@
package com.android.federatedcompute.services.examplestore;
import static com.android.federatedcompute.services.common.Constants.TRACE_GET_EXAMPLE_STORE_ITERATOR;
+import static com.android.federatedcompute.services.stats.FederatedComputeStatsLog.FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_EXAMPLE_STORE_START_QUERY_ERROR;
+import static com.android.federatedcompute.services.stats.FederatedComputeStatsLog.FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_EXAMPLE_STORE_START_QUERY_START;
+import static com.android.federatedcompute.services.stats.FederatedComputeStatsLog.FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_EXAMPLE_STORE_START_QUERY_SUCCESS;
+import static com.android.federatedcompute.services.stats.FederatedComputeStatsLog.FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_EXAMPLE_STORE_START_QUERY_TIMEOUT;
import android.content.Context;
import android.federatedcompute.aidl.IExampleStoreCallback;
@@ -24,18 +28,14 @@
import android.federatedcompute.aidl.IExampleStoreService;
import android.federatedcompute.common.ClientConstants;
import android.os.Bundle;
-import android.os.SystemClock;
import android.os.Trace;
-import androidx.concurrent.futures.CallbackToFutureAdapter;
-
import com.android.federatedcompute.internal.util.AbstractServiceBinder;
import com.android.federatedcompute.internal.util.LogUtil;
-import com.android.federatedcompute.services.common.ExampleStats;
import com.android.federatedcompute.services.common.FlagsFactory;
+import com.android.federatedcompute.services.common.TrainingEventLogger;
import com.android.federatedcompute.services.data.FederatedTrainingTask;
-import com.google.common.util.concurrent.ListenableFuture;
import com.google.internal.federated.plan.ExampleSelector;
import java.util.concurrent.ArrayBlockingQueue;
@@ -69,7 +69,8 @@
FederatedTrainingTask task,
String taskName,
int minExample,
- ExampleSelector exampleSelector) {
+ ExampleSelector exampleSelector,
+ TrainingEventLogger logger) {
try {
Trace.beginAsyncSection(TRACE_GET_EXAMPLE_STORE_ITERATOR, 1);
Bundle bundle = new Bundle();
@@ -87,6 +88,8 @@
ClientConstants.EXTRA_COLLECTION_URI, exampleSelector.getCollectionUri());
}
BlockingQueue<CallbackResult> asyncResult = new ArrayBlockingQueue<>(1);
+ logger.logEventKind(
+ FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_EXAMPLE_STORE_START_QUERY_START);
exampleStoreService.startQuery(
bundle,
new IExampleStoreCallback.Stub() {
@@ -109,12 +112,23 @@
FlagsFactory.getFlags().getExampleStoreServiceCallbackTimeoutSec(),
TimeUnit.SECONDS);
// Callback result is null if timeout.
- if (callbackResult == null || callbackResult.mErrorCode != 0) {
+ if (callbackResult == null) {
+ logger.logEventKind(
+ FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_EXAMPLE_STORE_START_QUERY_TIMEOUT);
return null;
}
+ if (callbackResult.mErrorCode != 0 || callbackResult.mIterator == null) {
+ logger.logEventKind(
+ FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_EXAMPLE_STORE_START_QUERY_ERROR);
+ return null;
+ }
+ logger.logEventKind(
+ FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_EXAMPLE_STORE_START_QUERY_SUCCESS);
return callbackResult.mIterator;
} catch (Exception e) {
LogUtil.e(TAG, e, "Got exception when StartQuery");
+ logger.logEventKind(
+ FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_EXAMPLE_STORE_START_QUERY_ERROR);
return null;
}
}
@@ -128,45 +142,4 @@
mErrorCode = errorCode;
}
}
-
- private ListenableFuture<IExampleStoreIterator> runExampleStoreStartQuery(
- IExampleStoreService exampleStoreService,
- Bundle input,
- ExampleStats exampleStats,
- long startCallTimeNanos) {
- return CallbackToFutureAdapter.getFuture(
- completer -> {
- try {
- exampleStoreService.startQuery(
- input,
- new IExampleStoreCallback.Stub() {
- @Override
- public void onStartQuerySuccess(
- IExampleStoreIterator iterator) {
- LogUtil.d(TAG, "Acquired iterator");
- exampleStats.mStartQueryLatencyNanos.addAndGet(
- SystemClock.elapsedRealtimeNanos()
- - startCallTimeNanos);
- completer.set(iterator);
- Trace.endAsyncSection(TRACE_GET_EXAMPLE_STORE_ITERATOR, 0);
- }
-
- @Override
- public void onStartQueryFailure(int errorCode) {
- LogUtil.e(TAG, "Could not acquire iterator: " + errorCode);
- exampleStats.mStartQueryLatencyNanos.addAndGet(
- SystemClock.elapsedRealtimeNanos()
- - startCallTimeNanos);
- completer.setException(
- new IllegalStateException(
- "StartQuery failed: " + errorCode));
- Trace.endAsyncSection(TRACE_GET_EXAMPLE_STORE_ITERATOR, 0);
- }
- });
- } catch (Exception e) {
- completer.setException(e);
- }
- return "runExampleStoreStartQuery";
- });
- }
}
diff --git a/federatedcompute/src/com/android/federatedcompute/services/http/CheckinResult.java b/federatedcompute/src/com/android/federatedcompute/services/http/CheckinResult.java
index 00542f4..d940935 100644
--- a/federatedcompute/src/com/android/federatedcompute/services/http/CheckinResult.java
+++ b/federatedcompute/src/com/android/federatedcompute/services/http/CheckinResult.java
@@ -25,22 +25,27 @@
import com.google.ondevicepersonalization.federatedcompute.proto.TaskAssignment;
/**
- * The result after client calls TaskAssignemnt API. It includes init checkpoint data and plan data.
+ * The result after client calls TaskAssignment API. It includes init checkpoint data and plan data.
*/
public class CheckinResult {
- private String mInputCheckpoint = null;
- private ClientOnlyPlan mPlanData = null;
- private TaskAssignment mTaskAssignment = null;
- private RejectionInfo mRejectionInfo = null;
+ private final String mInputCheckpoint;
+ private final ClientOnlyPlan mPlanData;
+ private final TaskAssignment mTaskAssignment;
+ private final RejectionInfo mRejectionInfo;
+
public CheckinResult(
String inputCheckpoint, ClientOnlyPlan planData, TaskAssignment taskAssignment) {
this.mInputCheckpoint = inputCheckpoint;
this.mPlanData = planData;
this.mTaskAssignment = taskAssignment;
+ this.mRejectionInfo = null;
}
public CheckinResult(RejectionInfo mRejectionInfo) {
this.mRejectionInfo = mRejectionInfo;
+ this.mInputCheckpoint = null;
+ this.mPlanData = null;
+ this.mTaskAssignment = null;
}
@Nullable
diff --git a/federatedcompute/src/com/android/federatedcompute/services/http/HttpClient.java b/federatedcompute/src/com/android/federatedcompute/services/http/HttpClient.java
deleted file mode 100644
index 5bfd162..0000000
--- a/federatedcompute/src/com/android/federatedcompute/services/http/HttpClient.java
+++ /dev/null
@@ -1,198 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.federatedcompute.services.http;
-
-import static com.android.federatedcompute.services.common.FederatedComputeExecutors.getBlockingExecutor;
-import static com.android.federatedcompute.services.http.HttpClientUtil.HTTP_OK_STATUS;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-
-import com.android.federatedcompute.internal.util.LogUtil;
-import com.android.federatedcompute.services.common.Flags;
-import com.android.federatedcompute.services.common.PhFlags;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.util.concurrent.Futures;
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.io.BufferedOutputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.HttpURLConnection;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.net.URLConnection;
-import java.util.Map;
-import java.util.Objects;
-import java.util.concurrent.TimeUnit;
-
-/**
- * The HTTP client to be used by the FederatedCompute to communicate with remote federated servers.
- */
-public class HttpClient {
- private static final String TAG = HttpClient.class.getSimpleName();
- private static final int NETWORK_CONNECT_TIMEOUT_MS = (int) TimeUnit.SECONDS.toMillis(5);
- private static final int NETWORK_READ_TIMEOUT_MS = (int) TimeUnit.SECONDS.toMillis(30);
- private final Flags mFlags;
-
- public HttpClient() {
- mFlags = PhFlags.getInstance();
- }
-
- @NonNull
- @VisibleForTesting
- URLConnection setup(@NonNull URL url) throws IOException {
- Objects.requireNonNull(url);
- URLConnection urlConnection = url.openConnection();
- urlConnection.setConnectTimeout(NETWORK_CONNECT_TIMEOUT_MS);
- urlConnection.setReadTimeout(NETWORK_READ_TIMEOUT_MS);
- return urlConnection;
- }
-
- /**
- * Perform HTTP requests based on given information asynchronously with retries in case http
- * will return not OK response code.
- */
- @NonNull
- public ListenableFuture<FederatedComputeHttpResponse> performRequestAsyncWithRetry(
- FederatedComputeHttpRequest request) {
- try {
- return getBlockingExecutor().submit(() -> performRequestWithRetry(request));
- } catch (Exception e) {
- return Futures.immediateFailedFuture(e);
- }
- }
-
- /** Perform HTTP requests based on given information with retries. */
- @NonNull
- public FederatedComputeHttpResponse performRequestWithRetry(FederatedComputeHttpRequest request)
- throws IOException {
- int count = 0;
- FederatedComputeHttpResponse response = null;
- int retryLimit = mFlags.getHttpRequestRetryLimit();
- while (count < retryLimit) {
- try {
- response = performRequest(request);
- if (HTTP_OK_STATUS.contains(response.getStatusCode())) {
- return response;
- }
- // we want to continue retry in case it is IO exception.
- } catch (IOException e) {
- // propagate IO exception after RETRY_LIMIT times attempt.
- if (count >= retryLimit - 1) {
- throw e;
- }
- } finally {
- count++;
- }
- }
- return response;
- }
-
- /** Perform HTTP requests based on given information. */
- @NonNull
- public FederatedComputeHttpResponse performRequest(FederatedComputeHttpRequest request)
- throws IOException {
- if (request.getUri() == null || request.getHttpMethod() == null) {
- LogUtil.e(TAG, "Endpoint or http method is empty");
- throw new IllegalArgumentException("Endpoint or http method is empty");
- }
-
- URL url;
- try {
- url = new URL(request.getUri());
- } catch (MalformedURLException e) {
- LogUtil.e(TAG, e, "Malformed registration target URL");
- throw new IllegalArgumentException("Malformed registration target URL", e);
- }
-
- HttpURLConnection urlConnection;
- try {
- urlConnection = (HttpURLConnection) setup(url);
- } catch (IOException e) {
- LogUtil.e(TAG, e, "Failed to open target URL");
- throw new IOException("Failed to open target URL", e);
- }
-
- try {
- urlConnection.setRequestMethod(request.getHttpMethod().name());
- urlConnection.setInstanceFollowRedirects(true);
-
- if (request.getExtraHeaders() != null && !request.getExtraHeaders().isEmpty()) {
- for (Map.Entry<String, String> entry : request.getExtraHeaders().entrySet()) {
- urlConnection.setRequestProperty(entry.getKey(), entry.getValue());
- }
- }
-
- if (request.getBody() != null && request.getBody().length > 0) {
- urlConnection.setDoOutput(true);
- try (BufferedOutputStream out =
- new BufferedOutputStream(urlConnection.getOutputStream())) {
- out.write(request.getBody());
- }
- }
-
- int responseCode = urlConnection.getResponseCode();
- if (HTTP_OK_STATUS.contains(responseCode)) {
- return new FederatedComputeHttpResponse.Builder()
- .setPayload(
- getByteArray(
- urlConnection.getInputStream(),
- urlConnection.getContentLengthLong()))
- .setHeaders(urlConnection.getHeaderFields())
- .setStatusCode(responseCode)
- .build();
- } else {
- return new FederatedComputeHttpResponse.Builder()
- .setPayload(
- getByteArray(
- urlConnection.getErrorStream(),
- urlConnection.getContentLengthLong()))
- .setHeaders(urlConnection.getHeaderFields())
- .setStatusCode(responseCode)
- .build();
- }
- } catch (IOException e) {
- LogUtil.e(TAG, e, "Failed to get registration response");
- throw new IOException("Failed to get registration response", e);
- } finally {
- if (urlConnection != null) {
- urlConnection.disconnect();
- }
- }
- }
-
- private byte[] getByteArray(@Nullable InputStream in, long contentLength) throws IOException {
- if (contentLength == 0) {
- return HttpClientUtil.EMPTY_BODY;
- }
- try {
- // TODO(b/297952090): evaluate the large file download.
- byte[] buffer = new byte[HttpClientUtil.DEFAULT_BUFFER_SIZE];
- ByteArrayOutputStream out = new ByteArrayOutputStream();
- int bytesRead;
- while ((bytesRead = in.read(buffer)) != -1) {
- out.write(buffer, 0, bytesRead);
- }
- return out.toByteArray();
- } finally {
- in.close();
- }
- }
-}
diff --git a/federatedcompute/src/com/android/federatedcompute/services/http/HttpClientUtil.java b/federatedcompute/src/com/android/federatedcompute/services/http/HttpClientUtil.java
index 7847118..b1925f1 100644
--- a/federatedcompute/src/com/android/federatedcompute/services/http/HttpClientUtil.java
+++ b/federatedcompute/src/com/android/federatedcompute/services/http/HttpClientUtil.java
@@ -16,21 +16,10 @@
package com.android.federatedcompute.services.http;
-import com.android.federatedcompute.internal.util.LogUtil;
-
import com.google.common.collect.ImmutableSet;
-import com.google.protobuf.ByteString;
import org.json.JSONObject;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.util.List;
-import java.util.Map;
-import java.util.zip.GZIPInputStream;
-import java.util.zip.GZIPOutputStream;
-
/** Utility class containing http related variable e.g. headers, method. */
public final class HttpClientUtil {
private static final String TAG = HttpClientUtil.class.getSimpleName();
@@ -64,13 +53,6 @@
public static final int DEFAULT_BUFFER_SIZE = 1024;
public static final byte[] EMPTY_BODY = new byte[0];
- /** The supported http methods. */
- public enum HttpMethod {
- GET,
- POST,
- PUT,
- }
-
public static final class FederatedComputePayloadDataContract {
public static final String KEY_ID = "keyId";
@@ -81,81 +63,5 @@
public static final byte[] ASSOCIATED_DATA = new JSONObject().toString().getBytes();
}
- /** Compresses the input data using Gzip. */
- public static byte[] compressWithGzip(byte[] uncompressedData) {
- try (ByteString.Output outputStream = ByteString.newOutput(uncompressedData.length);
- GZIPOutputStream gzipOutputStream = new GZIPOutputStream(outputStream)) {
- gzipOutputStream.write(uncompressedData);
- gzipOutputStream.finish();
- return outputStream.toByteString().toByteArray();
- } catch (IOException e) {
- LogUtil.e(TAG, "Failed to compress using Gzip");
- throw new IllegalStateException("Failed to compress using Gzip", e);
- }
- }
-
- /** Uncompresses the input data using Gzip. */
- public static byte[] uncompressWithGzip(byte[] data) {
- try (ByteArrayInputStream inputStream = new ByteArrayInputStream(data);
- GZIPInputStream gzip = new GZIPInputStream(inputStream);
- ByteArrayOutputStream result = new ByteArrayOutputStream()) {
- int length;
- byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
- while ((length = gzip.read(buffer, 0, DEFAULT_BUFFER_SIZE)) > 0) {
- result.write(buffer, 0, length);
- }
- return result.toByteArray();
- } catch (Exception e) {
- LogUtil.e(TAG, e, "Failed to decompress the data.");
- throw new IllegalStateException("Failed to unscompress using Gzip", e);
- }
- }
-
- /** Calculates total bytes are sent via network based on provided http request. */
- public static long getTotalSentBytes(FederatedComputeHttpRequest request) {
- long totalBytes = 0;
- totalBytes +=
- request.getHttpMethod().name().length()
- + " ".length()
- + request.getUri().length()
- + " HTTP/1.1\r\n".length();
- for (String key : request.getExtraHeaders().keySet()) {
- totalBytes +=
- key.length()
- + ": ".length()
- + request.getExtraHeaders().get(key).length()
- + "\r\n".length();
- }
- if (request.getExtraHeaders().containsKey(CONTENT_LENGTH_HDR)) {
- totalBytes += Long.parseLong(request.getExtraHeaders().get(CONTENT_LENGTH_HDR));
- }
- return totalBytes;
- }
-
- /** Calculates total bytes are received via network based on provided http response. */
- public static long getTotalReceivedBytes(FederatedComputeHttpResponse response) {
- long totalBytes = 0;
- boolean foundContentLengthHdr = false;
- for (Map.Entry<String, List<String>> header : response.getHeaders().entrySet()) {
- if (header.getKey() == null) {
- continue;
- }
- for (String headerValue : header.getValue()) {
- totalBytes += header.getKey().length() + ": ".length();
- totalBytes += headerValue == null ? 0 : headerValue.length();
- }
- // Uses Content-Length header to estimate total received bytes which is the most
- // accurate.
- if (header.getKey().equals(CONTENT_LENGTH_HDR)) {
- totalBytes += Long.parseLong(header.getValue().get(0));
- foundContentLengthHdr = true;
- }
- }
- if (!foundContentLengthHdr && response.getPayload() != null) {
- totalBytes += response.getPayload().length;
- }
- return totalBytes;
- }
-
private HttpClientUtil() {}
}
diff --git a/federatedcompute/src/com/android/federatedcompute/services/http/HttpFederatedProtocol.java b/federatedcompute/src/com/android/federatedcompute/services/http/HttpFederatedProtocol.java
index 3dd2d25..fc7ce1f 100644
--- a/federatedcompute/src/com/android/federatedcompute/services/http/HttpFederatedProtocol.java
+++ b/federatedcompute/src/com/android/federatedcompute/services/http/HttpFederatedProtocol.java
@@ -19,10 +19,8 @@
import static com.android.federatedcompute.services.common.Constants.TRACE_HTTP_ISSUE_CHECKIN;
import static com.android.federatedcompute.services.common.Constants.TRACE_HTTP_REPORT_RESULT;
import static com.android.federatedcompute.services.common.FederatedComputeExecutors.getBackgroundExecutor;
+import static com.android.federatedcompute.services.common.FederatedComputeExecutors.getBlockingExecutor;
import static com.android.federatedcompute.services.common.FederatedComputeExecutors.getLightweightExecutor;
-import static com.android.federatedcompute.services.common.FileUtils.createTempFile;
-import static com.android.federatedcompute.services.common.FileUtils.readFileAsByteArray;
-import static com.android.federatedcompute.services.common.FileUtils.writeToFile;
import static com.android.federatedcompute.services.common.TrainingEventLogger.getTaskIdForLogging;
import static com.android.federatedcompute.services.http.HttpClientUtil.ACCEPT_ENCODING_HDR;
import static com.android.federatedcompute.services.http.HttpClientUtil.FCP_OWNER_ID_DIGEST;
@@ -31,10 +29,13 @@
import static com.android.federatedcompute.services.http.HttpClientUtil.HTTP_OK_STATUS;
import static com.android.federatedcompute.services.http.HttpClientUtil.HTTP_UNAUTHORIZED_STATUS;
import static com.android.federatedcompute.services.http.HttpClientUtil.ODP_IDEMPOTENCY_KEY;
-import static com.android.federatedcompute.services.http.HttpClientUtil.compressWithGzip;
-import static com.android.federatedcompute.services.http.HttpClientUtil.getTotalReceivedBytes;
-import static com.android.federatedcompute.services.http.HttpClientUtil.getTotalSentBytes;
-import static com.android.federatedcompute.services.http.HttpClientUtil.uncompressWithGzip;
+import static com.android.odp.module.common.FileUtils.createTempFile;
+import static com.android.odp.module.common.FileUtils.readFileAsByteArray;
+import static com.android.odp.module.common.FileUtils.writeToFile;
+import static com.android.odp.module.common.HttpClientUtils.compressWithGzip;
+import static com.android.odp.module.common.HttpClientUtils.getTotalReceivedBytes;
+import static com.android.odp.module.common.HttpClientUtils.getTotalSentBytes;
+import static com.android.odp.module.common.HttpClientUtils.uncompressWithGzip;
import android.os.Trace;
import android.util.Base64;
@@ -46,9 +47,12 @@
import com.android.federatedcompute.services.data.FederatedComputeEncryptionKey;
import com.android.federatedcompute.services.encryption.Encrypter;
import com.android.federatedcompute.services.http.HttpClientUtil.FederatedComputePayloadDataContract;
-import com.android.federatedcompute.services.http.HttpClientUtil.HttpMethod;
import com.android.federatedcompute.services.security.AuthorizationContext;
import com.android.federatedcompute.services.training.util.ComputationResult;
+import com.android.odp.module.common.HttpClient;
+import com.android.odp.module.common.HttpClientUtils;
+import com.android.odp.module.common.OdpHttpRequest;
+import com.android.odp.module.common.OdpHttpResponse;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
@@ -59,23 +63,25 @@
import com.google.internal.federatedcompute.v1.ClientVersion;
import com.google.internal.federatedcompute.v1.RejectionInfo;
import com.google.internal.federatedcompute.v1.Resource;
-import com.google.internal.federatedcompute.v1.ResourceCapabilities;
import com.google.internal.federatedcompute.v1.ResourceCompressionFormat;
+import com.google.internal.federatedcompute.v1.UploadInstruction;
import com.google.ondevicepersonalization.federatedcompute.proto.CreateTaskAssignmentRequest;
import com.google.ondevicepersonalization.federatedcompute.proto.CreateTaskAssignmentResponse;
import com.google.ondevicepersonalization.federatedcompute.proto.ReportResultRequest;
import com.google.ondevicepersonalization.federatedcompute.proto.ReportResultRequest.Result;
import com.google.ondevicepersonalization.federatedcompute.proto.ReportResultResponse;
import com.google.ondevicepersonalization.federatedcompute.proto.TaskAssignment;
-import com.google.ondevicepersonalization.federatedcompute.proto.UploadInstruction;
import com.google.protobuf.InvalidProtocolBufferException;
import org.json.JSONObject;
+import java.io.BufferedInputStream;
+import java.io.FileInputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.Callable;
+import java.util.zip.GZIPInputStream;
/** Implements a single session of HTTP-based federated compute protocol. */
public final class HttpFederatedProtocol {
@@ -117,7 +123,8 @@
entryUri,
clientVersion,
populationName,
- new HttpClient(),
+ new HttpClient(
+ FlagsFactory.getFlags().getHttpRequestRetryLimit(), getBlockingExecutor()),
encrypter,
trainingEventLogger);
}
@@ -138,14 +145,14 @@
getLightweightExecutor());
}
- /** Donwloads model checkpoint and federated compute plan from remote server. */
+ /** Downloads model checkpoint and federated compute plan from remote server. */
public ListenableFuture<CheckinResult> downloadTaskAssignment(TaskAssignment taskAssignment) {
NetworkStats networkStats = new NetworkStats();
networkStats.recordStartTimeNow();
- ListenableFuture<FederatedComputeHttpResponse> planDataResponseFuture =
+ ListenableFuture<OdpHttpResponse> planDataResponseFuture =
fetchTaskResource(taskAssignment.getPlan(), networkStats);
- ListenableFuture<FederatedComputeHttpResponse> checkpointDataResponseFuture =
- fetchTaskResource(taskAssignment.getInitCheckpoint(), networkStats);
+ ListenableFuture<OdpHttpResponse> checkpointDataResponseFuture =
+ fetchTaskResource(taskAssignment.getInitCheckpoint(), networkStats, true);
return Futures.whenAllSucceed(planDataResponseFuture, checkpointDataResponseFuture)
.call(
new Callable<CheckinResult>() {
@@ -161,7 +168,7 @@
getBackgroundExecutor());
}
- /** Helper functions to reporting result and upload result. */
+ /** Helper functions to report and upload result. */
public FluentFuture<RejectionInfo> reportResult(
ComputationResult computationResult,
FederatedComputeEncryptionKey encryptionKey,
@@ -194,7 +201,6 @@
return Futures.immediateFuture(
reportResultResponse.getRejectionInfo());
}
- // TODO (b/328789639): add a event to track ReportResult success.
NetworkStats uploadStats = new NetworkStats();
return FluentFuture.from(
processReportResultResponseAndUploadResult(
@@ -234,9 +240,7 @@
}
private CreateTaskAssignmentResponse processCreateTaskAssignmentResponse(
- AuthorizationContext authContext,
- FederatedComputeHttpResponse response,
- NetworkStats networkStats) {
+ AuthorizationContext authContext, OdpHttpResponse response, NetworkStats networkStats) {
networkStats.recordEndTimeNow();
if (authContext.isFirstAuthTry()) {
validateHttpResponseAllowAuthStatus("Start task assignment", response);
@@ -257,7 +261,8 @@
throw new IllegalStateException("Could not parse StartTaskAssignmentResponse proto", e);
}
if (taskAssignmentResponse.hasRejectionInfo()) {
- mTrainingEventLogger.logCheckinRejected(networkStats);
+ mTrainingEventLogger.logCheckinRejected(
+ taskAssignmentResponse.getRejectionInfo(), networkStats);
return taskAssignmentResponse;
}
TaskAssignment taskAssignment = getTaskAssignment(taskAssignmentResponse);
@@ -268,18 +273,14 @@
return taskAssignmentResponse;
}
- private ListenableFuture<FederatedComputeHttpResponse> createTaskAssignment(
+ private ListenableFuture<OdpHttpResponse> createTaskAssignment(
AuthorizationContext authContext, NetworkStats networkStats) {
CreateTaskAssignmentRequest request =
CreateTaskAssignmentRequest.newBuilder()
.setClientVersion(
ClientVersion.newBuilder()
.setVersionCode(String.valueOf(mClientVersion)))
- .setResourceCapabilities(
- ResourceCapabilities.newBuilder()
- .addSupportedCompressionFormats(
- ResourceCompressionFormat
- .RESOURCE_COMPRESSION_FORMAT_GZIP))
+ .setResourceCapabilities(HttpClientUtils.getResourceCapabilities())
.build();
String taskAssignmentUriSuffix =
@@ -291,10 +292,10 @@
headers.put(ODP_IDEMPOTENCY_KEY, System.currentTimeMillis() + " - " + UUID.randomUUID());
headers.put(
FCP_OWNER_ID_DIGEST, authContext.getOwnerId() + "-" + authContext.getOwnerCert());
- FederatedComputeHttpRequest httpRequest =
+ OdpHttpRequest httpRequest =
mTaskAssignmentRequestCreator.createProtoRequest(
taskAssignmentUriSuffix,
- HttpMethod.POST,
+ HttpClientUtils.HttpMethod.POST,
headers,
request.toByteArray(),
/* isProtobufEncoded= */ true);
@@ -339,15 +340,14 @@
}
private CheckinResult getCheckinResult(
- ListenableFuture<FederatedComputeHttpResponse> planDataResponseFuture,
- ListenableFuture<FederatedComputeHttpResponse> checkpointDataResponseFuture,
+ ListenableFuture<OdpHttpResponse> planDataResponseFuture,
+ ListenableFuture<OdpHttpResponse> checkpointDataResponseFuture,
TaskAssignment taskAssignment,
NetworkStats networkStats)
throws Exception {
networkStats.recordEndTimeNow();
- FederatedComputeHttpResponse planDataResponse = Futures.getDone(planDataResponseFuture);
- FederatedComputeHttpResponse checkpointDataResponse =
- Futures.getDone(checkpointDataResponseFuture);
+ OdpHttpResponse planDataResponse = Futures.getDone(planDataResponseFuture);
+ OdpHttpResponse checkpointDataResponse = Futures.getDone(checkpointDataResponseFuture);
validateHttpResponseStatus("Fetch plan", planDataResponse);
validateHttpResponseStatus("Fetch checkpoint", checkpointDataResponse);
networkStats.addBytesDownloaded(getTotalReceivedBytes(planDataResponse));
@@ -367,22 +367,42 @@
mTrainingEventLogger.logCheckinInvalidPayload(networkStats);
throw new IllegalStateException("Could not parse ClientOnlyPlan proto", e);
}
- mTrainingEventLogger.logCheckinFinished(networkStats);
-
- // Process download checkpoint resource.
- String inputCheckpointFile = createTempFile("input", ".ckp");
- byte[] checkpointData = checkpointDataResponse.getPayload();
- if (taskAssignment.getInitCheckpoint().getCompressionFormat()
- == ResourceCompressionFormat.RESOURCE_COMPRESSION_FORMAT_GZIP
- || checkpointDataResponse.isResponseCompressed()) {
- checkpointData = uncompressWithGzip(checkpointData);
+ if (checkpointDataResponse.getPayloadFileName() == null) {
+ Trace.endAsyncSection(TRACE_HTTP_ISSUE_CHECKIN, 0);
+ mTrainingEventLogger.logCheckinInvalidPayload(networkStats);
+ return null;
}
- writeToFile(inputCheckpointFile, checkpointData);
+
+ // Process downloaded checkpoint resource.
+ String payloadFileName = checkpointDataResponse.getPayloadFileName();
+ long checkpointFileSize = checkpointDataResponse.getDownloadedPayloadSize();
+ if (checkpointDataResponse.isResponseCompressed()) {
+ String checkpointFile = createTempFile("input", ".ckp");
+ checkpointFileSize =
+ writeToFile(
+ checkpointFile,
+ new GZIPInputStream(
+ new BufferedInputStream(new FileInputStream(payloadFileName))));
+ LogUtil.d(TAG, "Uncompressed checkpoint data file size: %d", checkpointFileSize);
+ payloadFileName = checkpointFile;
+ }
+ if (checkpointFileSize > FlagsFactory.getFlags().getFcpCheckpointFileSizeLimit()) {
+ LogUtil.e(
+ TAG,
+ "CheckPoint data is too large: %d, which more than a limit: %d",
+ checkpointFileSize,
+ FlagsFactory.getFlags().getFcpCheckpointFileSizeLimit());
+ Trace.endAsyncSection(TRACE_HTTP_ISSUE_CHECKIN, 0);
+ mTrainingEventLogger.logCheckinInvalidPayload(networkStats);
+ return null;
+ }
+
+ mTrainingEventLogger.logCheckinFinished(networkStats);
Trace.endAsyncSection(TRACE_HTTP_ISSUE_CHECKIN, 0);
- return new CheckinResult(inputCheckpointFile, clientOnlyPlan, taskAssignment);
+ return new CheckinResult(payloadFileName, clientOnlyPlan, taskAssignment);
}
- private ListenableFuture<FederatedComputeHttpResponse> performReportResult(
+ private ListenableFuture<OdpHttpResponse> performReportResult(
ComputationResult computationResult,
AuthorizationContext authContext,
NetworkStats networkStats) {
@@ -396,11 +416,7 @@
ReportResultRequest startDataUploadRequest =
ReportResultRequest.newBuilder()
.setResult(result)
- .setResourceCapabilities(
- ResourceCapabilities.newBuilder()
- .addSupportedCompressionFormats(
- ResourceCompressionFormat
- .RESOURCE_COMPRESSION_FORMAT_GZIP))
+ .setResourceCapabilities(HttpClientUtils.getResourceCapabilities())
.build();
String startDataUploadUri =
String.format(
@@ -416,10 +432,10 @@
mAssignmentId,
result.toString());
Map<String, String> headers = authContext.generateAuthHeaders();
- FederatedComputeHttpRequest httpRequest =
+ OdpHttpRequest httpRequest =
mTaskAssignmentRequestCreator.createProtoRequest(
startDataUploadUri,
- HttpMethod.PUT,
+ HttpClientUtils.HttpMethod.PUT,
headers,
startDataUploadRequest.toByteArray(),
/* isProtobufEncoded= */ true);
@@ -427,12 +443,11 @@
return mHttpClient.performRequestAsyncWithRetry(httpRequest);
}
- private ListenableFuture<FederatedComputeHttpResponse>
- processReportResultResponseAndUploadResult(
- ReportResultResponse reportResultResponse,
- ComputationResult computationResult,
- FederatedComputeEncryptionKey encryptionKey,
- NetworkStats networkStats) {
+ private ListenableFuture<OdpHttpResponse> processReportResultResponseAndUploadResult(
+ ReportResultResponse reportResultResponse,
+ ComputationResult computationResult,
+ FederatedComputeEncryptionKey encryptionKey,
+ NetworkStats networkStats) {
try {
Preconditions.checkArgument(
!computationResult.getOutputCheckpointFile().isEmpty(),
@@ -447,15 +462,10 @@
// Apply a top-level compression to the payload.
if (uploadInstruction.getCompressionFormat()
== ResourceCompressionFormat.RESOURCE_COMPRESSION_FORMAT_GZIP) {
- outputBytes = compressWithGzip(outputBytes);
+ outputBytes = HttpClientUtils.compressWithGzip(outputBytes);
}
- HashMap<String, String> requestHeader = new HashMap<>();
- uploadInstruction
- .getExtraRequestHeadersMap()
- .forEach(
- (key, value) -> {
- requestHeader.put(key, value);
- });
+ HashMap<String, String> requestHeader =
+ new HashMap<>(uploadInstruction.getExtraRequestHeadersMap());
LogUtil.d(
TAG,
"Start upload training result: population name %s, task name %s,"
@@ -463,10 +473,10 @@
mPopulationName,
mTaskId,
mAssignmentId);
- FederatedComputeHttpRequest httpUploadRequest =
- FederatedComputeHttpRequest.create(
+ OdpHttpRequest httpUploadRequest =
+ OdpHttpRequest.create(
uploadInstruction.getUploadLocation(),
- HttpMethod.PUT,
+ HttpClientUtils.HttpMethod.PUT,
requestHeader,
outputBytes);
networkStats.recordStartTimeNow();
@@ -506,8 +516,7 @@
return body.toString().getBytes();
}
- private void validateHttpResponseStatus(
- String stage, FederatedComputeHttpResponse httpResponse) {
+ private static void validateHttpResponseStatus(String stage, OdpHttpResponse httpResponse) {
if (!HTTP_OK_STATUS.contains(httpResponse.getStatusCode())) {
throw new IllegalStateException(stage + " failed: " + httpResponse.getStatusCode());
}
@@ -515,8 +524,8 @@
LogUtil.i(TAG, stage + " success.");
}
- private void validateHttpResponseAllowAuthStatus(
- String stage, FederatedComputeHttpResponse httpResponse) {
+ private static void validateHttpResponseAllowAuthStatus(
+ String stage, OdpHttpResponse httpResponse) {
if (!HTTP_OK_OR_UNAUTHENTICATED_STATUS.contains(httpResponse.getStatusCode())) {
throw new IllegalStateException(stage + " failed: " + httpResponse.getStatusCode());
}
@@ -524,28 +533,37 @@
LogUtil.i(TAG, stage + " success.");
}
- private ListenableFuture<FederatedComputeHttpResponse> fetchTaskResource(
+ private ListenableFuture<OdpHttpResponse> fetchTaskResource(
Resource resource, NetworkStats networkStats) {
+ return fetchTaskResource(resource, networkStats, false);
+ }
+
+ private ListenableFuture<OdpHttpResponse> fetchTaskResource(
+ Resource resource, NetworkStats networkStats, boolean payloadIntoFileEnabled) {
switch (resource.getResourceCase()) {
case URI:
Preconditions.checkArgument(
!resource.getUri().isEmpty(), "Resource.uri must be non-empty when set");
HashMap<String, String> headerList = new HashMap<>();
- if (resource.getCompressionFormat()
- == ResourceCompressionFormat.RESOURCE_COMPRESSION_FORMAT_GZIP) {
+ boolean gZipCompressionEnabled = resource.getCompressionFormat()
+ == ResourceCompressionFormat.RESOURCE_COMPRESSION_FORMAT_GZIP;
+ if (gZipCompressionEnabled) {
// Set this header to disable decompressive transcoding when download from
// Google Cloud Storage.
// https://cloud.google.com/storage/docs/transcoding#decompressive_transcoding
headerList.put(ACCEPT_ENCODING_HDR, GZIP_ENCODING_HDR);
}
LogUtil.d(TAG, "start fetch task resources");
- FederatedComputeHttpRequest httpRequest =
- FederatedComputeHttpRequest.create(
+ OdpHttpRequest httpRequest =
+ OdpHttpRequest.create(
resource.getUri(),
- HttpMethod.GET,
+ HttpClientUtils.HttpMethod.GET,
headerList,
HttpClientUtil.EMPTY_BODY);
networkStats.addBytesUploaded(getTotalSentBytes(httpRequest));
+ if (payloadIntoFileEnabled) {
+ return mHttpClient.performRequestIntoFileAsyncWithRetry(httpRequest);
+ }
return mHttpClient.performRequestAsyncWithRetry(httpRequest);
case INLINE_RESOURCE:
return Futures.immediateFailedFuture(
diff --git a/federatedcompute/src/com/android/federatedcompute/services/http/ProtocolRequestCreator.java b/federatedcompute/src/com/android/federatedcompute/services/http/ProtocolRequestCreator.java
index 12062c7..a01b71d 100644
--- a/federatedcompute/src/com/android/federatedcompute/services/http/ProtocolRequestCreator.java
+++ b/federatedcompute/src/com/android/federatedcompute/services/http/ProtocolRequestCreator.java
@@ -16,10 +16,9 @@
package com.android.federatedcompute.services.http;
-import static com.android.federatedcompute.services.http.HttpClientUtil.CONTENT_TYPE_HDR;
-import static com.android.federatedcompute.services.http.HttpClientUtil.PROTOBUF_CONTENT_TYPE;
-
-import com.android.federatedcompute.services.http.HttpClientUtil.HttpMethod;
+import com.android.odp.module.common.HttpClientUtils;
+import com.android.odp.module.common.HttpClientUtils.HttpMethod;
+import com.android.odp.module.common.OdpHttpRequest;
import com.google.internal.federatedcompute.v1.ForwardingInfo;
@@ -27,14 +26,14 @@
import java.util.Map;
/**
- * A helper class to create FederatedComputeHttpRequest with base uri, request headers and
- * compression setting.
+ * A helper class to create {@link OdpHttpRequest} with base uri, request headers and compression
+ * setting for federated compute.
*/
-public final class ProtocolRequestCreator {
+final class ProtocolRequestCreator {
private final String mRequestBaseUri;
private final HashMap<String, String> mHeaderList;
- public ProtocolRequestCreator(String requestBaseUri, HashMap<String, String> headerList) {
+ ProtocolRequestCreator(String requestBaseUri, HashMap<String, String> headerList) {
this.mRequestBaseUri = requestBaseUri;
this.mHeaderList = headerList;
}
@@ -43,7 +42,7 @@
* Creates a {@link ProtocolRequestCreator} based on forwarding info. Validates and extracts the
* base URI for the subsequent requests.
*/
- public static ProtocolRequestCreator create(ForwardingInfo forwardingInfo) {
+ static ProtocolRequestCreator create(ForwardingInfo forwardingInfo) {
if (forwardingInfo.getTargetUriPrefix().isEmpty()) {
throw new IllegalArgumentException("Missing `ForwardingInfo.target_uri_prefix`");
}
@@ -52,18 +51,15 @@
return new ProtocolRequestCreator(forwardingInfo.getTargetUriPrefix(), extraHeaders);
}
- /** Creates a {@link FederatedComputeHttpRequest} with base uri and compression setting. */
- public FederatedComputeHttpRequest createProtoRequest(
+ /** Creates a {@link OdpHttpRequest} with base uri and compression setting. */
+ OdpHttpRequest createProtoRequest(
String uri, HttpMethod httpMethod, byte[] requestBody, boolean isProtobufEncoded) {
HashMap<String, String> extraHeaders = new HashMap<>();
return createProtoRequest(uri, httpMethod, extraHeaders, requestBody, isProtobufEncoded);
}
- /**
- * Creates a {@link FederatedComputeHttpRequest} with base uri, request headers and compression
- * setting.
- */
- public FederatedComputeHttpRequest createProtoRequest(
+ /** Creates a {@link OdpHttpRequest} with base uri, request headers and compression setting. */
+ OdpHttpRequest createProtoRequest(
String uri,
HttpMethod httpMethod,
Map<String, String> extraHeaders,
@@ -74,24 +70,14 @@
requestHeader.putAll(extraHeaders);
if (isProtobufEncoded && requestBody.length > 0) {
- requestHeader.put(CONTENT_TYPE_HDR, PROTOBUF_CONTENT_TYPE);
+ requestHeader.put(
+ HttpClientUtils.CONTENT_TYPE_HDR, HttpClientUtils.PROTOBUF_CONTENT_TYPE);
}
- return FederatedComputeHttpRequest.create(
- joinBaseUriWithSuffix(mRequestBaseUri, uri),
+ return OdpHttpRequest.create(
+ HttpClientUtils.joinBaseUriWithSuffix(mRequestBaseUri, uri),
httpMethod,
requestHeader,
requestBody);
}
- private String joinBaseUriWithSuffix(String baseUri, String suffix) {
- if (suffix.isEmpty() || !suffix.startsWith("/")) {
- throw new IllegalArgumentException("uri_suffix be empty or must have a leading '/'");
- }
-
- if (baseUri.endsWith("/")) {
- baseUri = baseUri.substring(0, baseUri.length() - 1);
- }
- suffix = suffix.substring(1);
- return String.join("/", baseUri, suffix);
- }
}
diff --git a/federatedcompute/src/com/android/federatedcompute/services/scheduling/JobSchedulerHelper.java b/federatedcompute/src/com/android/federatedcompute/services/scheduling/JobSchedulerHelper.java
index 5c89557..a49966d 100644
--- a/federatedcompute/src/com/android/federatedcompute/services/scheduling/JobSchedulerHelper.java
+++ b/federatedcompute/src/com/android/federatedcompute/services/scheduling/JobSchedulerHelper.java
@@ -93,7 +93,10 @@
jobInfo.setRequiresDeviceIdle(task.getTrainingConstraints().requiresSchedulerIdle())
.setRequiresBatteryNotLow(
task.getTrainingConstraints().requiresSchedulerBatteryNotLow())
- .setMinimumLatency(task.earliestNextRunTime() - nowMillis)
+ .setMinimumLatency(
+ (task.earliestNextRunTime() - nowMillis) > 0
+ ? (task.earliestNextRunTime() - nowMillis)
+ : 0)
.setPersisted(true);
jobInfo.setRequiredNetworkType(
diff --git a/federatedcompute/src/com/android/federatedcompute/services/security/AuthorizationContext.java b/federatedcompute/src/com/android/federatedcompute/services/security/AuthorizationContext.java
index 6a346fd..c2e072d 100644
--- a/federatedcompute/src/com/android/federatedcompute/services/security/AuthorizationContext.java
+++ b/federatedcompute/src/com/android/federatedcompute/services/security/AuthorizationContext.java
@@ -172,8 +172,9 @@
AuthTokenCallbackResult callbackResult =
authTokenBlockingQueue.poll(
BLOCKING_QUEUE_TIMEOUT_IN_SECONDS, TimeUnit.SECONDS);
- if (!callbackResult.isEmpty()) {
- LogUtil.e(TAG, "checking blocking queue");
+ if (callbackResult.isEmpty()) {
+ LogUtil.e(TAG, "Timed out waiting for blocking queue.");
+ } else {
headers.put(
ODP_AUTHORIZATION_KEY,
callbackResult.getAuthToken().getAuthorizationToken());
@@ -188,7 +189,7 @@
return headers;
}
- private FutureCallback<AuthTokenCallbackResult> createCallbackForBlockingQueue(
+ private static FutureCallback<AuthTokenCallbackResult> createCallbackForBlockingQueue(
BlockingQueue<AuthTokenCallbackResult> authorizationTokenBlockingQueue) {
return new FutureCallback<>() {
@Override
@@ -203,7 +204,7 @@
};
}
- private AuthTokenCallbackResult convertODPAuthToken(ODPAuthorizationToken authToken) {
+ private static AuthTokenCallbackResult convertODPAuthToken(ODPAuthorizationToken authToken) {
return new AuthTokenCallbackResult(authToken, authToken == null);
}
diff --git a/federatedcompute/src/com/android/federatedcompute/services/sharedlibrary/spe/FederatedComputeJobServiceFactory.java b/federatedcompute/src/com/android/federatedcompute/services/sharedlibrary/spe/FederatedComputeJobServiceFactory.java
index 2e1c5c9..29d0ef1 100644
--- a/federatedcompute/src/com/android/federatedcompute/services/sharedlibrary/spe/FederatedComputeJobServiceFactory.java
+++ b/federatedcompute/src/com/android/federatedcompute/services/sharedlibrary/spe/FederatedComputeJobServiceFactory.java
@@ -22,11 +22,11 @@
import android.content.Context;
import com.android.adservices.shared.proto.ModuleJobPolicy;
-import com.android.adservices.shared.proto.ProtoParser;
import com.android.adservices.shared.spe.framework.JobServiceFactory;
import com.android.adservices.shared.spe.framework.JobWorker;
import com.android.adservices.shared.spe.logging.JobSchedulingLogger;
import com.android.adservices.shared.spe.logging.JobServiceLogger;
+import com.android.adservices.shared.util.ProtoParser;
import com.android.federatedcompute.internal.util.LogUtil;
import com.android.federatedcompute.services.common.FederatedComputeExecutors;
import com.android.federatedcompute.services.common.Flags;
@@ -87,6 +87,7 @@
ModuleJobPolicy policy =
ProtoParser.parseBase64EncodedStringToProto(
ModuleJobPolicy.parser(),
+ ClientErrorLogger.getInstance(),
PROTO_PROPERTY_FOR_LOGCAT,
flags.getFcpModuleJobPolicy());
sSingleton =
diff --git a/federatedcompute/src/com/android/federatedcompute/services/training/EligibilityDecider.java b/federatedcompute/src/com/android/federatedcompute/services/training/EligibilityDecider.java
index 302d47d..8a66c3d 100644
--- a/federatedcompute/src/com/android/federatedcompute/services/training/EligibilityDecider.java
+++ b/federatedcompute/src/com/android/federatedcompute/services/training/EligibilityDecider.java
@@ -20,6 +20,9 @@
import static com.android.federatedcompute.services.stats.FederatedComputeStatsLog.FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_ELIGIBILITY_EVAL_COMPUTATION_ELIGIBLE;
import static com.android.federatedcompute.services.stats.FederatedComputeStatsLog.FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_ELIGIBILITY_EVAL_COMPUTATION_ERROR_EXAMPLE_ITERATOR;
import static com.android.federatedcompute.services.stats.FederatedComputeStatsLog.FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_ELIGIBILITY_EVAL_COMPUTATION_STARTED;
+import static com.android.federatedcompute.services.stats.FederatedComputeStatsLog.FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_EXAMPLE_STORE_BIND_ERROR;
+import static com.android.federatedcompute.services.stats.FederatedComputeStatsLog.FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_EXAMPLE_STORE_BIND_START;
+import static com.android.federatedcompute.services.stats.FederatedComputeStatsLog.FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_EXAMPLE_STORE_BIND_SUCCESS;
import android.content.Context;
import android.federatedcompute.aidl.IExampleStoreIterator;
@@ -157,12 +160,16 @@
ExampleSelector exampleSelector) {
try {
long callStartTimeNanos = SystemClock.elapsedRealtimeNanos();
+ logger.logEventKind(
+ FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_EXAMPLE_STORE_BIND_START);
IExampleStoreService exampleStoreService =
mExampleStoreServiceProvider.getExampleStoreService(
task.appPackageName(), context);
if (exampleStoreService == null) {
logger.logEventKind(
FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_ELIGIBILITY_EVAL_COMPUTATION_ERROR_EXAMPLE_ITERATOR);
+ logger.logEventKind(
+ FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_EXAMPLE_STORE_BIND_ERROR);
LogUtil.e(
TAG,
"Failed to compute DataAvailabilityPolicy due to bind ExampleStore"
@@ -172,6 +179,8 @@
taskId);
return false;
}
+ logger.logEventKind(
+ FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_EXAMPLE_STORE_BIND_SUCCESS);
exampleStats.mBindToExampleStoreLatencyNanos.addAndGet(
SystemClock.elapsedRealtimeNanos() - callStartTimeNanos);
callStartTimeNanos = SystemClock.elapsedRealtimeNanos();
@@ -181,7 +190,8 @@
task,
taskId,
dataAvailabilityPolicy.getMinExampleCount(),
- exampleSelector);
+ exampleSelector,
+ logger);
if (iterator == null) {
LogUtil.d(
TAG,
diff --git a/federatedcompute/src/com/android/federatedcompute/services/training/FederatedComputeWorker.java b/federatedcompute/src/com/android/federatedcompute/services/training/FederatedComputeWorker.java
index 1b2a6a1..6c5b0ff 100644
--- a/federatedcompute/src/com/android/federatedcompute/services/training/FederatedComputeWorker.java
+++ b/federatedcompute/src/com/android/federatedcompute/services/training/FederatedComputeWorker.java
@@ -26,11 +26,23 @@
import static com.android.federatedcompute.services.common.Constants.TRACE_WORKER_START_TRAINING_RUN;
import static com.android.federatedcompute.services.common.FederatedComputeExecutors.getBackgroundExecutor;
import static com.android.federatedcompute.services.common.FederatedComputeExecutors.getLightweightExecutor;
-import static com.android.federatedcompute.services.common.FileUtils.createTempFile;
-import static com.android.federatedcompute.services.common.FileUtils.createTempFileDescriptor;
import static com.android.federatedcompute.services.stats.FederatedComputeStatsLog.FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_COMPUTATION_STARTED;
import static com.android.federatedcompute.services.stats.FederatedComputeStatsLog.FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_ELIGIBILITY_EVAL_NOT_CONFIGURED;
+import static com.android.federatedcompute.services.stats.FederatedComputeStatsLog.FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_EXAMPLE_STORE_BIND_ERROR;
+import static com.android.federatedcompute.services.stats.FederatedComputeStatsLog.FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_EXAMPLE_STORE_BIND_START;
+import static com.android.federatedcompute.services.stats.FederatedComputeStatsLog.FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_EXAMPLE_STORE_BIND_SUCCESS;
+import static com.android.federatedcompute.services.stats.FederatedComputeStatsLog.FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_EXAMPLE_STORE_ERROR;
import static com.android.federatedcompute.services.stats.FederatedComputeStatsLog.FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_RUN_COMPLETE;
+import static com.android.federatedcompute.services.stats.FederatedComputeStatsLog.FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_RUN_FAILED_COMPUTATION_FAILED;
+import static com.android.federatedcompute.services.stats.FederatedComputeStatsLog.FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_RUN_FAILED_DOWNLOAD_FAILED;
+import static com.android.federatedcompute.services.stats.FederatedComputeStatsLog.FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_RUN_FAILED_ENCRYPTION_KEY_FETCH_FAILED;
+import static com.android.federatedcompute.services.stats.FederatedComputeStatsLog.FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_RUN_FAILED_NOT_ELIGIBLE;
+import static com.android.federatedcompute.services.stats.FederatedComputeStatsLog.FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_RUN_FAILED_REPORT_FAILED;
+import static com.android.federatedcompute.services.stats.FederatedComputeStatsLog.FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_RUN_FAILED_WITH_EXCEPTION;
+import static com.android.federatedcompute.services.stats.FederatedComputeStatsLog.FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_RUN_FAILED_WITH_REJECTION;
+import static com.android.federatedcompute.services.stats.FederatedComputeStatsLog.FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_RUN_STARTED;
+import static com.android.odp.module.common.FileUtils.createTempFile;
+import static com.android.odp.module.common.FileUtils.createTempFileDescriptor;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -51,7 +63,6 @@
import com.android.federatedcompute.internal.util.LogUtil;
import com.android.federatedcompute.services.common.Constants;
import com.android.federatedcompute.services.common.ExampleStats;
-import com.android.federatedcompute.services.common.FileUtils;
import com.android.federatedcompute.services.common.Flags;
import com.android.federatedcompute.services.common.FlagsFactory;
import com.android.federatedcompute.services.common.TrainingEventLogger;
@@ -79,6 +90,7 @@
import com.android.federatedcompute.services.training.util.TrainingConditionsChecker.Condition;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;
+import com.android.odp.module.common.FileUtils;
import com.android.odp.module.common.PackageUtils;
import com.google.common.annotations.VisibleForTesting;
@@ -130,12 +142,12 @@
private TrainingRun mActiveRun = null;
private HttpFederatedProtocol mHttpFederatedProtocol;
- private ExampleStoreServiceProvider mExampleStoreServiceProvider;
+ private final ExampleStoreServiceProvider mExampleStoreServiceProvider;
private AbstractServiceBinder<IIsolatedTrainingService> mIsolatedTrainingServiceBinder;
- private FederatedComputeEncryptionKeyManager mEncryptionKeyManager;
+ private final FederatedComputeEncryptionKeyManager mEncryptionKeyManager;
@VisibleForTesting
- public FederatedComputeWorker(
+ FederatedComputeWorker(
Context context,
FederatedComputeJobManager jobManager,
TrainingConditionsChecker trainingConditionsChecker,
@@ -182,6 +194,8 @@
LogUtil.d(TAG, "startTrainingRun() %d", jobId);
TrainingEventLogger trainingEventLogger = mInjector.getTrainingEventLogger();
trainingEventLogger.setClientVersion(PackageUtils.getApexVersion(this.mContext));
+ trainingEventLogger.logEventKind(
+ FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_RUN_STARTED);
return FluentFuture.from(
mInjector
.getBgExecutor()
@@ -351,6 +365,10 @@
if (taskAssignmentOnUnauthenticated.hasRejectionInfo()) {
// This function is called only when the device received
// 401 (unauthenticated). Only retry rejection is allowed.
+ LogUtil.d(
+ TAG, "job %d was rejected during check in, reason %s",
+ run.mTask.jobId(), taskAssignmentOnUnauthenticated
+ .getRejectionInfo().getReason());
if (taskAssignmentOnUnauthenticated
.getRejectionInfo()
.hasRetryWindow()) {
@@ -376,6 +394,8 @@
TrainingRun run,
CreateTaskAssignmentResponse taskAssignmentResponse,
boolean enableFailuresTracking) {
+ run.mTrainingEventLogger.logEventKind(
+ FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_RUN_FAILED_WITH_REJECTION);
performFinishRoutines(
run.mCallback,
ContributionResult.FAIL,
@@ -422,6 +442,8 @@
.flattenToString(),
run.mTask.ownerIdCertDigest()),
run.mTrainingEventLogger);
+ run.mTrainingEventLogger.logEventKind(
+ FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_RUN_FAILED_NOT_ELIGIBLE);
// Reschedule the job.
performFinishRoutines(
run.mCallback,
@@ -441,7 +463,24 @@
mHttpFederatedProtocol.downloadTaskAssignment(
createTaskAssignmentResponse.getTaskAssignment()))
.transformAsync(
- checkinResult -> doFederatedComputation(run, checkinResult, eligibleResult),
+ checkinResult -> {
+ if (checkinResult == null) {
+ LogUtil.w(TAG, "Failed to acquire checkin result!");
+ run.mTrainingEventLogger.logEventKind(
+ FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_RUN_FAILED_DOWNLOAD_FAILED);
+ // Reschedule the job.
+ performFinishRoutines(
+ run.mCallback,
+ ContributionResult.FAIL,
+ run.mTask.jobId(),
+ run.mTask.populationName(),
+ run.mTask.getTrainingIntervalOptions(),
+ /* taskRetry= */ null,
+ /* enableFailuresTracking= */ true);
+ return Futures.immediateFuture(null);
+ }
+ return doFederatedComputation(run, checkinResult, eligibleResult);
+ },
getBackgroundExecutor());
}
@@ -485,7 +524,9 @@
: activeKeys.get(new Random().nextInt(activeKeys.size()));
if (encryptionKey == null) {
// no active keys to encrypt the FL/FA computation results, stop the computation run.
- reportFailureResultToServer(run);
+ run.mTrainingEventLogger.logEventKind(
+ FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_RUN_FAILED_ENCRYPTION_KEY_FETCH_FAILED);
+ reportFailureResultToServer(run, null);
return Futures.immediateFailedFuture(
new IllegalStateException("No active key available on device."));
}
@@ -502,7 +543,9 @@
}
if (iterator == null) {
- reportFailureResultToServer(run);
+ run.mTrainingEventLogger.logEventKind(
+ FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_RUN_FAILED_COMPUTATION_FAILED);
+ reportFailureResultToServer(run, FLRunnerResult.ErrorStatus.EXAMPLE_ITERATOR_ERROR);
return Futures.immediateFailedFuture(
new IllegalStateException(
String.format(
@@ -562,6 +605,11 @@
ComputationResult computationResult =
Futures.getDone(computationResultFuture);
RejectionInfo reportToServer = Futures.getDone(reportToServerFuture);
+ if (computationResult.getFlRunnerResult().getContributionResult()
+ != ContributionResult.SUCCESS) {
+ run.mTrainingEventLogger.logEventKind(
+ FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_RUN_FAILED_COMPUTATION_FAILED);
+ }
// report to Server will hold null in case of success, or rejection info
// in case server answered with rejection
if (reportToServer != null) {
@@ -581,6 +629,13 @@
run.mTaskId,
run.mTask,
failedReportComputationResult);
+ if (computationResult.getFlRunnerResult().getContributionResult()
+ == ContributionResult.SUCCESS) {
+ // do not log failed delivery if, failed computation os already
+ // logged.
+ run.mTrainingEventLogger.logEventKind(
+ FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_RUN_FAILED_REPORT_FAILED);
+ }
performFinishRoutines(
run.mCallback,
ContributionResult.FAIL,
@@ -613,14 +668,15 @@
mInjector.getBgExecutor());
}
- private void reportFailureResultToServer(TrainingRun run) {
+ private void reportFailureResultToServer(
+ TrainingRun run, @Nullable FLRunnerResult.ErrorStatus failureStatus) {
+ FLRunnerResult.Builder runnerResultBuilder =
+ FLRunnerResult.newBuilder().setContributionResult(ContributionResult.FAIL);
+ if (failureStatus != null) {
+ runnerResultBuilder.setErrorStatus(failureStatus);
+ }
ComputationResult failedComputationResult =
- new ComputationResult(
- null,
- FLRunnerResult.newBuilder()
- .setContributionResult(ContributionResult.FAIL)
- .build(),
- null);
+ new ComputationResult(null, runnerResultBuilder.build(), null);
try {
reportFailureResultToServer(
failedComputationResult,
@@ -657,13 +713,18 @@
TrainingRun run, ExampleSelector exampleSelector) {
try {
long startTimeNanos = SystemClock.elapsedRealtimeNanos();
+ run.mTrainingEventLogger.logEventKind(
+ FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_EXAMPLE_STORE_BIND_START);
IExampleStoreService exampleStoreService =
mExampleStoreServiceProvider.getExampleStoreService(
run.mTask.appPackageName(), mContext);
if (exampleStoreService == null) {
- run.mTrainingEventLogger.logComputationExampleIteratorError(new ExampleStats());
+ run.mTrainingEventLogger.logEventKind(
+ FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_EXAMPLE_STORE_BIND_ERROR);
return null;
}
+ run.mTrainingEventLogger.logEventKind(
+ FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_EXAMPLE_STORE_BIND_SUCCESS);
run.mExampleStats.mBindToExampleStoreLatencyNanos.addAndGet(
SystemClock.elapsedRealtimeNanos() - startTimeNanos);
run.mExampleStoreService = exampleStoreService;
@@ -671,12 +732,18 @@
IExampleStoreIterator iterator =
mExampleStoreServiceProvider.getExampleIterator(
- run.mExampleStoreService, run.mTask, run.mTaskId, 0, exampleSelector);
+ run.mExampleStoreService,
+ run.mTask,
+ run.mTaskId,
+ 0,
+ exampleSelector,
+ run.mTrainingEventLogger);
run.mExampleStats.mStartQueryLatencyNanos.addAndGet(
SystemClock.elapsedRealtimeNanos() - startTimeNanos);
return iterator;
} catch (Exception e) {
- run.mTrainingEventLogger.logComputationExampleIteratorError(new ExampleStats());
+ run.mTrainingEventLogger.logEventKind(
+ FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_EXAMPLE_STORE_ERROR);
LogUtil.e(TAG, "StartQuery failure: " + e.getMessage());
return null;
}
@@ -706,6 +773,20 @@
finish(taskRetry, contributionResult, true);
}
+ /** Log that training run failed with exception. */
+ public void logTrainEventFinishedWithException() {
+ synchronized (mLock) {
+ if (mActiveRun == null) {
+ return;
+ }
+ if (mActiveRun.mTrainingEventLogger == null) {
+ return;
+ }
+ mActiveRun.mTrainingEventLogger.logEventKind(
+ FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_RUN_FAILED_WITH_EXCEPTION);
+ }
+ }
+
/**
* Cancel the current running job, schedule recurrent job, unbind from ExampleStoreService and
* ResultHandlingService etc.
@@ -1237,6 +1318,8 @@
.setErrorMessage(throwable.getMessage())
.build(),
null);
+ mLogger.logEventKind(
+ FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_RUN_FAILED_COMPUTATION_FAILED);
reportFailureResultToServer(
failedReportComputationResult, authContext, mLogger);
completer.setException(throwable);
diff --git a/federatedcompute/src/com/android/federatedcompute/services/training/FederatedJobService.java b/federatedcompute/src/com/android/federatedcompute/services/training/FederatedJobService.java
index 76e28f0..dd6c807 100644
--- a/federatedcompute/src/com/android/federatedcompute/services/training/FederatedJobService.java
+++ b/federatedcompute/src/com/android/federatedcompute/services/training/FederatedJobService.java
@@ -69,6 +69,7 @@
public void onFailure(Throwable t) {
LogUtil.e(
TAG, t, "Failed to handle computation job: %d", params.getJobId());
+ worker.logTrainEventFinishedWithException();
worker.finish(null, ContributionResult.FAIL, false);
}
},
diff --git a/federatedcompute/src/com/android/federatedcompute/services/training/IsolatedTrainingServiceImpl.java b/federatedcompute/src/com/android/federatedcompute/services/training/IsolatedTrainingServiceImpl.java
index 87ce290..a493275 100644
--- a/federatedcompute/src/com/android/federatedcompute/services/training/IsolatedTrainingServiceImpl.java
+++ b/federatedcompute/src/com/android/federatedcompute/services/training/IsolatedTrainingServiceImpl.java
@@ -31,12 +31,12 @@
import com.android.federatedcompute.internal.util.LogUtil;
import com.android.federatedcompute.services.common.Constants;
import com.android.federatedcompute.services.common.FederatedComputeExecutors;
-import com.android.federatedcompute.services.common.FileUtils;
import com.android.federatedcompute.services.data.fbs.TrainingFlags;
import com.android.federatedcompute.services.examplestore.ExampleConsumptionRecorder;
import com.android.federatedcompute.services.training.aidl.IIsolatedTrainingService;
import com.android.federatedcompute.services.training.aidl.ITrainingResultCallback;
import com.android.federatedcompute.services.training.util.ListenableSupplier;
+import com.android.odp.module.common.FileUtils;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.util.concurrent.FutureCallback;
diff --git a/federatedcompute/src/com/android/federatedcompute/services/training/util/ComputationResult.java b/federatedcompute/src/com/android/federatedcompute/services/training/util/ComputationResult.java
index 9e39e99..652c9e9 100644
--- a/federatedcompute/src/com/android/federatedcompute/services/training/util/ComputationResult.java
+++ b/federatedcompute/src/com/android/federatedcompute/services/training/util/ComputationResult.java
@@ -63,6 +63,15 @@
if (mFlRunnerResult.getErrorStatus() == FLRunnerResult.ErrorStatus.NOT_ELIGIBLE) {
return Result.NOT_ELIGIBLE;
}
+ if (mFlRunnerResult.getErrorStatus() == FLRunnerResult.ErrorStatus.TENSORFLOW_ERROR) {
+ return Result.FAILED_MODEL_COMPUTATION;
+ }
+ if (mFlRunnerResult.getErrorStatus() == FLRunnerResult.ErrorStatus.INVALID_ARGUMENT) {
+ return Result.FAILED_MODEL_COMPUTATION;
+ }
+ if (mFlRunnerResult.getErrorStatus() == FLRunnerResult.ErrorStatus.EXAMPLE_ITERATOR_ERROR) {
+ return Result.FAILED_EXAMPLE_GENERATION;
+ }
return Result.FAILED;
}
}
diff --git a/flags/Android.bp b/flags/Android.bp
index 0181d7c..4d4b9fd 100644
--- a/flags/Android.bp
+++ b/flags/Android.bp
@@ -24,7 +24,7 @@
aconfig_declarations: "ondevicepersonalization_flags",
defaults: ["framework-minus-apex-aconfig-java-defaults"],
visibility: [
- "//packages/modules/OnDevicePersonalization/framework",
+ "//packages/modules/OnDevicePersonalization:__subpackages__",
],
min_sdk_version: "33",
apex_available: [
diff --git a/flags/ondevicepersonalization_flags.aconfig b/flags/ondevicepersonalization_flags.aconfig
index e808ada..9a00688 100644
--- a/flags/ondevicepersonalization_flags.aconfig
+++ b/flags/ondevicepersonalization_flags.aconfig
@@ -4,15 +4,41 @@
flag {
name: "on_device_personalization_apis_enabled"
is_exported: true
- namespace: "on_device_personalization"
+ namespace: "ondevicepersonalization_aconfig"
# TODO(b/320156647): Add bug number and description
bug: "320156647"
description: "Enter a description per b/320156647"
+ is_fixed_read_only: true
}
flag {
name: "fcp_model_version_enabled"
- namespace: "on_device_personalization"
+ namespace: "ondevicepersonalization_aconfig"
bug: "335080565"
description: "Enable model version support for federated compute"
+ is_fixed_read_only: true
+}
+
+flag {
+ name: "data_class_missing_ctors_and_getters_enabled"
+ namespace: "ondevicepersonalization_aconfig"
+ bug: "353356413"
+ description: "Add missing ctors and getters to certain data classes"
+ is_fixed_read_only: true
+}
+
+flag {
+ name: "execute_in_isolated_service_api_enabled"
+ namespace: "ondevicepersonalization_aconfig"
+ bug: "336801193"
+ description: "Enable executeInIsolatedService API"
+ is_fixed_read_only: true
+}
+
+flag {
+ name: "fcp_schedule_with_outcome_receiver_enabled"
+ namespace: "ondevicepersonalization_aconfig"
+ bug: "343848473"
+ description: "Enable the federated compute schedule API that accepts an OutcomeReceiver."
+ is_fixed_read_only: true
}
diff --git a/framework/Android.bp b/framework/Android.bp
index 3c7db35..c122033 100644
--- a/framework/Android.bp
+++ b/framework/Android.bp
@@ -50,6 +50,7 @@
":framework-ondevicepersonalization-sources",
],
libs: [
+ "app-compat-annotations",
"modules-utils-preconditions",
"framework-connectivity.stubs.module_lib",
"ondevicepersonalization_flags_lib",
diff --git a/framework/api/current.txt b/framework/api/current.txt
index d3ef2f4..8b2ad03 100644
--- a/framework/api/current.txt
+++ b/framework/api/current.txt
@@ -2,6 +2,7 @@
package android.adservices.ondevicepersonalization {
@FlaggedApi("com.android.adservices.ondevicepersonalization.flags.on_device_personalization_apis_enabled") public final class AppInfo implements android.os.Parcelable {
+ ctor @FlaggedApi("com.android.adservices.ondevicepersonalization.flags.data_class_missing_ctors_and_getters_enabled") public AppInfo(boolean);
method public int describeContents();
method @NonNull public boolean isInstalled();
method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -9,6 +10,7 @@
}
@FlaggedApi("com.android.adservices.ondevicepersonalization.flags.on_device_personalization_apis_enabled") public final class DownloadCompletedInput {
+ ctor @FlaggedApi("com.android.adservices.ondevicepersonalization.flags.data_class_missing_ctors_and_getters_enabled") public DownloadCompletedInput(@NonNull android.adservices.ondevicepersonalization.KeyValueStore);
method @NonNull public android.adservices.ondevicepersonalization.KeyValueStore getDownloadedContents();
}
@@ -24,6 +26,7 @@
}
@FlaggedApi("com.android.adservices.ondevicepersonalization.flags.on_device_personalization_apis_enabled") public final class EventInput {
+ ctor @FlaggedApi("com.android.adservices.ondevicepersonalization.flags.data_class_missing_ctors_and_getters_enabled") public EventInput(@Nullable android.adservices.ondevicepersonalization.RequestLogRecord, @NonNull android.os.PersistableBundle);
method @NonNull public android.os.PersistableBundle getParameters();
method @Nullable public android.adservices.ondevicepersonalization.RequestLogRecord getRequestLogRecord();
}
@@ -63,12 +66,43 @@
method @NonNull @WorkerThread public android.net.Uri createEventTrackingUrlWithResponse(@NonNull android.os.PersistableBundle, @Nullable byte[], @Nullable String);
}
+ @FlaggedApi("com.android.adservices.ondevicepersonalization.flags.execute_in_isolated_service_api_enabled") public class ExecuteInIsolatedServiceRequest {
+ method @NonNull public android.os.PersistableBundle getAppParams();
+ method @NonNull public android.adservices.ondevicepersonalization.ExecuteInIsolatedServiceRequest.OutputSpec getOutputSpec();
+ method @NonNull public android.content.ComponentName getService();
+ }
+
+ public static final class ExecuteInIsolatedServiceRequest.Builder {
+ ctor public ExecuteInIsolatedServiceRequest.Builder(@NonNull android.content.ComponentName);
+ method @NonNull public android.adservices.ondevicepersonalization.ExecuteInIsolatedServiceRequest build();
+ method @NonNull public android.adservices.ondevicepersonalization.ExecuteInIsolatedServiceRequest.Builder setAppParams(@NonNull android.os.PersistableBundle);
+ method @NonNull public android.adservices.ondevicepersonalization.ExecuteInIsolatedServiceRequest.Builder setOutputSpec(@NonNull android.adservices.ondevicepersonalization.ExecuteInIsolatedServiceRequest.OutputSpec);
+ }
+
+ public static class ExecuteInIsolatedServiceRequest.OutputSpec {
+ method @NonNull public static android.adservices.ondevicepersonalization.ExecuteInIsolatedServiceRequest.OutputSpec buildBestValueSpec(@IntRange(from=0) int);
+ method @IntRange(from=android.adservices.ondevicepersonalization.ExecuteInIsolatedServiceResponse.DEFAULT_BEST_VALUE) public int getMaxIntValue();
+ method public int getOutputType();
+ field @NonNull public static final android.adservices.ondevicepersonalization.ExecuteInIsolatedServiceRequest.OutputSpec DEFAULT;
+ field public static final int OUTPUT_TYPE_BEST_VALUE = 1; // 0x1
+ field public static final int OUTPUT_TYPE_NULL = 0; // 0x0
+ }
+
+ @FlaggedApi("com.android.adservices.ondevicepersonalization.flags.execute_in_isolated_service_api_enabled") public class ExecuteInIsolatedServiceResponse {
+ ctor public ExecuteInIsolatedServiceResponse(@Nullable android.adservices.ondevicepersonalization.SurfacePackageToken, @IntRange(from=android.adservices.ondevicepersonalization.ExecuteInIsolatedServiceResponse.DEFAULT_BEST_VALUE) int);
+ method @IntRange(from=android.adservices.ondevicepersonalization.ExecuteInIsolatedServiceResponse.DEFAULT_BEST_VALUE) public int getBestValue();
+ method @Nullable public android.adservices.ondevicepersonalization.SurfacePackageToken getSurfacePackageToken();
+ field public static final int DEFAULT_BEST_VALUE = -1; // 0xffffffff
+ }
+
@FlaggedApi("com.android.adservices.ondevicepersonalization.flags.on_device_personalization_apis_enabled") public final class ExecuteInput {
+ ctor @FlaggedApi("com.android.adservices.ondevicepersonalization.flags.data_class_missing_ctors_and_getters_enabled") public ExecuteInput(@NonNull String, @NonNull android.os.PersistableBundle);
method @NonNull public String getAppPackageName();
method @NonNull public android.os.PersistableBundle getAppParams();
}
@FlaggedApi("com.android.adservices.ondevicepersonalization.flags.on_device_personalization_apis_enabled") public final class ExecuteOutput {
+ method @FlaggedApi("com.android.adservices.ondevicepersonalization.flags.execute_in_isolated_service_api_enabled") @IntRange(from=android.adservices.ondevicepersonalization.ExecuteInIsolatedServiceResponse.DEFAULT_BEST_VALUE) public int getBestValue();
method @NonNull public java.util.List<android.adservices.ondevicepersonalization.EventLogRecord> getEventLogRecords();
method @Nullable public byte[] getOutputData();
method @Nullable public android.adservices.ondevicepersonalization.RenderingConfig getRenderingConfig();
@@ -79,6 +113,7 @@
ctor public ExecuteOutput.Builder();
method @NonNull public android.adservices.ondevicepersonalization.ExecuteOutput.Builder addEventLogRecord(@NonNull android.adservices.ondevicepersonalization.EventLogRecord);
method @NonNull public android.adservices.ondevicepersonalization.ExecuteOutput build();
+ method @FlaggedApi("com.android.adservices.ondevicepersonalization.flags.execute_in_isolated_service_api_enabled") @NonNull public android.adservices.ondevicepersonalization.ExecuteOutput.Builder setBestValue(@IntRange(from=0) int);
method @NonNull public android.adservices.ondevicepersonalization.ExecuteOutput.Builder setEventLogRecords(@NonNull java.util.List<android.adservices.ondevicepersonalization.EventLogRecord>);
method @NonNull public android.adservices.ondevicepersonalization.ExecuteOutput.Builder setOutputData(@Nullable byte...);
method @NonNull public android.adservices.ondevicepersonalization.ExecuteOutput.Builder setRenderingConfig(@Nullable android.adservices.ondevicepersonalization.RenderingConfig);
@@ -95,9 +130,21 @@
method @NonNull public android.adservices.ondevicepersonalization.FederatedComputeInput.Builder setPopulationName(@NonNull String);
}
+ @FlaggedApi("com.android.adservices.ondevicepersonalization.flags.fcp_schedule_with_outcome_receiver_enabled") public final class FederatedComputeScheduleRequest {
+ ctor public FederatedComputeScheduleRequest(@NonNull android.adservices.ondevicepersonalization.FederatedComputeScheduler.Params, @NonNull String);
+ method @NonNull public android.adservices.ondevicepersonalization.FederatedComputeScheduler.Params getParams();
+ method @NonNull public String getPopulationName();
+ }
+
+ @FlaggedApi("com.android.adservices.ondevicepersonalization.flags.fcp_schedule_with_outcome_receiver_enabled") public final class FederatedComputeScheduleResponse {
+ ctor public FederatedComputeScheduleResponse(@NonNull android.adservices.ondevicepersonalization.FederatedComputeScheduleRequest);
+ method @NonNull public android.adservices.ondevicepersonalization.FederatedComputeScheduleRequest getFederatedComputeScheduleRequest();
+ }
+
@FlaggedApi("com.android.adservices.ondevicepersonalization.flags.on_device_personalization_apis_enabled") public class FederatedComputeScheduler {
method @WorkerThread public void cancel(@NonNull android.adservices.ondevicepersonalization.FederatedComputeInput);
method @WorkerThread public void schedule(@NonNull android.adservices.ondevicepersonalization.FederatedComputeScheduler.Params, @NonNull android.adservices.ondevicepersonalization.FederatedComputeInput);
+ method @FlaggedApi("com.android.adservices.ondevicepersonalization.flags.fcp_schedule_with_outcome_receiver_enabled") @WorkerThread public void schedule(@NonNull android.adservices.ondevicepersonalization.FederatedComputeScheduleRequest, @NonNull android.os.OutcomeReceiver<android.adservices.ondevicepersonalization.FederatedComputeScheduleResponse,java.lang.Exception>);
}
public static class FederatedComputeScheduler.Params {
@@ -166,7 +213,10 @@
}
@FlaggedApi("com.android.adservices.ondevicepersonalization.flags.on_device_personalization_apis_enabled") public final class IsolatedServiceException extends java.lang.Exception {
- ctor public IsolatedServiceException(@IntRange(from=1, to=127) int);
+ ctor public IsolatedServiceException(int);
+ ctor @FlaggedApi("com.android.adservices.ondevicepersonalization.flags.data_class_missing_ctors_and_getters_enabled") public IsolatedServiceException(int, @Nullable Throwable);
+ ctor @FlaggedApi("com.android.adservices.ondevicepersonalization.flags.data_class_missing_ctors_and_getters_enabled") public IsolatedServiceException(int, @Nullable String, @Nullable Throwable);
+ method @FlaggedApi("com.android.adservices.ondevicepersonalization.flags.data_class_missing_ctors_and_getters_enabled") public int getErrorCode();
}
@FlaggedApi("com.android.adservices.ondevicepersonalization.flags.on_device_personalization_apis_enabled") public interface IsolatedWorker {
@@ -199,12 +249,20 @@
@FlaggedApi("com.android.adservices.ondevicepersonalization.flags.on_device_personalization_apis_enabled") public class OnDevicePersonalizationException extends java.lang.Exception {
method public int getErrorCode();
+ field @FlaggedApi("com.android.adservices.ondevicepersonalization.flags.execute_in_isolated_service_api_enabled") public static final int ERROR_INFERENCE_FAILED = 9; // 0x9
+ field @FlaggedApi("com.android.adservices.ondevicepersonalization.flags.execute_in_isolated_service_api_enabled") public static final int ERROR_INFERENCE_MODEL_NOT_FOUND = 8; // 0x8
+ field @FlaggedApi("com.android.adservices.ondevicepersonalization.flags.execute_in_isolated_service_api_enabled") public static final int ERROR_INVALID_TRAINING_MANIFEST = 7; // 0x7
field public static final int ERROR_ISOLATED_SERVICE_FAILED = 1; // 0x1
+ field @FlaggedApi("com.android.adservices.ondevicepersonalization.flags.execute_in_isolated_service_api_enabled") public static final int ERROR_ISOLATED_SERVICE_LOADING_FAILED = 3; // 0x3
+ field @FlaggedApi("com.android.adservices.ondevicepersonalization.flags.execute_in_isolated_service_api_enabled") public static final int ERROR_ISOLATED_SERVICE_MANIFEST_PARSING_FAILED = 4; // 0x4
+ field @FlaggedApi("com.android.adservices.ondevicepersonalization.flags.execute_in_isolated_service_api_enabled") public static final int ERROR_ISOLATED_SERVICE_TIMEOUT = 5; // 0x5
field public static final int ERROR_PERSONALIZATION_DISABLED = 2; // 0x2
+ field @FlaggedApi("com.android.adservices.ondevicepersonalization.flags.execute_in_isolated_service_api_enabled") public static final int ERROR_SCHEDULE_TRAINING_FAILED = 6; // 0x6
}
@FlaggedApi("com.android.adservices.ondevicepersonalization.flags.on_device_personalization_apis_enabled") public class OnDevicePersonalizationManager {
method public void execute(@NonNull android.content.ComponentName, @NonNull android.os.PersistableBundle, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.adservices.ondevicepersonalization.OnDevicePersonalizationManager.ExecuteResult,java.lang.Exception>);
+ method @FlaggedApi("com.android.adservices.ondevicepersonalization.flags.execute_in_isolated_service_api_enabled") public void executeInIsolatedService(@NonNull android.adservices.ondevicepersonalization.ExecuteInIsolatedServiceRequest, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.adservices.ondevicepersonalization.ExecuteInIsolatedServiceResponse,java.lang.Exception>);
method public void requestSurfacePackage(@NonNull android.adservices.ondevicepersonalization.SurfacePackageToken, @NonNull android.os.IBinder, int, int, int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.view.SurfaceControlViewHost.SurfacePackage,java.lang.Exception>);
}
@@ -214,6 +272,7 @@
}
@FlaggedApi("com.android.adservices.ondevicepersonalization.flags.on_device_personalization_apis_enabled") public final class RenderInput {
+ ctor @FlaggedApi("com.android.adservices.ondevicepersonalization.flags.data_class_missing_ctors_and_getters_enabled") public RenderInput(int, int, @Nullable android.adservices.ondevicepersonalization.RenderingConfig);
method public int getHeight();
method @Nullable public android.adservices.ondevicepersonalization.RenderingConfig getRenderingConfig();
method public int getWidth();
@@ -284,6 +343,7 @@
}
@FlaggedApi("com.android.adservices.ondevicepersonalization.flags.on_device_personalization_apis_enabled") public final class TrainingExamplesInput {
+ ctor @FlaggedApi("com.android.adservices.ondevicepersonalization.flags.data_class_missing_ctors_and_getters_enabled") public TrainingExamplesInput(@NonNull String, @NonNull String, @Nullable byte[], @Nullable String);
method @FlaggedApi("com.android.adservices.ondevicepersonalization.flags.fcp_model_version_enabled") @Nullable public String getCollectionName();
method @NonNull public String getPopulationName();
method @Nullable public byte[] getResumptionToken();
@@ -330,6 +390,7 @@
}
@FlaggedApi("com.android.adservices.ondevicepersonalization.flags.on_device_personalization_apis_enabled") public final class WebTriggerInput {
+ ctor @FlaggedApi("com.android.adservices.ondevicepersonalization.flags.data_class_missing_ctors_and_getters_enabled") public WebTriggerInput(@NonNull android.net.Uri, @NonNull String, @NonNull byte[]);
method @NonNull public String getAppPackageName();
method @NonNull public byte[] getData();
method @NonNull public android.net.Uri getDestinationUrl();
diff --git a/framework/java/android/adservices/ondevicepersonalization/AppInfo.java b/framework/java/android/adservices/ondevicepersonalization/AppInfo.java
index 57ce7f9..4d462b8 100644
--- a/framework/java/android/adservices/ondevicepersonalization/AppInfo.java
+++ b/framework/java/android/adservices/ondevicepersonalization/AppInfo.java
@@ -32,9 +32,17 @@
@DataClass(genHiddenBuilder = true, genEqualsHashCode = true)
public final class AppInfo implements Parcelable {
/** Whether the app is installed. */
- @NonNull boolean mInstalled = false;
+ private boolean mInstalled = false;
-
+ /**
+ * Creates a new AppInfo.
+ *
+ * @param installed {@code true} if the app is installed.
+ */
+ @FlaggedApi(Flags.FLAG_DATA_CLASS_MISSING_CTORS_AND_GETTERS_ENABLED)
+ public AppInfo(boolean installed) {
+ this.mInstalled = installed;
+ }
// Code below generated by codegen v1.0.23.
//
@@ -48,17 +56,6 @@
// Settings > Editor > Code Style > Formatter Control
//@formatter:off
-
- @DataClass.Generated.Member
- /* package-private */ AppInfo(
- @NonNull boolean installed) {
- this.mInstalled = installed;
- AnnotationValidations.validate(
- NonNull.class, null, mInstalled);
-
- // onConstructed(); // You can define this method to get a callback
- }
-
/**
* Whether the app is installed.
*/
diff --git a/framework/java/android/adservices/ondevicepersonalization/Constants.java b/framework/java/android/adservices/ondevicepersonalization/Constants.java
index f9aae84..09ad701 100644
--- a/framework/java/android/adservices/ondevicepersonalization/Constants.java
+++ b/framework/java/android/adservices/ondevicepersonalization/Constants.java
@@ -30,9 +30,42 @@
public static final int STATUS_NAME_NOT_FOUND = 101;
public static final int STATUS_CLASS_NOT_FOUND = 102;
public static final int STATUS_SERVICE_FAILED = 103;
+
+ /**
+ * Internal code that tracks user privacy is not eligible to run operation. DO NOT expose this
+ * status externally.
+ */
public static final int STATUS_PERSONALIZATION_DISABLED = 104;
+
public static final int STATUS_KEY_NOT_FOUND = 105;
+ /** Internal error code that tracks failure to read ODP manifest settings. */
+ public static final int STATUS_MANIFEST_PARSING_FAILED = 106;
+
+ /** Internal error code that tracks misconfigured ODP manifest settings. */
+ public static final int STATUS_MANIFEST_MISCONFIGURED = 107;
+
+ /** Internal error code that tracks errors in loading the Isolated Service. */
+ public static final int STATUS_ISOLATED_SERVICE_LOADING_FAILED = 108;
+
+ /** Internal error code that tracks error when Isolated Service times out. */
+ public static final int STATUS_ISOLATED_SERVICE_TIMEOUT = 109;
+
+ /** Internal error code that tracks error when the FCP manifest is invalid or missing. */
+ public static final int STATUS_FCP_MANIFEST_INVALID = 110;
+
+ /** Internal code that tracks empty result returned from data storage. */
+ public static final int STATUS_SUCCESS_EMPTY_RESULT = 111;
+
+ /** Internal code that tracks timeout exception when run operation. */
+ public static final int STATUS_TIMEOUT = 112;
+
+ /** Internal code that tracks remote exception when run operation. */
+ public static final int STATUS_REMOTE_EXCEPTION = 113;
+ /** Internal code that tracks method not found. */
+ public static final int STATUS_METHOD_NOT_FOUND = 114;
+ public static final int STATUS_CALLER_NOT_ALLOWED = 115;
+
// Operations implemented by IsolatedService.
public static final int OP_EXECUTE = 1;
public static final int OP_DOWNLOAD = 2;
@@ -64,6 +97,8 @@
public static final String EXTRA_MIME_TYPE = "android.ondevicepersonalization.extra.mime_type";
public static final String EXTRA_OUTPUT_DATA =
"android.ondevicepersonalization.extra.output_data";
+ public static final String EXTRA_OUTPUT_BEST_VALUE =
+ "android.ondevicepersonalization.extra.output_best_value";
public static final String EXTRA_RESPONSE_DATA =
"android.ondevicepersonalization.extra.response_data";
public static final String EXTRA_RESULT = "android.ondevicepersonalization.extra.result";
@@ -105,6 +140,7 @@
public static final int API_NAME_MODEL_MANAGER_RUN = 20;
public static final int API_NAME_FEDERATED_COMPUTE_CANCEL = 21;
public static final int API_NAME_NOTIFY_MEASUREMENT_EVENT = 22;
+ public static final int API_NAME_ADSERVICES_GET_COMMON_STATES = 23;
// Data Access Service operations.
public static final int DATA_ACCESS_OP_REMOTE_DATA_LOOKUP = 1;
diff --git a/framework/java/android/adservices/ondevicepersonalization/DownloadCompletedInput.java b/framework/java/android/adservices/ondevicepersonalization/DownloadCompletedInput.java
index 147d2da..05714b0 100644
--- a/framework/java/android/adservices/ondevicepersonalization/DownloadCompletedInput.java
+++ b/framework/java/android/adservices/ondevicepersonalization/DownloadCompletedInput.java
@@ -21,8 +21,8 @@
import android.annotation.Nullable;
import com.android.adservices.ondevicepersonalization.flags.Flags;
-import com.android.ondevicepersonalization.internal.util.AnnotationValidations;
-import com.android.ondevicepersonalization.internal.util.DataClass;
+
+import java.util.Objects;
/**
* The input data for {@link
@@ -30,7 +30,6 @@
*
*/
@FlaggedApi(Flags.FLAG_ON_DEVICE_PERSONALIZATION_APIS_ENABLED)
-@DataClass(genHiddenBuilder = true, genEqualsHashCode = true)
public final class DownloadCompletedInput {
/**
* A {@link KeyValueStore} that contains the downloaded content.
@@ -38,45 +37,25 @@
@NonNull KeyValueStore mDownloadedContents;
-
- // Code below generated by codegen v1.0.23.
- //
- // DO NOT MODIFY!
- // CHECKSTYLE:OFF Generated code
- //
- // To regenerate run:
- // $ codegen $ANDROID_BUILD_TOP/packages/modules/OnDevicePersonalization/framework/java/android/adservices/ondevicepersonalization/DownloadCompletedInput.java
- //
- // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
- // Settings > Editor > Code Style > Formatter Control
- //@formatter:off
-
-
- @DataClass.Generated.Member
- /* package-private */ DownloadCompletedInput(
+ /** Creates a {@link DownloadCompletedInput}
+ *
+ * @param downloadedContents a {@link KeyValueStore} that contains the downloaded contents.
+ */
+ @FlaggedApi(Flags.FLAG_DATA_CLASS_MISSING_CTORS_AND_GETTERS_ENABLED)
+ public DownloadCompletedInput(
@NonNull KeyValueStore downloadedContents) {
- this.mDownloadedContents = downloadedContents;
- AnnotationValidations.validate(
- NonNull.class, null, mDownloadedContents);
-
- // onConstructed(); // You can define this method to get a callback
+ this.mDownloadedContents = Objects.requireNonNull(downloadedContents);
}
/**
* Map containing downloaded keys and values
*/
- @DataClass.Generated.Member
public @NonNull KeyValueStore getDownloadedContents() {
return mDownloadedContents;
}
@Override
- @DataClass.Generated.Member
public boolean equals(@Nullable Object o) {
- // You can override field equality logic by defining either of the methods like:
- // boolean fieldNameEquals(DownloadCompletedInput other) { ... }
- // boolean fieldNameEquals(FieldType otherValue) { ... }
-
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
@SuppressWarnings("unchecked")
@@ -87,83 +66,29 @@
}
@Override
- @DataClass.Generated.Member
public int hashCode() {
- // You can override field hashCode logic by defining methods like:
- // int fieldNameHashCode() { ... }
-
int _hash = 1;
_hash = 31 * _hash + java.util.Objects.hashCode(mDownloadedContents);
return _hash;
}
+ // TODO(b/353356413): Remove builder after it is not used in CTS.
/**
* A builder for {@link DownloadCompletedInput}
* @hide
*/
- @SuppressWarnings("WeakerAccess")
- @DataClass.Generated.Member
public static final class Builder {
-
private @NonNull KeyValueStore mDownloadedContents;
- private long mBuilderFieldsSet = 0L;
-
- public Builder() {
- }
-
- /**
- * Creates a new Builder.
- *
- * @param downloadedContents
- * Map containing downloaded keys and values
- */
public Builder(
@NonNull KeyValueStore downloadedContents) {
mDownloadedContents = downloadedContents;
- AnnotationValidations.validate(
- NonNull.class, null, mDownloadedContents);
}
- /**
- * Map containing downloaded keys and values
- */
- @DataClass.Generated.Member
- public @NonNull Builder setDownloadedContents(@NonNull KeyValueStore value) {
- checkNotUsed();
- mBuilderFieldsSet |= 0x1;
- mDownloadedContents = value;
- return this;
- }
-
- /** Builds the instance. This builder should not be touched after calling this! */
public @NonNull DownloadCompletedInput build() {
- checkNotUsed();
- mBuilderFieldsSet |= 0x2; // Mark builder used
-
DownloadCompletedInput o = new DownloadCompletedInput(
mDownloadedContents);
return o;
}
-
- private void checkNotUsed() {
- if ((mBuilderFieldsSet & 0x2) != 0) {
- throw new IllegalStateException(
- "This Builder should not be reused. Use a new Builder instance instead");
- }
- }
}
-
- @DataClass.Generated(
- time = 1706205792643L,
- codegenVersion = "1.0.23",
- sourceFile = "packages/modules/OnDevicePersonalization/framework/java/android/adservices/ondevicepersonalization/DownloadCompletedInput.java",
- inputSignatures = " @android.annotation.NonNull android.adservices.ondevicepersonalization.KeyValueStore mDownloadedContents\nclass DownloadCompletedInput extends java.lang.Object implements []\[email protected](genHiddenBuilder=true, genEqualsHashCode=true)")
- @Deprecated
- private void __metadata() {}
-
-
- //@formatter:on
- // End of generated code
-
}
diff --git a/framework/java/android/adservices/ondevicepersonalization/EventInput.java b/framework/java/android/adservices/ondevicepersonalization/EventInput.java
index 0ee3f67..f3f0bee 100644
--- a/framework/java/android/adservices/ondevicepersonalization/EventInput.java
+++ b/framework/java/android/adservices/ondevicepersonalization/EventInput.java
@@ -22,15 +22,14 @@
import android.os.PersistableBundle;
import com.android.adservices.ondevicepersonalization.flags.Flags;
-import com.android.ondevicepersonalization.internal.util.AnnotationValidations;
-import com.android.ondevicepersonalization.internal.util.DataClass;
+
+import java.util.Objects;
/**
* The input data for {@link
* IsolatedWorker#onEvent(EventInput, android.os.OutcomeReceiver)}.
*/
@FlaggedApi(Flags.FLAG_ON_DEVICE_PERSONALIZATION_APIS_ENABLED)
-@DataClass(genBuilder = false, genHiddenConstructor = true, genEqualsHashCode = true)
public final class EventInput {
/**
* The {@link RequestLogRecord} that was returned as a result of
@@ -50,21 +49,6 @@
this(parcel.getRequestLogRecord(), parcel.getParameters());
}
-
-
- // Code below generated by codegen v1.0.23.
- //
- // DO NOT MODIFY!
- // CHECKSTYLE:OFF Generated code
- //
- // To regenerate run:
- // $ codegen $ANDROID_BUILD_TOP/packages/modules/OnDevicePersonalization/framework/java/android/adservices/ondevicepersonalization/EventInput.java
- //
- // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
- // Settings > Editor > Code Style > Formatter Control
- //@formatter:off
-
-
/**
* Creates a new EventInput.
*
@@ -75,25 +59,19 @@
* The Event URL parameters that the service passed to {@link
* EventUrlProvider#createEventTrackingUrlWithResponse(PersistableBundle, byte[], String)}
* or {@link EventUrlProvider#createEventTrackingUrlWithRedirect(PersistableBundle, Uri)}.
- * @hide
*/
- @DataClass.Generated.Member
+ @FlaggedApi(Flags.FLAG_DATA_CLASS_MISSING_CTORS_AND_GETTERS_ENABLED)
public EventInput(
@Nullable RequestLogRecord requestLogRecord,
@NonNull PersistableBundle parameters) {
this.mRequestLogRecord = requestLogRecord;
- this.mParameters = parameters;
- AnnotationValidations.validate(
- NonNull.class, null, mParameters);
-
- // onConstructed(); // You can define this method to get a callback
+ this.mParameters = Objects.requireNonNull(parameters);
}
/**
* The {@link RequestLogRecord} that was returned as a result of
* {@link IsolatedWorker#onExecute(ExecuteInput, android.os.OutcomeReceiver)}.
*/
- @DataClass.Generated.Member
public @Nullable RequestLogRecord getRequestLogRecord() {
return mRequestLogRecord;
}
@@ -103,18 +81,12 @@
* EventUrlProvider#createEventTrackingUrlWithResponse(PersistableBundle, byte[], String)}
* or {@link EventUrlProvider#createEventTrackingUrlWithRedirect(PersistableBundle, Uri)}.
*/
- @DataClass.Generated.Member
public @NonNull PersistableBundle getParameters() {
return mParameters;
}
@Override
- @DataClass.Generated.Member
public boolean equals(@Nullable Object o) {
- // You can override field equality logic by defining either of the methods like:
- // boolean fieldNameEquals(EventInput other) { ... }
- // boolean fieldNameEquals(FieldType otherValue) { ... }
-
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
@SuppressWarnings("unchecked")
@@ -126,27 +98,10 @@
}
@Override
- @DataClass.Generated.Member
public int hashCode() {
- // You can override field hashCode logic by defining methods like:
- // int fieldNameHashCode() { ... }
-
int _hash = 1;
_hash = 31 * _hash + java.util.Objects.hashCode(mRequestLogRecord);
_hash = 31 * _hash + java.util.Objects.hashCode(mParameters);
return _hash;
}
-
- @DataClass.Generated(
- time = 1698882321696L,
- codegenVersion = "1.0.23",
- sourceFile = "packages/modules/OnDevicePersonalization/framework/java/android/adservices/ondevicepersonalization/EventInput.java",
- inputSignatures = "private @android.annotation.Nullable android.adservices.ondevicepersonalization.RequestLogRecord mRequestLogRecord\nprivate @android.annotation.NonNull android.os.PersistableBundle mParameters\nclass EventInput extends java.lang.Object implements []\[email protected](genBuilder=false, genHiddenConstructor=true, genEqualsHashCode=true)")
- @Deprecated
- private void __metadata() {}
-
-
- //@formatter:on
- // End of generated code
-
}
diff --git a/framework/java/android/adservices/ondevicepersonalization/ExecuteInIsolatedServiceRequest.java b/framework/java/android/adservices/ondevicepersonalization/ExecuteInIsolatedServiceRequest.java
new file mode 100644
index 0000000..396132e
--- /dev/null
+++ b/framework/java/android/adservices/ondevicepersonalization/ExecuteInIsolatedServiceRequest.java
@@ -0,0 +1,244 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.adservices.ondevicepersonalization;
+
+import static android.adservices.ondevicepersonalization.ExecuteInIsolatedServiceResponse.DEFAULT_BEST_VALUE;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.os.PersistableBundle;
+
+import com.android.adservices.ondevicepersonalization.flags.Flags;
+import com.android.ondevicepersonalization.internal.util.AnnotationValidations;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/** The request of {@link OnDevicePersonalizationManager#executeInIsolatedService}. */
+@FlaggedApi(Flags.FLAG_EXECUTE_IN_ISOLATED_SERVICE_API_ENABLED)
+public class ExecuteInIsolatedServiceRequest {
+ /** The {@link ComponentName} of the {@link IsolatedService}. */
+ @NonNull private ComponentName mService;
+
+ /**
+ * A {@link PersistableBundle} that is passed from the calling app to the {@link
+ * IsolatedService}. The expected contents of this parameter are defined by the {@link
+ * IsolatedService}. The platform does not interpret this parameter.
+ */
+ @NonNull private PersistableBundle mAppParams;
+
+ /**
+ * The set of spec to indicate output of {@link IsolatedService}. It's mainly used by platform.
+ * If {@link OutputSpec} is set to {@link OutputSpec#DEFAULT}, OnDevicePersonalization will
+ * ignore result returned by {@link IsolatedService}. If {@link OutputSpec} is built with {@link
+ * OutputSpec#buildBestValueSpec}, OnDevicePersonalization will verify {@link
+ * ExecuteOutput#getBestValue()} returned by {@link IsolatedService} within the max value range
+ * set in {@link OutputSpec#getMaxIntValue} and add noise.
+ */
+ @NonNull private OutputSpec mOutputSpec;
+
+ /**
+ * The set of spec to indicate output of {@link IsolatedService}. It's mainly used by platform.
+ * If {@link OutputSpec} is set to {@link OutputSpec#DEFAULT}, OnDevicePersonalization will
+ * ignore result returned by {@link IsolatedService}. If {@link OutputSpec} is built with {@link
+ * OutputSpec#buildBestValueSpec}, OnDevicePersonalization will verify {@link
+ * ExecuteOutput#getBestValue()} returned by {@link IsolatedService} within the max value range
+ * set in {@link OutputSpec#getMaxIntValue} and add noise.
+ */
+ public static class OutputSpec {
+ /**
+ * The default value of OutputType. If set, OnDevicePersonalization will ignore result
+ * returned by {@link IsolatedService} and {@link ExecuteInIsolatedServiceResponse} doesn't
+ * return any output data.
+ */
+ public static final int OUTPUT_TYPE_NULL = 0;
+
+ /**
+ * If set, {@link ExecuteInIsolatedServiceResponse#getBestValue()} will return an integer
+ * that indicates the index of best values passed in {@link
+ * ExecuteInIsolatedServiceRequest#getAppParams}.
+ */
+ public static final int OUTPUT_TYPE_BEST_VALUE = 1;
+
+ /** @hide */
+ @IntDef(
+ prefix = "OUTPUT_TYPE_",
+ value = {OUTPUT_TYPE_NULL, OUTPUT_TYPE_BEST_VALUE})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface OutputType {}
+
+ /** Default value is OUTPUT_TYPE_NULL. */
+ @OutputType private final int mOutputType;
+
+ /** Optional. Only set when output option is OUTPUT_TYPE_BEST_VALUE. */
+ @IntRange(from = DEFAULT_BEST_VALUE)
+ private final int mMaxIntValue;
+
+ /** The default value of {@link OutputSpec}. */
+ @NonNull
+ public static final OutputSpec DEFAULT =
+ new OutputSpec(OUTPUT_TYPE_NULL, DEFAULT_BEST_VALUE);
+
+ private OutputSpec(int outputType, int maxIntValue) {
+ mMaxIntValue = maxIntValue;
+ mOutputType = outputType;
+ }
+
+ /**
+ * Creates the output spec to get best value out of {@code maxIntValue}. If set this, caller
+ * can call {@link ExecuteInIsolatedServiceResponse#getBestValue} to get result.
+ *
+ * @param maxIntValue the maximum value {@link IsolatedWorker} can return to caller app.
+ */
+ public @NonNull static OutputSpec buildBestValueSpec(@IntRange(from = 0) int maxIntValue) {
+ AnnotationValidations.validate(IntRange.class, null, maxIntValue, "from", 0);
+ return new OutputSpec(OUTPUT_TYPE_BEST_VALUE, maxIntValue);
+ }
+
+ /**
+ * Returns the output type of {@link IsolatedService}. The default value is {@link
+ * OutputSpec#OUTPUT_TYPE_NULL}.
+ */
+ public @OutputType int getOutputType() {
+ return mOutputType;
+ }
+
+ /**
+ * Returns the value set in {@link OutputSpec#buildBestValueSpec}. The value is expected to
+ * be {@link ExecuteInIsolatedServiceResponse#DEFAULT_BEST_VALUE} if {@link #getOutputType}
+ * is {@link OutputSpec#OUTPUT_TYPE_NULL}.
+ */
+ public @IntRange(from = DEFAULT_BEST_VALUE) int getMaxIntValue() {
+ return mMaxIntValue;
+ }
+ }
+
+ /* package-private */ ExecuteInIsolatedServiceRequest(
+ @NonNull ComponentName service,
+ @NonNull PersistableBundle appParams,
+ @NonNull OutputSpec outputSpec) {
+ Objects.requireNonNull(service);
+ Objects.requireNonNull(appParams);
+ Objects.requireNonNull(outputSpec);
+ this.mService = service;
+ this.mAppParams = appParams;
+ this.mOutputSpec = outputSpec;
+ }
+
+ /** The {@link ComponentName} of the {@link IsolatedService}. */
+ public @NonNull ComponentName getService() {
+ return mService;
+ }
+
+ /**
+ * A {@link PersistableBundle} that is passed from the calling app to the {@link
+ * IsolatedService}. The expected contents of this parameter are defined by the {@link
+ * IsolatedService}. The platform does not interpret this parameter.
+ */
+ public @NonNull PersistableBundle getAppParams() {
+ return mAppParams;
+ }
+
+ /**
+ * The set of spec to indicate output of {@link IsolatedService}. It's mainly used by platform.
+ * For example, platform calls {@link OutputSpec#getOutputType} and validates the result
+ * received from {@link IsolatedService}.
+ */
+ public @NonNull OutputSpec getOutputSpec() {
+ return mOutputSpec;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ // You can override field equality logic by defining either of the methods like:
+ // boolean fieldNameEquals(ExecuteInIsolatedServiceRequest other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ ExecuteInIsolatedServiceRequest that = (ExecuteInIsolatedServiceRequest) o;
+ //noinspection PointlessBooleanExpression
+ return java.util.Objects.equals(mService, that.mService)
+ && java.util.Objects.equals(mAppParams, that.mAppParams)
+ && java.util.Objects.equals(mOutputSpec, that.mOutputSpec);
+ }
+
+ @Override
+ public int hashCode() {
+ // You can override field hashCode logic by defining methods like:
+ // int fieldNameHashCode() { ... }
+
+ int _hash = 1;
+ _hash = 31 * _hash + java.util.Objects.hashCode(mService);
+ _hash = 31 * _hash + java.util.Objects.hashCode(mAppParams);
+ _hash = 31 * _hash + java.util.Objects.hashCode(mOutputSpec);
+ return _hash;
+ }
+
+ /** A builder for {@link ExecuteInIsolatedServiceRequest} */
+ public static final class Builder {
+
+ private @NonNull ComponentName mService;
+ private @NonNull PersistableBundle mAppParams = PersistableBundle.EMPTY;
+ private @NonNull OutputSpec mOutputSpec = OutputSpec.DEFAULT;
+
+ /**
+ * Creates a new Builder.
+ *
+ * @param service The {@link ComponentName} of the {@link IsolatedService}.
+ */
+ public Builder(@NonNull ComponentName service) {
+ Objects.requireNonNull(service);
+ mService = service;
+ }
+
+ /**
+ * A {@link PersistableBundle} that is passed from the calling app to the {@link
+ * IsolatedService}. The expected contents of this parameter are defined by the {@link
+ * IsolatedService}. The platform does not interpret this parameter.
+ */
+ public @NonNull Builder setAppParams(@NonNull PersistableBundle value) {
+ Objects.requireNonNull(value);
+ mAppParams = value;
+ return this;
+ }
+
+ /**
+ * The set of spec to indicate output of {@link IsolatedService}. It's mainly used by
+ * platform. If {@link OutputSpec} is set to {@link OutputSpec#DEFAULT},
+ * OnDevicePersonalization will ignore result returned by {@link IsolatedService}. If {@link
+ * OutputSpec} is built with {@link OutputSpec#buildBestValueSpec}, OnDevicePersonalization
+ * will verify {@link ExecuteOutput#getBestValue()} returned by {@link IsolatedService}
+ * within the max value range set in {@link OutputSpec#getMaxIntValue} and add noise.
+ */
+ public @NonNull Builder setOutputSpec(@NonNull OutputSpec value) {
+ Objects.requireNonNull(value);
+ mOutputSpec = value;
+ return this;
+ }
+
+ /** Builds the instance. */
+ public @NonNull ExecuteInIsolatedServiceRequest build() {
+ return new ExecuteInIsolatedServiceRequest(mService, mAppParams, mOutputSpec);
+ }
+ }
+}
diff --git a/framework/java/android/adservices/ondevicepersonalization/ExecuteInIsolatedServiceResponse.java b/framework/java/android/adservices/ondevicepersonalization/ExecuteInIsolatedServiceResponse.java
new file mode 100644
index 0000000..b528b94
--- /dev/null
+++ b/framework/java/android/adservices/ondevicepersonalization/ExecuteInIsolatedServiceResponse.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.adservices.ondevicepersonalization;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntRange;
+import android.annotation.Nullable;
+
+import com.android.adservices.ondevicepersonalization.flags.Flags;
+import com.android.ondevicepersonalization.internal.util.AnnotationValidations;
+
+/** The response of {@link OnDevicePersonalizationManager#executeInIsolatedService}. */
+@FlaggedApi(Flags.FLAG_EXECUTE_IN_ISOLATED_SERVICE_API_ENABLED)
+public class ExecuteInIsolatedServiceResponse {
+ /**
+ * An opaque reference to content that can be displayed in a {@link android.view.SurfaceView}.
+ * This may be {@code null} if the {@link IsolatedService} has not generated any content to be
+ * displayed within the calling app.
+ */
+ @Nullable private final SurfacePackageToken mSurfacePackageToken;
+
+ /**
+ * The default value of {@link ExecuteInIsolatedServiceResponse#getBestValue} if {@link
+ * IsolatedService} didn't return any content.
+ */
+ public static final int DEFAULT_BEST_VALUE = -1;
+
+ /**
+ * The int value that was returned by the {@link IsolatedService} and applied noise. If {@link
+ * IsolatedService} didn't return any content, the default value is {@link #DEFAULT_BEST_VALUE}.
+ * If {@link IsolatedService} returns an integer value, we will apply the noise to the value and
+ * the range of this value is between 0 and {@link
+ * ExecuteInIsolatedServiceRequest.OutputSpec#getMaxIntValue()}.
+ */
+ private int mBestValue = DEFAULT_BEST_VALUE;
+
+ /**
+ * Creates a new ExecuteInIsolatedServiceResponse.
+ *
+ * @param surfacePackageToken an opaque reference to content that can be displayed in a {@link
+ * android.view.SurfaceView}. This may be {@code null} if the {@link IsolatedService} has
+ * not generated any content to be displayed within the calling app.
+ * @param bestValue an int value that was returned by the {@link IsolatedService} and applied
+ * noise.If {@link ExecuteInIsolatedServiceRequest} output type is set to {@link
+ * ExecuteInIsolatedServiceRequest.OutputSpec#OUTPUT_TYPE_NULL}, the platform ignores the
+ * data returned by {@link IsolatedService} and returns the default value {@link
+ * #DEFAULT_BEST_VALUE}. If {@link ExecuteInIsolatedServiceRequest} output type is set to
+ * {@link ExecuteInIsolatedServiceRequest.OutputSpec#OUTPUT_TYPE_BEST_VALUE}, the platform
+ * validates {@link ExecuteOutput#getBestValue} between 0 and {@link
+ * ExecuteInIsolatedServiceRequest.OutputSpec#getMaxIntValue} and applies noise to result.
+ */
+ public ExecuteInIsolatedServiceResponse(
+ @Nullable SurfacePackageToken surfacePackageToken,
+ @IntRange(from = DEFAULT_BEST_VALUE) int bestValue) {
+ AnnotationValidations.validate(IntRange.class, null, bestValue, "from", DEFAULT_BEST_VALUE);
+ mSurfacePackageToken = surfacePackageToken;
+ mBestValue = bestValue;
+ }
+
+ /** @hide */
+ public ExecuteInIsolatedServiceResponse(@Nullable SurfacePackageToken surfacePackageToken) {
+ mSurfacePackageToken = surfacePackageToken;
+ }
+
+ /**
+ * Returns a {@link SurfacePackageToken}, which is an opaque reference to content that can be
+ * displayed in a {@link android.view.SurfaceView}. This may be {@code null} if the {@link
+ * IsolatedService} has not generated any content to be displayed within the calling app.
+ */
+ @Nullable
+ public SurfacePackageToken getSurfacePackageToken() {
+ return mSurfacePackageToken;
+ }
+
+ /**
+ * Returns the int value that was returned by the {@link IsolatedService} and applied noise. If
+ * {@link ExecuteInIsolatedServiceRequest} output type is set to {@link
+ * ExecuteInIsolatedServiceRequest.OutputSpec#OUTPUT_TYPE_NULL}, the platform ignores the data
+ * returned by {@link IsolatedService} and returns the default value {@link
+ * #DEFAULT_BEST_VALUE}. If {@link ExecuteInIsolatedServiceRequest} output type is set to {@link
+ * ExecuteInIsolatedServiceRequest.OutputSpec#OUTPUT_TYPE_BEST_VALUE}, the platform validates
+ * {@link ExecuteOutput#getBestValue} between 0 and {@link
+ * ExecuteInIsolatedServiceRequest.OutputSpec#getMaxIntValue()} and applies noise to result.
+ */
+ public @IntRange(from = DEFAULT_BEST_VALUE) int getBestValue() {
+ return mBestValue;
+ }
+}
diff --git a/framework/java/android/adservices/ondevicepersonalization/ExecuteInput.java b/framework/java/android/adservices/ondevicepersonalization/ExecuteInput.java
index cda9262..e3c93ea 100644
--- a/framework/java/android/adservices/ondevicepersonalization/ExecuteInput.java
+++ b/framework/java/android/adservices/ondevicepersonalization/ExecuteInput.java
@@ -34,8 +34,8 @@
@FlaggedApi(Flags.FLAG_ON_DEVICE_PERSONALIZATION_APIS_ENABLED)
public final class ExecuteInput {
@NonNull private final String mAppPackageName;
- @Nullable private final ByteArrayParceledSlice mSerializedAppParams;
@NonNull private final Object mAppParamsLock = new Object();
+ @Nullable private ByteArrayParceledSlice mSerializedAppParams;
@NonNull private volatile PersistableBundle mAppParams = null;
/** @hide */
@@ -44,6 +44,18 @@
mSerializedAppParams = parcel.getSerializedAppParams();
}
+ /** Creates an {@link ExecuteInput}.
+ *
+ * @param appPackageName the package name of the calling app.
+ * @param appParams the parameters provided by the app to the {@link IsolatedService}. The
+ * service defines the expected keys in this {@link PersistableBundle}.
+ */
+ @FlaggedApi(Flags.FLAG_DATA_CLASS_MISSING_CTORS_AND_GETTERS_ENABLED)
+ public ExecuteInput(@NonNull String appPackageName, @NonNull PersistableBundle appParams) {
+ mAppPackageName = Objects.requireNonNull(appPackageName);
+ mAppParams = Objects.requireNonNull(appParams);
+ }
+
/**
* The package name of the calling app.
*/
@@ -68,6 +80,7 @@
? PersistableBundleUtils.fromByteArray(
mSerializedAppParams.getByteArray())
: PersistableBundle.EMPTY;
+ mSerializedAppParams = null;
return mAppParams;
} catch (Exception e) {
throw new IllegalStateException(e);
diff --git a/framework/java/android/adservices/ondevicepersonalization/aidl/IOnDevicePersonalizationConfigServiceCallback.aidl b/framework/java/android/adservices/ondevicepersonalization/ExecuteOptionsParcel.aidl
similarity index 62%
rename from framework/java/android/adservices/ondevicepersonalization/aidl/IOnDevicePersonalizationConfigServiceCallback.aidl
rename to framework/java/android/adservices/ondevicepersonalization/ExecuteOptionsParcel.aidl
index 74092b4..89a5ddd 100644
--- a/framework/java/android/adservices/ondevicepersonalization/aidl/IOnDevicePersonalizationConfigServiceCallback.aidl
+++ b/framework/java/android/adservices/ondevicepersonalization/ExecuteOptionsParcel.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,17 +14,6 @@
* limitations under the License.
*/
-package android.adservices.ondevicepersonalization.aidl;
+package android.adservices.ondevicepersonalization;
-import android.os.Bundle;
-
-/**
- * Callback from a OnDevicePersonalizationConfigService.
- * @hide
- */
-oneway interface IOnDevicePersonalizationConfigServiceCallback {
-
- void onSuccess();
-
- void onFailure(int errorCode);
-}
\ No newline at end of file
+parcelable ExecuteOptionsParcel;
diff --git a/framework/java/android/adservices/ondevicepersonalization/ExecuteOptionsParcel.java b/framework/java/android/adservices/ondevicepersonalization/ExecuteOptionsParcel.java
new file mode 100644
index 0000000..e008b42
--- /dev/null
+++ b/framework/java/android/adservices/ondevicepersonalization/ExecuteOptionsParcel.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.adservices.ondevicepersonalization;
+
+import android.annotation.NonNull;
+import android.os.Parcelable;
+
+import com.android.ondevicepersonalization.internal.util.AnnotationValidations;
+import com.android.ondevicepersonalization.internal.util.DataClass;
+
+/** @hide */
+@DataClass(genAidl = false, genBuilder = false)
+public class ExecuteOptionsParcel implements Parcelable {
+ /** Default value is OUTPUT_TYPE_NULL. */
+ @ExecuteInIsolatedServiceRequest.OutputSpec.OutputType private final int mOutputType;
+
+ /** Optional. Only set when output option is OUTPUT_TYPE_BEST_VALUE. */
+ private final int mMaxIntValue;
+
+ public static ExecuteOptionsParcel DEFAULT = new ExecuteOptionsParcel();
+
+ /** @hide */
+ public ExecuteOptionsParcel(@NonNull ExecuteInIsolatedServiceRequest.OutputSpec options) {
+ this(options.getOutputType(), options.getMaxIntValue());
+ }
+
+ /**
+ * Create a default instance of {@link ExecuteOptionsParcel}.
+ *
+ * @hide
+ */
+ private ExecuteOptionsParcel() {
+ this(ExecuteInIsolatedServiceRequest.OutputSpec.OUTPUT_TYPE_NULL, -1);
+ }
+
+ // Code below generated by codegen v1.0.23.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen
+ // $ANDROID_BUILD_TOP/packages/modules/OnDevicePersonalization/framework/java/android/adservices/ondevicepersonalization/ExecuteOptionsParcel.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ // @formatter:off
+
+ /**
+ * Creates a new ExecuteOptionsParcel.
+ *
+ * @param outputType Default value is OUTPUT_TYPE_NULL.
+ * @param maxIntValue Optional. Only set when output option is OUTPUT_TYPE_BEST_VALUE.
+ */
+ @DataClass.Generated.Member
+ public ExecuteOptionsParcel(
+ @ExecuteInIsolatedServiceRequest.OutputSpec.OutputType int outputType,
+ int maxIntValue) {
+ this.mOutputType = outputType;
+ AnnotationValidations.validate(
+ ExecuteInIsolatedServiceRequest.OutputSpec.OutputType.class, null, mOutputType);
+ this.mMaxIntValue = maxIntValue;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /** Default value is OUTPUT_TYPE_NULL. */
+ @DataClass.Generated.Member
+ public @ExecuteInIsolatedServiceRequest.OutputSpec.OutputType int getOutputType() {
+ return mOutputType;
+ }
+
+ /** Optional. Only set when output option is OUTPUT_TYPE_BEST_VALUE. */
+ @DataClass.Generated.Member
+ public int getMaxIntValue() {
+ return mMaxIntValue;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ dest.writeInt(mOutputType);
+ dest.writeInt(mMaxIntValue);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() {
+ return 0;
+ }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ protected ExecuteOptionsParcel(@NonNull android.os.Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ int outputType = in.readInt();
+ int maxIntValue = in.readInt();
+
+ this.mOutputType = outputType;
+ AnnotationValidations.validate(
+ ExecuteInIsolatedServiceRequest.OutputSpec.OutputType.class, null, mOutputType);
+ this.mMaxIntValue = maxIntValue;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<ExecuteOptionsParcel> CREATOR =
+ new Parcelable.Creator<ExecuteOptionsParcel>() {
+ @Override
+ public ExecuteOptionsParcel[] newArray(int size) {
+ return new ExecuteOptionsParcel[size];
+ }
+
+ @Override
+ public ExecuteOptionsParcel createFromParcel(@NonNull android.os.Parcel in) {
+ return new ExecuteOptionsParcel(in);
+ }
+ };
+ // @formatter:on
+ // End of generated code
+
+}
diff --git a/framework/java/android/adservices/ondevicepersonalization/ExecuteOutput.java b/framework/java/android/adservices/ondevicepersonalization/ExecuteOutput.java
index 808de96..7c8468f 100644
--- a/framework/java/android/adservices/ondevicepersonalization/ExecuteOutput.java
+++ b/framework/java/android/adservices/ondevicepersonalization/ExecuteOutput.java
@@ -16,9 +16,15 @@
package android.adservices.ondevicepersonalization;
+import static android.adservices.ondevicepersonalization.ExecuteInIsolatedServiceResponse.DEFAULT_BEST_VALUE;
+
import android.annotation.FlaggedApi;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.os.OutcomeReceiver;
+import android.os.PersistableBundle;
import com.android.adservices.ondevicepersonalization.flags.Flags;
import com.android.ondevicepersonalization.internal.util.AnnotationValidations;
@@ -28,11 +34,9 @@
import java.util.List;
/**
- * The result returned by
- * {@link IsolatedWorker#onExecute(ExecuteInput, android.os.OutcomeReceiver)} in response to a call to
- * {@code OnDevicePersonalizationManager#execute(ComponentName, PersistableBundle,
- * java.util.concurrent.Executor, OutcomeReceiver)}
- * from a client app.
+ * The result returned by {@link IsolatedWorker#onExecute(ExecuteInput, android.os.OutcomeReceiver)}
+ * in response to a call to {@code OnDevicePersonalizationManager#execute(ComponentName,
+ * PersistableBundle, java.util.concurrent.Executor, OutcomeReceiver)} from a client app.
*/
@FlaggedApi(Flags.FLAG_ON_DEVICE_PERSONALIZATION_APIS_ENABLED)
@DataClass(genBuilder = true, genEqualsHashCode = true)
@@ -64,18 +68,25 @@
@NonNull private List<EventLogRecord> mEventLogRecords = Collections.emptyList();
/**
- * A byte array that an {@link IsolatedService} may optionally return to to a calling app,
- * by setting this field to a non-null value.
- * The contents of this array will be returned to the caller of
- * {@link OnDevicePersonalizationManager#execute(ComponentName, PersistableBundle, java.util.concurrent.Executor, OutcomeReceiver)}
- * if returning data from isolated processes is allowed by policy and the
- * (calling app package, isolated service package) pair is present in an allowlist that
- * permits data to be returned.
+ * A byte array that an {@link IsolatedService} may optionally return to a calling app, by
+ * setting this field to a non-null value. The contents of this array will be returned to the
+ * caller of {@link OnDevicePersonalizationManager#execute} if returning data from isolated
+ * processes is allowed by policy and the (calling app package, isolated service package) pair
+ * is present in an allowlist that permits data to be returned.
*/
- @DataClass.MaySetToNull
- @Nullable private byte[] mOutputData = null;
+ @DataClass.MaySetToNull @Nullable private byte[] mOutputData = null;
-
+ /**
+ * An integer value that an {@link IsolatedService} may optionally return to a calling app, by
+ * setting this field to the value between 0 and {@link
+ * ExecuteInIsolatedServiceRequest.OutputSpec#getMaxIntValue()}. The noise will be added to the
+ * value of this field before returned to the caller of {@link
+ * OnDevicePersonalizationManager#executeInIsolatedService}. In order to get this field, the
+ * (calling app package, isolated service package) pair must be present in an allowlist that
+ * permits data to be returned and {@link
+ * ExecuteInIsolatedServiceRequest.OutputSpec#buildBestValueSpec} is set.
+ */
+ private final int mBestValue;
// Code below generated by codegen v1.0.23.
//
@@ -95,13 +106,15 @@
@Nullable RequestLogRecord requestLogRecord,
@Nullable RenderingConfig renderingConfig,
@NonNull List<EventLogRecord> eventLogRecords,
- @Nullable byte[] outputData) {
+ @Nullable byte[] outputData,
+ int bestValue) {
this.mRequestLogRecord = requestLogRecord;
this.mRenderingConfig = renderingConfig;
this.mEventLogRecords = eventLogRecords;
AnnotationValidations.validate(
NonNull.class, null, mEventLogRecords);
this.mOutputData = outputData;
+ this.mBestValue = bestValue;
// onConstructed(); // You can define this method to get a callback
}
@@ -139,19 +152,33 @@
}
/**
- * A byte array that an {@link IsolatedService} may optionally return to to a calling app,
- * by setting this field to a non-null value.
- * The contents of this array will be returned to the caller of
- * {@link OnDevicePersonalizationManager#execute(ComponentName, PersistableBundle, java.util.concurrent.Executor, OutcomeReceiver)}
- * if returning data from isolated processes is allowed by policy and the
- * (calling app package, isolated service package) pair is present in an allowlist that
- * permits data to be returned.
+ * A byte array that an {@link IsolatedService} may optionally return to a calling app, by
+ * setting this field to a non-null value. The contents of this array will be returned to the
+ * caller of {@link OnDevicePersonalizationManager#execute} if returning data from isolated
+ * processes is allowed by policy and the (calling app package, isolated service package) pair
+ * is present in an allowlist that permits data to be returned.
*/
@DataClass.Generated.Member
public @Nullable byte[] getOutputData() {
return mOutputData;
}
+ /**
+ * An integer value that an {@link IsolatedService} may optionally return to a calling app, by
+ * setting this field to the value between 0 and {@link
+ * ExecuteInIsolatedServiceRequest.OutputSpec#getMaxIntValue()}. The noise will be added to the
+ * value of this field before returned to the caller of {@link
+ * OnDevicePersonalizationManager#executeInIsolatedService}. In order to get this field, the
+ * (calling app package, isolated service package) pair must be present in an allowlist that
+ * permits data to be returned and {@link
+ * ExecuteInIsolatedServiceRequest.OutputSpec#buildBestValueSpec} is set.
+ */
+ @FlaggedApi(Flags.FLAG_EXECUTE_IN_ISOLATED_SERVICE_API_ENABLED)
+ @DataClass.Generated.Member
+ public @IntRange(from = DEFAULT_BEST_VALUE) int getBestValue() {
+ return mBestValue;
+ }
+
@Override
@DataClass.Generated.Member
public boolean equals(@Nullable Object o) {
@@ -168,7 +195,8 @@
&& java.util.Objects.equals(mRequestLogRecord, that.mRequestLogRecord)
&& java.util.Objects.equals(mRenderingConfig, that.mRenderingConfig)
&& java.util.Objects.equals(mEventLogRecords, that.mEventLogRecords)
- && java.util.Arrays.equals(mOutputData, that.mOutputData);
+ && java.util.Arrays.equals(mOutputData, that.mOutputData)
+ && mBestValue == that.mBestValue;
}
@Override
@@ -182,6 +210,7 @@
_hash = 31 * _hash + java.util.Objects.hashCode(mRenderingConfig);
_hash = 31 * _hash + java.util.Objects.hashCode(mEventLogRecords);
_hash = 31 * _hash + java.util.Arrays.hashCode(mOutputData);
+ _hash = 31 * _hash + mBestValue;
return _hash;
}
@@ -196,6 +225,7 @@
private @Nullable RenderingConfig mRenderingConfig;
private @NonNull List<EventLogRecord> mEventLogRecords;
private @Nullable byte[] mOutputData;
+ private int mBestValue = -1;
private long mBuilderFieldsSet = 0L;
@@ -209,19 +239,17 @@
*/
@DataClass.Generated.Member
public @NonNull Builder setRequestLogRecord(@Nullable RequestLogRecord value) {
- checkNotUsed();
mBuilderFieldsSet |= 0x1;
mRequestLogRecord = value;
return this;
}
/**
- * A {@link RenderingConfig} object that contains information about the content to be rendered
- * in the client app view. Can be null if no content is to be rendered.
+ * A {@link RenderingConfig} object that contains information about the content to be
+ * rendered in the client app view. Can be null if no content is to be rendered.
*/
@DataClass.Generated.Member
public @NonNull Builder setRenderingConfig(@Nullable RenderingConfig value) {
- checkNotUsed();
mBuilderFieldsSet |= 0x2;
mRenderingConfig = value;
return this;
@@ -237,7 +265,6 @@
*/
@DataClass.Generated.Member
public @NonNull Builder setEventLogRecords(@NonNull List<EventLogRecord> value) {
- checkNotUsed();
mBuilderFieldsSet |= 0x4;
mEventLogRecords = value;
return this;
@@ -252,26 +279,42 @@
}
/**
- * A byte array that an {@link IsolatedService} may optionally return to to a calling app,
- * by setting this field to a non-null value.
- * The contents of this array will be returned to the caller of
- * {@link OnDevicePersonalizationManager#execute(ComponentName, PersistableBundle, java.util.concurrent.Executor, OutcomeReceiver)}
- * if returning data from isolated processes is allowed by policy and the
- * (calling app package, isolated service package) pair is present in an allowlist that
- * permits data to be returned.
+ * A byte array that an {@link IsolatedService} may optionally return to a calling app, by
+ * setting this field to a non-null value. The contents of this array will be returned to
+ * the caller of {@link OnDevicePersonalizationManager#execute(ComponentName,
+ * PersistableBundle, java.util.concurrent.Executor, OutcomeReceiver)} if returning data
+ * from isolated processes is allowed by policy and the (calling app package, isolated
+ * service package) pair is present in an allowlist that permits data to be returned.
*/
@DataClass.Generated.Member
public @NonNull Builder setOutputData(@Nullable byte... value) {
- checkNotUsed();
mBuilderFieldsSet |= 0x8;
mOutputData = value;
return this;
}
+ /**
+ * An integer value that an {@link IsolatedService} may optionally return to a calling app,
+ * by setting this field to the value between 0 and {@link
+ * ExecuteInIsolatedServiceRequest.OutputSpec#getMaxIntValue()}. The noise will be added to
+ * the value of this field before returned to the caller of {@link
+ * OnDevicePersonalizationManager#executeInIsolatedService}. In order to get this field, the
+ * (calling app package, isolated service package) pair must be present in an allowlist that
+ * permits data to be returned and {@link
+ * ExecuteInIsolatedServiceRequest.OutputSpec#buildBestValueSpec} is set.
+ */
+ @FlaggedApi(Flags.FLAG_EXECUTE_IN_ISOLATED_SERVICE_API_ENABLED)
+ @DataClass.Generated.Member
+ public @NonNull Builder setBestValue(@IntRange(from = 0) int value) {
+ AnnotationValidations.validate(IntRange.class, null, value, "from", 0);
+ mBuilderFieldsSet |= 0x10;
+ mBestValue = value;
+ return this;
+ }
+
/** Builds the instance. This builder should not be touched after calling this! */
public @NonNull ExecuteOutput build() {
- checkNotUsed();
- mBuilderFieldsSet |= 0x10; // Mark builder used
+ mBuilderFieldsSet |= 0x20; // Mark builder used
if ((mBuilderFieldsSet & 0x1) == 0) {
mRequestLogRecord = null;
@@ -285,27 +328,24 @@
if ((mBuilderFieldsSet & 0x8) == 0) {
mOutputData = null;
}
+ if ((mBuilderFieldsSet & 0x10) == 0) {
+ mBestValue = -1;
+ }
ExecuteOutput o = new ExecuteOutput(
mRequestLogRecord,
mRenderingConfig,
mEventLogRecords,
- mOutputData);
+ mOutputData,
+ mBestValue);
return o;
}
-
- private void checkNotUsed() {
- if ((mBuilderFieldsSet & 0x10) != 0) {
- throw new IllegalStateException(
- "This Builder should not be reused. Use a new Builder instance instead");
- }
- }
}
@DataClass.Generated(
- time = 1707251143585L,
+ time = 1721951665662L,
codegenVersion = "1.0.23",
sourceFile = "packages/modules/OnDevicePersonalization/framework/java/android/adservices/ondevicepersonalization/ExecuteOutput.java",
- inputSignatures = "private @com.android.ondevicepersonalization.internal.util.DataClass.MaySetToNull @android.annotation.Nullable android.adservices.ondevicepersonalization.RequestLogRecord mRequestLogRecord\nprivate @com.android.ondevicepersonalization.internal.util.DataClass.MaySetToNull @android.annotation.Nullable android.adservices.ondevicepersonalization.RenderingConfig mRenderingConfig\nprivate @com.android.ondevicepersonalization.internal.util.DataClass.PluralOf(\"eventLogRecord\") @android.annotation.NonNull java.util.List<android.adservices.ondevicepersonalization.EventLogRecord> mEventLogRecords\nprivate @com.android.ondevicepersonalization.internal.util.DataClass.MaySetToNull @android.annotation.Nullable byte[] mOutputData\nclass ExecuteOutput extends java.lang.Object implements []\[email protected](genBuilder=true, genEqualsHashCode=true)")
+ inputSignatures = "private @com.android.ondevicepersonalization.internal.util.DataClass.MaySetToNull @android.annotation.Nullable android.adservices.ondevicepersonalization.RequestLogRecord mRequestLogRecord\nprivate @com.android.ondevicepersonalization.internal.util.DataClass.MaySetToNull @android.annotation.Nullable android.adservices.ondevicepersonalization.RenderingConfig mRenderingConfig\nprivate @com.android.ondevicepersonalization.internal.util.DataClass.PluralOf(\"eventLogRecord\") @android.annotation.NonNull java.util.List<android.adservices.ondevicepersonalization.EventLogRecord> mEventLogRecords\nprivate @com.android.ondevicepersonalization.internal.util.DataClass.MaySetToNull @android.annotation.Nullable byte[] mOutputData\nprivate int mBestValue\nclass ExecuteOutput extends java.lang.Object implements []\[email protected](genBuilder=true, genEqualsHashCode=true)")
@Deprecated
private void __metadata() {}
diff --git a/framework/java/android/adservices/ondevicepersonalization/ExecuteOutputParcel.java b/framework/java/android/adservices/ondevicepersonalization/ExecuteOutputParcel.java
index cf797ba..394f2b1 100644
--- a/framework/java/android/adservices/ondevicepersonalization/ExecuteOutputParcel.java
+++ b/framework/java/android/adservices/ondevicepersonalization/ExecuteOutputParcel.java
@@ -67,62 +67,83 @@
*/
@Nullable private byte[] mOutputData = null;
+ /**
+ * An integer value that an {@link IsolatedService} may optionally return to to a calling app,
+ * by setting this field to the value between 0 and max value in {@link
+ * ExecuteInIsolatedServiceRequest.Options}. The value of this field will be returned to the
+ * caller of {@link OnDevicePersonalizationManager#executeInIsolatedService} if returning data
+ * from isolated processes is allowed by policy and the (calling app package, isolated service
+ * package) pair is present in an allowlist that permits data to be returned.
+ *
+ * @hide
+ */
+ private int mBestValue = -1;
+
/** @hide */
public ExecuteOutputParcel(@NonNull ExecuteOutput value) {
- this(value.getRequestLogRecord(), value.getRenderingConfig(), value.getEventLogRecords(),
- value.getOutputData());
+ this(
+ value.getRequestLogRecord(),
+ value.getRenderingConfig(),
+ value.getEventLogRecords(),
+ value.getOutputData(),
+ value.getBestValue());
}
-
-
// Code below generated by codegen v1.0.23.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
//
// To regenerate run:
- // $ codegen $ANDROID_BUILD_TOP/packages/modules/OnDevicePersonalization/framework/java/android/adservices/ondevicepersonalization/ExecuteOutputParcel.java
+ // $ codegen
+ // $ANDROID_BUILD_TOP/packages/modules/OnDevicePersonalization/framework/java/android/adservices/ondevicepersonalization/ExecuteOutputParcel.java
//
// To exclude the generated code from IntelliJ auto-formatting enable (one-time):
// Settings > Editor > Code Style > Formatter Control
- //@formatter:off
-
+ // @formatter:off
/**
* Creates a new ExecuteOutputParcel.
*
- * @param requestLogRecord
- * Persistent data to be written to the REQUESTS table after
- * {@link IsolatedWorker#onExecute(ExecuteInput, android.os.OutcomeReceiver)}
- * completes. If null, no persistent data will be written.
- * @param renderingConfig
- * A {@link RenderingConfig} object that contains information about the content to be rendered
- * in the client app view. Can be null if no content is to be rendered.
- * @param eventLogRecords
- * A list of {@link EventLogRecord}. Writes events to the EVENTS table and associates
- * them with requests with the specified corresponding {@link RequestLogRecord} from
- * {@link EventLogRecord#getRequestLogRecord()}.
- * If the event does not contain a {@link RequestLogRecord} emitted by this package, the
- * EventLogRecord is not written.
- * @param outputData
- * A byte array returned by an {@link IsolatedService} to a calling app. The contents of
- * this array is returned to the caller of
- * {@link OnDevicePersonalizationManager#execute(ComponentName, PersistableBundle, java.util.concurrent.Executor, OutcomeReceiver)}
- * if the (calling app package, isolated service package) pair is present in an allow list
- * that permits data to be returned to the caller.
+ * @param requestLogRecord Persistent data to be written to the REQUESTS table after {@link
+ * IsolatedWorker#onExecute(ExecuteInput, android.os.OutcomeReceiver)} completes. If null,
+ * no persistent data will be written.
+ * @param renderingConfig A {@link RenderingConfig} object that contains information about the
+ * content to be rendered in the client app view. Can be null if no content is to be
+ * rendered.
+ * @param eventLogRecords A list of {@link EventLogRecord}. Writes events to the EVENTS table
+ * and associates them with requests with the specified corresponding {@link
+ * RequestLogRecord} from {@link EventLogRecord#getRequestLogRecord()}. If the event does
+ * not contain a {@link RequestLogRecord} emitted by this package, the EventLogRecord is not
+ * written.
+ * @param outputData A byte array returned by an {@link IsolatedService} to a calling app. The
+ * contents of this array is returned to the caller of {@link
+ * OnDevicePersonalizationManager#execute(ComponentName, PersistableBundle,
+ * java.util.concurrent.Executor, OutcomeReceiver)} if the (calling app package, isolated
+ * service package) pair is present in an allow list that permits data to be returned to the
+ * caller.
+ * @param bestValue An integer value that an {@link IsolatedService} may optionally return to to
+ * a calling app, by setting this field to the value between 0 and max value in {@link
+ * ExecuteInIsolatedServiceRequest.OutputSpec}. The value of this field will be returned to
+ * the caller of {@link OnDevicePersonalizationManager#executeInIsolatedService} if
+ * returning data from isolated processes is allowed by policy and the (calling app package,
+ * isolated service package) pair is present in an allowlist that permits data to be
+ * returned.
*/
@DataClass.Generated.Member
public ExecuteOutputParcel(
@Nullable RequestLogRecord requestLogRecord,
@Nullable RenderingConfig renderingConfig,
@NonNull List<EventLogRecord> eventLogRecords,
- @Nullable byte[] outputData) {
+ @Nullable byte[] outputData,
+ int bestValue) {
this.mRequestLogRecord = requestLogRecord;
this.mRenderingConfig = renderingConfig;
this.mEventLogRecords = eventLogRecords;
AnnotationValidations.validate(
NonNull.class, null, mEventLogRecords);
this.mOutputData = outputData;
+ this.mBestValue = bestValue;
// onConstructed(); // You can define this method to get a callback
}
@@ -172,6 +193,21 @@
return mOutputData;
}
+ /**
+ * An integer value that an {@link IsolatedService} may optionally return to to a calling app,
+ * by setting this field to the value between 0 and max value in {@link
+ * ExecuteInIsolatedServiceRequest.Options}. The value of this field will be returned to the
+ * caller of {@link OnDevicePersonalizationManager#executeInIsolatedService} if returning data
+ * from isolated processes is allowed by policy and the (calling app package, isolated service
+ * package) pair is present in an allowlist that permits data to be returned.
+ *
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public int getBestValue() {
+ return mBestValue;
+ }
+
@Override
@DataClass.Generated.Member
public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
@@ -186,6 +222,7 @@
if (mRenderingConfig != null) dest.writeTypedObject(mRenderingConfig, flags);
dest.writeParcelableList(mEventLogRecords, flags);
dest.writeByteArray(mOutputData);
+ dest.writeInt(mBestValue);
}
@Override
@@ -205,6 +242,7 @@
List<EventLogRecord> eventLogRecords = new java.util.ArrayList<>();
in.readParcelableList(eventLogRecords, EventLogRecord.class.getClassLoader());
byte[] outputData = in.createByteArray();
+ int bestValue = in.readInt();
this.mRequestLogRecord = requestLogRecord;
this.mRenderingConfig = renderingConfig;
@@ -212,6 +250,7 @@
AnnotationValidations.validate(
NonNull.class, null, mEventLogRecords);
this.mOutputData = outputData;
+ this.mBestValue = bestValue;
// onConstructed(); // You can define this method to get a callback
}
@@ -231,14 +270,15 @@
};
@DataClass.Generated(
- time = 1706684633171L,
+ time = 1721773162236L,
codegenVersion = "1.0.23",
- sourceFile = "packages/modules/OnDevicePersonalization/framework/java/android/adservices/ondevicepersonalization/ExecuteOutputParcel.java",
- inputSignatures = "private @android.annotation.Nullable android.adservices.ondevicepersonalization.RequestLogRecord mRequestLogRecord\nprivate @android.annotation.Nullable android.adservices.ondevicepersonalization.RenderingConfig mRenderingConfig\nprivate @com.android.ondevicepersonalization.internal.util.DataClass.PluralOf(\"eventLogRecord\") @android.annotation.NonNull java.util.List<android.adservices.ondevicepersonalization.EventLogRecord> mEventLogRecords\nprivate @android.annotation.Nullable byte[] mOutputData\nclass ExecuteOutputParcel extends java.lang.Object implements [android.os.Parcelable]\[email protected](genAidl=false, genBuilder=false)")
+ sourceFile =
+ "packages/modules/OnDevicePersonalization/framework/java/android/adservices/ondevicepersonalization/ExecuteOutputParcel.java",
+ inputSignatures =
+ "private @android.annotation.Nullable android.adservices.ondevicepersonalization.RequestLogRecord mRequestLogRecord\nprivate @android.annotation.Nullable android.adservices.ondevicepersonalization.RenderingConfig mRenderingConfig\nprivate @com.android.ondevicepersonalization.internal.util.DataClass.PluralOf(\"eventLogRecord\") @android.annotation.NonNull java.util.List<android.adservices.ondevicepersonalization.EventLogRecord> mEventLogRecords\nprivate @android.annotation.Nullable byte[] mOutputData\nprivate int mBestValue\nclass ExecuteOutputParcel extends java.lang.Object implements [android.os.Parcelable]\[email protected](genAidl=false, genBuilder=false)")
@Deprecated
private void __metadata() {}
-
//@formatter:on
// End of generated code
diff --git a/framework/java/android/adservices/ondevicepersonalization/FederatedComputeScheduleRequest.java b/framework/java/android/adservices/ondevicepersonalization/FederatedComputeScheduleRequest.java
new file mode 100644
index 0000000..f8282e8
--- /dev/null
+++ b/framework/java/android/adservices/ondevicepersonalization/FederatedComputeScheduleRequest.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.adservices.ondevicepersonalization;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.adservices.ondevicepersonalization.flags.Flags;
+import com.android.ondevicepersonalization.internal.util.AnnotationValidations;
+import com.android.ondevicepersonalization.internal.util.DataClass;
+
+/**
+ * The input for {@link FederatedComputeScheduler#schedule(FederatedComputeScheduleRequest,
+ * android.os.OutcomeReceiver)}.
+ */
+@DataClass(genEqualsHashCode = true)
+@FlaggedApi(Flags.FLAG_FCP_SCHEDULE_WITH_OUTCOME_RECEIVER_ENABLED)
+public final class FederatedComputeScheduleRequest {
+ /** Parameters related to job scheduling. */
+ @NonNull private FederatedComputeScheduler.Params mParams;
+
+ /**
+ * Population refers to a collection of devices that specific task groups can run on. It should
+ * match task plan configured at remote federated compute server.
+ */
+ @NonNull private String mPopulationName;
+
+ // Code below generated by codegen v1.0.23.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen
+ // $ANDROID_BUILD_TOP/packages/modules/OnDevicePersonalization/framework/java/android/adservices/ondevicepersonalization/FederatedComputeScheduleRequest.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ // @formatter:off
+
+ @DataClass.Generated.Member
+ public FederatedComputeScheduleRequest(
+ @NonNull FederatedComputeScheduler.Params params, @NonNull String populationName) {
+ this.mParams = params;
+ AnnotationValidations.validate(NonNull.class, null, mParams);
+ this.mPopulationName = populationName;
+ AnnotationValidations.validate(NonNull.class, null, mPopulationName);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /** Parameters related to job scheduling. */
+ @DataClass.Generated.Member
+ public @NonNull FederatedComputeScheduler.Params getParams() {
+ return mParams;
+ }
+
+ /**
+ * Population refers to a collection of devices that specific task groups can run on. It should
+ * match task plan configured at remote federated compute server.
+ */
+ @DataClass.Generated.Member
+ public @NonNull String getPopulationName() {
+ return mPopulationName;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public boolean equals(@Nullable Object o) {
+ // You can override field equality logic by defining either of the methods like:
+ // boolean fieldNameEquals(FederatedComputeScheduleRequest other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ FederatedComputeScheduleRequest that = (FederatedComputeScheduleRequest) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && java.util.Objects.equals(mParams, that.mParams)
+ && java.util.Objects.equals(mPopulationName, that.mPopulationName);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int hashCode() {
+ // You can override field hashCode logic by defining methods like:
+ // int fieldNameHashCode() { ... }
+
+ int _hash = 1;
+ _hash = 31 * _hash + java.util.Objects.hashCode(mParams);
+ _hash = 31 * _hash + java.util.Objects.hashCode(mPopulationName);
+ return _hash;
+ }
+
+ @DataClass.Generated(
+ time = 1724192543514L,
+ codegenVersion = "1.0.23",
+ sourceFile =
+ "packages/modules/OnDevicePersonalization/framework/java/android/adservices/ondevicepersonalization/FederatedComputeScheduleRequest.java",
+ inputSignatures =
+ "private @android.annotation.NonNull"
+ + " android.adservices.ondevicepersonalization.FederatedComputeScheduler.Params"
+ + " mParams\n"
+ + "private @android.annotation.NonNull java.lang.String mPopulationName\n"
+ + "class FederatedComputeScheduleRequest extends java.lang.Object"
+ + " implements []\n"
+ + "@com.android.ondevicepersonalization.internal.util.DataClass(genEqualsHashCode=true)")
+ @Deprecated
+ private void __metadata() {}
+
+ // @formatter:on
+ // End of generated code
+}
diff --git a/framework/java/android/adservices/ondevicepersonalization/FederatedComputeScheduleResponse.java b/framework/java/android/adservices/ondevicepersonalization/FederatedComputeScheduleResponse.java
new file mode 100644
index 0000000..ae905ce
--- /dev/null
+++ b/framework/java/android/adservices/ondevicepersonalization/FederatedComputeScheduleResponse.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.adservices.ondevicepersonalization;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+
+import com.android.adservices.ondevicepersonalization.flags.Flags;
+import com.android.ondevicepersonalization.internal.util.AnnotationValidations;
+import com.android.ondevicepersonalization.internal.util.DataClass;
+
+/**
+ * The result returned by {@link FederatedComputeScheduler#schedule(FederatedComputeScheduleRequest,
+ * android.os.OutcomeReceiver)} when successful.
+ */
+@DataClass(genEqualsHashCode = true)
+@FlaggedApi(Flags.FLAG_FCP_SCHEDULE_WITH_OUTCOME_RECEIVER_ENABLED)
+public final class FederatedComputeScheduleResponse {
+
+ @NonNull private FederatedComputeScheduleRequest mFederatedComputeScheduleRequest;
+
+ // Code below generated by codegen v1.0.23.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen
+ // $ANDROID_BUILD_TOP/packages/modules/OnDevicePersonalization/framework/java/android/adservices/ondevicepersonalization/FederatedComputeScheduleResponse.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ // @formatter:off
+
+ @DataClass.Generated.Member
+ public FederatedComputeScheduleResponse(
+ @NonNull FederatedComputeScheduleRequest federatedComputeScheduleRequest) {
+ this.mFederatedComputeScheduleRequest = federatedComputeScheduleRequest;
+ AnnotationValidations.validate(NonNull.class, null, mFederatedComputeScheduleRequest);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /** The request associated with this response. */
+ @DataClass.Generated.Member
+ public @NonNull FederatedComputeScheduleRequest getFederatedComputeScheduleRequest() {
+ return mFederatedComputeScheduleRequest;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public boolean equals(@android.annotation.Nullable Object o) {
+ // You can override field equality logic by defining either of the methods like:
+ // boolean fieldNameEquals(FederatedComputeScheduleResponse other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ FederatedComputeScheduleResponse that = (FederatedComputeScheduleResponse) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && java.util.Objects.equals(
+ mFederatedComputeScheduleRequest, that.mFederatedComputeScheduleRequest);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int hashCode() {
+ // You can override field hashCode logic by defining methods like:
+ // int fieldNameHashCode() { ... }
+
+ int _hash = 1;
+ _hash = 31 * _hash + java.util.Objects.hashCode(mFederatedComputeScheduleRequest);
+ return _hash;
+ }
+
+ @DataClass.Generated(
+ time = 1725476292347L,
+ codegenVersion = "1.0.23",
+ sourceFile =
+ "packages/modules/OnDevicePersonalization/framework/java/android/adservices/ondevicepersonalization/FederatedComputeScheduleResponse.java",
+ inputSignatures =
+ "private @android.annotation.NonNull java.lang.String mPopulationName\n"
+ + "class FederatedComputeScheduleResponse extends java.lang.Object"
+ + " implements []\n"
+ + "@com.android.ondevicepersonalization.internal.util.DataClass(genEqualsHashCode=true)")
+ @Deprecated
+ private void __metadata() {}
+
+ // @formatter:on
+ // End of generated code
+
+}
diff --git a/framework/java/android/adservices/ondevicepersonalization/FederatedComputeScheduler.java b/framework/java/android/adservices/ondevicepersonalization/FederatedComputeScheduler.java
index 81e9c69..17bddc2 100644
--- a/framework/java/android/adservices/ondevicepersonalization/FederatedComputeScheduler.java
+++ b/framework/java/android/adservices/ondevicepersonalization/FederatedComputeScheduler.java
@@ -23,12 +23,14 @@
import android.annotation.NonNull;
import android.annotation.WorkerThread;
import android.federatedcompute.common.TrainingOptions;
+import android.os.OutcomeReceiver;
import android.os.RemoteException;
import com.android.adservices.ondevicepersonalization.flags.Flags;
import com.android.ondevicepersonalization.internal.util.LoggerFactory;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
/**
* Handles scheduling federated compute jobs. See {@link
@@ -37,6 +39,8 @@
@FlaggedApi(Flags.FLAG_ON_DEVICE_PERSONALIZATION_APIS_ENABLED)
public class FederatedComputeScheduler {
private static final String TAG = FederatedComputeScheduler.class.getSimpleName();
+
+ private static final int FEDERATED_COMPUTE_SCHEDULE_TIMEOUT_SECONDS = 30;
private static final LoggerFactory.Logger sLogger = LoggerFactory.getLogger();
private final IFederatedComputeService mFcService;
@@ -53,31 +57,33 @@
// TODO(b/269665435): add sample code snippet.
/**
* Schedules a federated compute job. In {@link IsolatedService#onRequest}, the app can call
- * {@link IsolatedService#getFederatedComputeScheduler} to pass scheduler when construct {@link
- * IsolatedWorker}.
+ * {@link IsolatedService#getFederatedComputeScheduler} to pass the scheduler when constructing
+ * the {@link IsolatedWorker}.
*
* @param params parameters related to job scheduling.
- * @param input the configuration of the federated compute. It should be consistent with the
+ * @param input the configuration of the federated computation. It should be consistent with the
* federated compute server setup.
*/
@WorkerThread
public void schedule(@NonNull Params params, @NonNull FederatedComputeInput input) {
- final long startTimeMillis = System.currentTimeMillis();
- int responseCode = Constants.STATUS_INTERNAL_ERROR;
if (mFcService == null) {
+ logApiCallStats(
+ Constants.API_NAME_FEDERATED_COMPUTE_SCHEDULE,
+ 0,
+ Constants.STATUS_INTERNAL_ERROR);
throw new IllegalStateException(
"FederatedComputeScheduler not available for this instance.");
}
-
- android.federatedcompute.common.TrainingInterval trainingInterval =
- convertTrainingInterval(params.getTrainingInterval());
+ final long startTimeMillis = System.currentTimeMillis();
TrainingOptions trainingOptions =
new TrainingOptions.Builder()
.setPopulationName(input.getPopulationName())
- .setTrainingInterval(trainingInterval)
+ .setTrainingInterval(convertTrainingInterval(params.getTrainingInterval()))
.build();
+
CountDownLatch latch = new CountDownLatch(1);
final int[] err = {0};
+ int responseCode = Constants.STATUS_INTERNAL_ERROR;
try {
mFcService.schedule(
trainingOptions,
@@ -93,16 +99,24 @@
latch.countDown();
}
});
- latch.await();
+
+ boolean countedDown =
+ latch.await(FEDERATED_COMPUTE_SCHEDULE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+
if (err[0] != 0) {
- // Fail silently for now. TODO(b/346827691): update schedule/cancel API to return
- // error status to caller.
- sLogger.e("Internal failure occurred while scheduling job, error code %d", err[0]);
- responseCode = Constants.STATUS_INTERNAL_ERROR;
+ sLogger.e(
+ TAG + " : Internal failure occurred while scheduling job, error code %d",
+ err[0]);
+ responseCode = err[0];
+ return;
+ } else if (!countedDown) {
+ sLogger.d(TAG + " : timed out waiting for schedule operation to complete.");
+ responseCode = Constants.STATUS_TIMEOUT;
return;
}
responseCode = Constants.STATUS_SUCCESS;
} catch (RemoteException | InterruptedException e) {
+ responseCode = Constants.STATUS_REMOTE_EXCEPTION;
sLogger.e(TAG + ": Failed to schedule federated compute job", e);
throw new IllegalStateException(e);
} finally {
@@ -114,9 +128,96 @@
}
/**
+ * Schedules a federated compute job. In {@link IsolatedService#onRequest}, the app can call
+ * {@link IsolatedService#getFederatedComputeScheduler} to pass the scheduler when constructing
+ * the {@link IsolatedWorker}.
+ *
+ * @param federatedComputeScheduleRequest input parameters related to job scheduling.
+ * @param outcomeReceiver This either returns a {@link FederatedComputeScheduleResponse} on
+ * success, or {@link Exception} on failure. The exception type is {@link
+ * OnDevicePersonalizationException} with error code {@link
+ * OnDevicePersonalizationException#ERROR_INVALID_TRAINING_MANIFEST} if the manifest is
+ * missing the federated compute server URL or {@link
+ * OnDevicePersonalizationException#ERROR_SCHEDULE_TRAINING_FAILED} when scheduling fails
+ * for other reasons.
+ */
+ @WorkerThread
+ @FlaggedApi(Flags.FLAG_FCP_SCHEDULE_WITH_OUTCOME_RECEIVER_ENABLED)
+ public void schedule(
+ @NonNull FederatedComputeScheduleRequest federatedComputeScheduleRequest,
+ @NonNull OutcomeReceiver<FederatedComputeScheduleResponse, Exception> outcomeReceiver) {
+ if (mFcService == null) {
+ logApiCallStats(
+ Constants.API_NAME_FEDERATED_COMPUTE_SCHEDULE,
+ 0,
+ Constants.STATUS_INTERNAL_ERROR);
+ outcomeReceiver.onError(
+ new IllegalStateException(
+ "FederatedComputeScheduler not available for this instance."));
+ }
+
+ final long startTimeMillis = System.currentTimeMillis();
+ TrainingOptions trainingOptions =
+ new TrainingOptions.Builder()
+ .setPopulationName(federatedComputeScheduleRequest.getPopulationName())
+ .setTrainingInterval(
+ convertTrainingInterval(
+ federatedComputeScheduleRequest
+ .getParams()
+ .getTrainingInterval()))
+ .build();
+ try {
+ mFcService.schedule(
+ trainingOptions,
+ new IFederatedComputeCallback.Stub() {
+ @Override
+ public void onSuccess() {
+ logApiCallStats(
+ Constants.API_NAME_FEDERATED_COMPUTE_SCHEDULE,
+ System.currentTimeMillis() - startTimeMillis,
+ Constants.STATUS_SUCCESS);
+ outcomeReceiver.onResult(
+ new FederatedComputeScheduleResponse(
+ federatedComputeScheduleRequest));
+ }
+
+ @Override
+ public void onFailure(int errorCode) {
+ logApiCallStats(
+ Constants.API_NAME_FEDERATED_COMPUTE_SCHEDULE,
+ System.currentTimeMillis() - startTimeMillis,
+ errorCode);
+ outcomeReceiver.onError(
+ new OnDevicePersonalizationException(
+ translateErrorCode(errorCode)));
+ }
+ });
+ } catch (RemoteException e) {
+ sLogger.e(TAG + ": Failed to schedule federated compute job", e);
+ logApiCallStats(
+ Constants.API_NAME_FEDERATED_COMPUTE_SCHEDULE,
+ System.currentTimeMillis() - startTimeMillis,
+ Constants.STATUS_REMOTE_EXCEPTION);
+ outcomeReceiver.onError(e);
+ }
+ }
+
+ /**
+ * Translate the failed error code from the {@link IFederatedComputeService} to appropriate API
+ * surface error code.
+ */
+ private static int translateErrorCode(int i) {
+ // Returns invalid/missing manifest or general error code to caller. The general error code
+ // includes personalization disable and all other errors populated from FCP service.
+ return i == Constants.STATUS_FCP_MANIFEST_INVALID
+ ? OnDevicePersonalizationException.ERROR_INVALID_TRAINING_MANIFEST
+ : OnDevicePersonalizationException.ERROR_SCHEDULE_TRAINING_FAILED;
+ }
+
+ /**
* Cancels a federated compute job with input training params. In {@link
* IsolatedService#onRequest}, the app can call {@link
- * IsolatedService#getFederatedComputeScheduler} to pass scheduler when construct {@link
+ * IsolatedService#getFederatedComputeScheduler} to pass scheduler when constructing the {@link
* IsolatedWorker}.
*
* @param input the configuration of the federated compute. It should be consistent with the
@@ -127,6 +228,10 @@
final long startTimeMillis = System.currentTimeMillis();
int responseCode = Constants.STATUS_INTERNAL_ERROR;
if (mFcService == null) {
+ logApiCallStats(
+ Constants.API_NAME_FEDERATED_COMPUTE_CANCEL,
+ System.currentTimeMillis() - startTimeMillis,
+ responseCode);
throw new IllegalStateException(
"FederatedComputeScheduler not available for this instance.");
}
@@ -142,18 +247,23 @@
}
@Override
- public void onFailure(int i) {
- err[0] = i;
+ public void onFailure(int errorCode) {
+ err[0] = errorCode;
latch.countDown();
}
});
- latch.await();
+ boolean countedDown =
+ latch.await(FEDERATED_COMPUTE_SCHEDULE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
if (err[0] != 0) {
sLogger.e("Internal failure occurred while cancelling job, error code %d", err[0]);
responseCode = Constants.STATUS_INTERNAL_ERROR;
// Fail silently for now. TODO(b/346827691): update schedule/cancel API to return
// error status to caller.
return;
+ } else if (!countedDown) {
+ sLogger.d(TAG + " : timed out waiting for cancel operation to complete.");
+ responseCode = Constants.STATUS_INTERNAL_ERROR;
+ return;
}
responseCode = Constants.STATUS_SUCCESS;
} catch (RemoteException | InterruptedException e) {
@@ -167,7 +277,7 @@
}
}
- private android.federatedcompute.common.TrainingInterval convertTrainingInterval(
+ private static android.federatedcompute.common.TrainingInterval convertTrainingInterval(
TrainingInterval interval) {
return new android.federatedcompute.common.TrainingInterval.Builder()
.setMinimumIntervalMillis(interval.getMinimumInterval().toMillis())
@@ -175,7 +285,7 @@
.build();
}
- private @android.federatedcompute.common.TrainingInterval.SchedulingMode int
+ private static @android.federatedcompute.common.TrainingInterval.SchedulingMode int
convertSchedulingMode(TrainingInterval interval) {
switch (interval.getSchedulingMode()) {
case TrainingInterval.SCHEDULING_MODE_ONE_TIME:
@@ -188,6 +298,7 @@
}
}
+ /** Helper method to log call stats based on response code. */
private void logApiCallStats(int apiName, long duration, int responseCode) {
try {
mDataAccessService.logApiCallStats(apiName, duration, responseCode);
diff --git a/framework/java/android/adservices/ondevicepersonalization/IsolatedService.java b/framework/java/android/adservices/ondevicepersonalization/IsolatedService.java
index 13cc7c2..47a45f5 100644
--- a/framework/java/android/adservices/ondevicepersonalization/IsolatedService.java
+++ b/framework/java/android/adservices/ondevicepersonalization/IsolatedService.java
@@ -35,6 +35,7 @@
import android.os.SystemClock;
import com.android.adservices.ondevicepersonalization.flags.Flags;
+import com.android.ondevicepersonalization.internal.util.ExceptionInfo;
import com.android.ondevicepersonalization.internal.util.LoggerFactory;
import com.android.ondevicepersonalization.internal.util.OdpParceledListSlice;
@@ -59,6 +60,7 @@
public abstract class IsolatedService extends Service {
private static final String TAG = IsolatedService.class.getSimpleName();
private static final LoggerFactory.Logger sLogger = LoggerFactory.getLogger();
+ private static final int MAX_EXCEPTION_CHAIN_DEPTH = 3;
private IBinder mBinder;
/** Creates a binder for an {@link IsolatedService}. */
@@ -272,11 +274,7 @@
resultCallback, requestToken, v -> new WebTriggerOutputParcel(v)));
} catch (Exception e) {
sLogger.e(e, TAG + ": Exception during Isolated Service web trigger operation.");
- try {
- resultCallback.onError(Constants.STATUS_INTERNAL_ERROR, 0);
- } catch (RemoteException re) {
- sLogger.e(re, TAG + ": Isolated Service Callback failed.");
- }
+ sendError(resultCallback, Constants.STATUS_INTERNAL_ERROR, e);
}
}
@@ -311,11 +309,7 @@
} catch (Exception e) {
sLogger.e(e,
TAG + ": Exception during Isolated Service training example operation.");
- try {
- resultCallback.onError(Constants.STATUS_INTERNAL_ERROR, 0);
- } catch (RemoteException re) {
- sLogger.e(re, TAG + ": Isolated Service Callback failed.");
- }
+ sendError(resultCallback, Constants.STATUS_INTERNAL_ERROR, e);
}
}
@@ -340,11 +334,7 @@
resultCallback, requestToken, v -> new EventOutputParcel(v)));
} catch (Exception e) {
sLogger.e(e, TAG + ": Exception during Isolated Service web view event operation.");
- try {
- resultCallback.onError(Constants.STATUS_INTERNAL_ERROR, 0);
- } catch (RemoteException re) {
- sLogger.e(re, TAG + ": Isolated Service Callback failed.");
- }
+ sendError(resultCallback, Constants.STATUS_INTERNAL_ERROR, e);
}
}
@@ -370,11 +360,7 @@
resultCallback, requestToken, v -> new RenderOutputParcel(v)));
} catch (Exception e) {
sLogger.e(e, TAG + ": Exception during Isolated Service render operation.");
- try {
- resultCallback.onError(Constants.STATUS_INTERNAL_ERROR, 0);
- } catch (RemoteException re) {
- sLogger.e(re, TAG + ": Isolated Service Callback failed.");
- }
+ sendError(resultCallback, Constants.STATUS_INTERNAL_ERROR, e);
}
}
@@ -397,9 +383,7 @@
"Failed to get IDataAccessService binder from the input params!")));
DownloadCompletedInput input =
- new DownloadCompletedInput.Builder()
- .setDownloadedContents(downloadedContents)
- .build();
+ new DownloadCompletedInput(downloadedContents);
IDataAccessService binder = getDataAccessService(params);
@@ -415,11 +399,7 @@
v -> new DownloadCompletedOutputParcel(v)));
} catch (Exception e) {
sLogger.e(e, TAG + ": Exception during Isolated Service download operation.");
- try {
- resultCallback.onError(Constants.STATUS_INTERNAL_ERROR, 0);
- } catch (RemoteException re) {
- sLogger.e(re, TAG + ": Isolated Service Callback failed.");
- }
+ sendError(resultCallback, Constants.STATUS_INTERNAL_ERROR, e);
}
}
@@ -497,15 +477,20 @@
resultCallback, requestToken, v -> new ExecuteOutputParcel(v)));
} catch (Exception e) {
sLogger.e(e, TAG + ": Exception during Isolated Service execute operation.");
- try {
- resultCallback.onError(Constants.STATUS_INTERNAL_ERROR, 0);
- } catch (RemoteException re) {
- sLogger.e(re, TAG + ": Isolated Service Callback failed.");
- }
+ sendError(resultCallback, Constants.STATUS_INTERNAL_ERROR, e);
}
}
}
+ private void sendError(IIsolatedServiceCallback resultCallback, int errorCode, Throwable t) {
+ try {
+ resultCallback.onError(
+ errorCode, 0, ExceptionInfo.toByteArray(t, MAX_EXCEPTION_CHAIN_DEPTH));
+ } catch (RemoteException re) {
+ sLogger.e(re, TAG + ": Isolated Service Callback failed.");
+ }
+ }
+
private static class WrappedCallback<T, U extends Parcelable>
implements OutcomeReceiver<T, IsolatedServiceException> {
@NonNull private final IIsolatedServiceCallback mCallback;
@@ -526,11 +511,7 @@
long elapsedTimeMillis =
SystemClock.elapsedRealtime() - mRequestToken.getStartTimeMillis();
if (result == null) {
- try {
- mCallback.onError(Constants.STATUS_SERVICE_FAILED, 0);
- } catch (RemoteException e) {
- sLogger.w(TAG + ": Callback failed.", e);
- }
+ sendError(0, new IllegalArgumentException("missing result"));
} else {
Bundle bundle = new Bundle();
U wrappedResult = mConverter.apply(result);
@@ -549,9 +530,16 @@
@Override
public void onError(IsolatedServiceException e) {
+ sendError(e.getErrorCode(), e);
+ }
+
+ private void sendError(int isolatedServiceErrorCode, Throwable t) {
try {
// TODO(b/324478256): Log and report the error code from e.
- mCallback.onError(Constants.STATUS_SERVICE_FAILED, e.getErrorCode());
+ mCallback.onError(
+ Constants.STATUS_SERVICE_FAILED,
+ isolatedServiceErrorCode,
+ ExceptionInfo.toByteArray(t, MAX_EXCEPTION_CHAIN_DEPTH));
} catch (RemoteException re) {
sLogger.w(TAG + ": Callback failed.", re);
}
diff --git a/framework/java/android/adservices/ondevicepersonalization/IsolatedServiceException.java b/framework/java/android/adservices/ondevicepersonalization/IsolatedServiceException.java
index b280b10..03e9544 100644
--- a/framework/java/android/adservices/ondevicepersonalization/IsolatedServiceException.java
+++ b/framework/java/android/adservices/ondevicepersonalization/IsolatedServiceException.java
@@ -17,7 +17,7 @@
package android.adservices.ondevicepersonalization;
import android.annotation.FlaggedApi;
-import android.annotation.IntRange;
+import android.annotation.Nullable;
import com.android.adservices.ondevicepersonalization.flags.Flags;
@@ -29,7 +29,7 @@
*/
@FlaggedApi(Flags.FLAG_ON_DEVICE_PERSONALIZATION_APIS_ENABLED)
public final class IsolatedServiceException extends Exception {
- @IntRange(from = 1, to = 127) private final int mErrorCode;
+ private final int mErrorCode;
/**
* Creates an {@link IsolatedServiceException} with an error code to be logged. The meaning of
@@ -38,16 +38,48 @@
*
* @param errorCode An error code defined by the {@link IsolatedService}.
*/
- public IsolatedServiceException(@IntRange(from = 1, to = 127) int errorCode) {
- super("IsolatedServiceException: Error " + errorCode);
+ public IsolatedServiceException(int errorCode) {
+ this(errorCode, "IsolatedServiceException: Error " + errorCode, null);
+ }
+
+ /**
+ * Creates an {@link IsolatedServiceException} with an error code to be logged. The meaning of
+ * the error code is defined by the {@link IsolatedService}. The platform does not interpret
+ * the error code.
+ *
+ * @param errorCode An error code defined by the {@link IsolatedService}.
+ * @param cause the cause of this exception.
+ */
+ @FlaggedApi(Flags.FLAG_DATA_CLASS_MISSING_CTORS_AND_GETTERS_ENABLED)
+ public IsolatedServiceException(
+ int errorCode,
+ @Nullable Throwable cause) {
+ this(errorCode, "IsolatedServiceException: Error " + errorCode, cause);
+ }
+
+ /**
+ * Creates an {@link IsolatedServiceException} with an error code to be logged. The meaning of
+ * the error code is defined by the {@link IsolatedService}. The platform does not interpret
+ * the error code.
+ *
+ * @param errorCode An error code defined by the {@link IsolatedService}.
+ * @param message the exception message.
+ * @param cause the cause of this exception.
+ */
+ @FlaggedApi(Flags.FLAG_DATA_CLASS_MISSING_CTORS_AND_GETTERS_ENABLED)
+ public IsolatedServiceException(
+ int errorCode,
+ @Nullable String message,
+ @Nullable Throwable cause) {
+ super(message, cause);
mErrorCode = errorCode;
}
/**
* Returns the error code for this exception.
- * @hide
*/
- public @IntRange(from = 1, to = 127) int getErrorCode() {
+ @FlaggedApi(Flags.FLAG_DATA_CLASS_MISSING_CTORS_AND_GETTERS_ENABLED)
+ public int getErrorCode() {
return mErrorCode;
}
}
diff --git a/framework/java/android/adservices/ondevicepersonalization/ModelManager.java b/framework/java/android/adservices/ondevicepersonalization/ModelManager.java
index 29f6e01..a83e910 100644
--- a/framework/java/android/adservices/ondevicepersonalization/ModelManager.java
+++ b/framework/java/android/adservices/ondevicepersonalization/ModelManager.java
@@ -67,6 +67,12 @@
@NonNull OutcomeReceiver<InferenceOutput, Exception> receiver) {
final long startTimeMillis = System.currentTimeMillis();
Objects.requireNonNull(input);
+ if (input.getInputData().length == 0) {
+ throw new IllegalArgumentException("Input data can not be empty");
+ }
+ if (input.getExpectedOutputStructure().getDataOutputs().isEmpty()) {
+ throw new IllegalArgumentException("Expected output data structure can not be empty");
+ }
Bundle bundle = new Bundle();
bundle.putBinder(Constants.EXTRA_DATA_ACCESS_SERVICE_BINDER, mDataService.asBinder());
bundle.putParcelable(Constants.EXTRA_INFERENCE_INPUT, new InferenceInputParcel(input));
@@ -108,12 +114,20 @@
executor.execute(
() -> {
long endTimeMillis = System.currentTimeMillis();
- receiver.onError(
- new IllegalStateException("Error: " + errorCode));
+ if (OnDevicePersonalizationException.isValidErrorCode(
+ errorCode)) {
+ receiver.onError(
+ new OnDevicePersonalizationException(
+ errorCode));
+ } else {
+ receiver.onError(
+ new IllegalStateException(
+ "Error: " + errorCode));
+ }
logApiCallStats(
Constants.API_NAME_MODEL_MANAGER_RUN,
endTimeMillis - startTimeMillis,
- Constants.STATUS_INTERNAL_ERROR);
+ errorCode);
});
}
});
diff --git a/framework/java/android/adservices/ondevicepersonalization/OnDevicePersonalizationConfigManager.java b/framework/java/android/adservices/ondevicepersonalization/OnDevicePersonalizationConfigManager.java
index df3fc3b..92dba02 100644
--- a/framework/java/android/adservices/ondevicepersonalization/OnDevicePersonalizationConfigManager.java
+++ b/framework/java/android/adservices/ondevicepersonalization/OnDevicePersonalizationConfigManager.java
@@ -18,24 +18,16 @@
import static android.adservices.ondevicepersonalization.OnDevicePersonalizationPermissions.MODIFY_ONDEVICEPERSONALIZATION_STATE;
-import android.adservices.ondevicepersonalization.aidl.IOnDevicePersonalizationConfigService;
-import android.adservices.ondevicepersonalization.aidl.IOnDevicePersonalizationConfigServiceCallback;
import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.content.Context;
-import android.os.Binder;
import android.os.OutcomeReceiver;
import com.android.adservices.ondevicepersonalization.flags.Flags;
-import com.android.federatedcompute.internal.util.AbstractServiceBinder;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.ondevicepersonalization.internal.util.LoggerFactory;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
/**
@@ -50,117 +42,19 @@
/** @hide */
public static final String ON_DEVICE_PERSONALIZATION_CONFIG_SERVICE =
"on_device_personalization_config_service";
- private static final LoggerFactory.Logger sLogger = LoggerFactory.getLogger();
- private static final String TAG = OnDevicePersonalizationConfigManager.class.getSimpleName();
-
- private static final String ODP_CONFIG_SERVICE_PACKAGE_SUFFIX =
- "com.android.ondevicepersonalization.services";
-
- private static final String ALT_ODP_CONFIG_SERVICE_PACKAGE_SUFFIX =
- "com.google.android.ondevicepersonalization.services";
- private static final String ODP_CONFIG_SERVICE_INTENT =
- "android.OnDevicePersonalizationConfigService";
-
- private final AbstractServiceBinder<IOnDevicePersonalizationConfigService> mServiceBinder;
/** @hide */
- public OnDevicePersonalizationConfigManager(@NonNull Context context) {
- this(
- AbstractServiceBinder.getServiceBinderByIntent(
- context,
- ODP_CONFIG_SERVICE_INTENT,
- List.of(
- ODP_CONFIG_SERVICE_PACKAGE_SUFFIX,
- ALT_ODP_CONFIG_SERVICE_PACKAGE_SUFFIX),
- IOnDevicePersonalizationConfigService.Stub::asInterface));
- }
-
- /** @hide */
- @VisibleForTesting
- public OnDevicePersonalizationConfigManager(
- AbstractServiceBinder<IOnDevicePersonalizationConfigService> serviceBinder) {
- this.mServiceBinder = serviceBinder;
- }
+ public OnDevicePersonalizationConfigManager(@NonNull Context context) {}
/**
- * API users are expected to call this to modify personalization status for
- * On Device Personalization. The status is persisted both in memory and to the disk.
- * When reboot, the in-memory status will be restored from the disk.
- * Personalization is disabled by default.
- *
- * @param enabled boolean whether On Device Personalization should be enabled.
- * @param executor The {@link Executor} on which to invoke the callback.
- * @param receiver This either returns null on success or {@link Exception} on failure.
- *
- * In case of an error, the receiver returns one of the following exceptions:
- * Returns an {@link IllegalStateException} if the callback is unable to send back results.
- * Returns a {@link SecurityException} if the caller is unauthorized to modify
- * personalization status.
+ * Deprecated. This API is a no-op. ODP automatically determines whether personalization
+ * should be enabled using
+ * {@link android.adservices.common.AdServicesCommonManager}.
*/
@RequiresPermission(MODIFY_ONDEVICEPERSONALIZATION_STATE)
public void setPersonalizationEnabled(boolean enabled,
@NonNull @CallbackExecutor Executor executor,
@NonNull OutcomeReceiver<Void, Exception> receiver) {
- CountDownLatch latch = new CountDownLatch(1);
- try {
- IOnDevicePersonalizationConfigService service = mServiceBinder.getService(executor);
- service.setPersonalizationStatus(enabled,
- new IOnDevicePersonalizationConfigServiceCallback.Stub() {
- @Override
- public void onSuccess() {
- final long token = Binder.clearCallingIdentity();
- try {
- executor.execute(() -> {
- receiver.onResult(null);
- latch.countDown();
- });
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- @Override
- public void onFailure(int errorCode) {
- final long token = Binder.clearCallingIdentity();
- try {
- executor.execute(() -> {
- sLogger.w(TAG + ": Unexpected failure from ODP"
- + "config service with error code: " + errorCode);
- receiver.onError(
- new IllegalStateException("Unexpected failure."));
- latch.countDown();
- });
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
- });
- } catch (IllegalArgumentException | NullPointerException e) {
- latch.countDown();
- throw e;
- } catch (SecurityException e) {
- sLogger.w(TAG + ": Unauthorized call to ODP config service.");
- receiver.onError(e);
- latch.countDown();
- } catch (Exception e) {
- sLogger.w(TAG + ": Unexpected exception during call to ODP config service.");
- receiver.onError(e);
- latch.countDown();
- } finally {
- try {
- latch.await();
- } catch (InterruptedException e) {
- sLogger.e(TAG + ": Failed to set personalization.", e);
- receiver.onError(e);
- }
- unbindFromService();
- }
- }
-
- /**
- * Unbind from config service.
- */
- private void unbindFromService() {
- mServiceBinder.unbindFromService();
+ executor.execute(() -> receiver.onResult(null));
}
}
diff --git a/framework/java/android/adservices/ondevicepersonalization/OnDevicePersonalizationException.java b/framework/java/android/adservices/ondevicepersonalization/OnDevicePersonalizationException.java
index 631e421..9abea59 100644
--- a/framework/java/android/adservices/ondevicepersonalization/OnDevicePersonalizationException.java
+++ b/framework/java/android/adservices/ondevicepersonalization/OnDevicePersonalizationException.java
@@ -15,7 +15,6 @@
*/
package android.adservices.ondevicepersonalization;
-
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
@@ -23,6 +22,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.Set;
/**
* Exception thrown by OnDevicePersonalization APIs.
@@ -41,42 +41,80 @@
*/
public static final int ERROR_PERSONALIZATION_DISABLED = 2;
- /**
- * The ODP module was unable to load the {@link IsolatedService}.
- * @hide
+ /** The ODP module was unable to load the {@link IsolatedService}.
+ *
+ * <p> Retrying may be successful for platform internal errors.
*/
- public static final int ERROR_ISOLATED_SERVICE_LOADING_FAILED = 3;
+ @FlaggedApi(Flags.FLAG_EXECUTE_IN_ISOLATED_SERVICE_API_ENABLED)
+ public static final int ERROR_ISOLATED_SERVICE_LOADING_FAILED = 3;
/**
* The ODP specific manifest settings for the {@link IsolatedService} are either missing or
* misconfigured.
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_EXECUTE_IN_ISOLATED_SERVICE_API_ENABLED)
public static final int ERROR_ISOLATED_SERVICE_MANIFEST_PARSING_FAILED = 4;
- /**
- * The {@link IsolatedService} was invoked but timed out before returning successfully.
- * @hide
+ /** The {@link IsolatedService} was invoked but timed out before returning successfully.
+ *
+ * <p> This is likely due to an issue with the {@link IsolatedWorker} implementation taking too
+ * long and retries are likely to fail.
*/
+ @FlaggedApi(Flags.FLAG_EXECUTE_IN_ISOLATED_SERVICE_API_ENABLED)
public static final int ERROR_ISOLATED_SERVICE_TIMEOUT = 5;
- /**
- * The {@link IsolatedService}'s output failed validation checks.
- * @hide
+ /** The {@link IsolatedService}'s call to {@link FederatedComputeScheduler#schedule} failed.
+ *
+ <p> Retrying may be successful if the issue is due to a platform internal error.
*/
- public static final int ERROR_OUTPUT_VALIDATION_FAILED = 6;
+ @FlaggedApi(Flags.FLAG_EXECUTE_IN_ISOLATED_SERVICE_API_ENABLED)
+ public static final int ERROR_SCHEDULE_TRAINING_FAILED = 6;
/**
- * The {@link IsolatedService}'s call to {@link FederatedComputeScheduler} failed.
- * @hide
+ * The {@link IsolatedService}'s call to {@link FederatedComputeScheduler#schedule} failed due
+ * to missing or misconfigured federated compute settings URL in the manifest.
*/
- public static final int ERROR_ISOLATED_SERVICE_FAILED_TRAINING = 7;
+ @FlaggedApi(Flags.FLAG_EXECUTE_IN_ISOLATED_SERVICE_API_ENABLED)
+ public static final int ERROR_INVALID_TRAINING_MANIFEST = 7;
+
+ /** Inference failed due to {@link ModelManager} not finding the downloaded model. */
+ @FlaggedApi(Flags.FLAG_EXECUTE_IN_ISOLATED_SERVICE_API_ENABLED)
+ public static final int ERROR_INFERENCE_MODEL_NOT_FOUND = 8;
+
+ /** {@link ModelManager} failed to run inference.
+ *
+ <p> Retrying may be successful if the issue is due to a platform internal error.
+ */
+ @FlaggedApi(Flags.FLAG_EXECUTE_IN_ISOLATED_SERVICE_API_ENABLED)
+ public static final int ERROR_INFERENCE_FAILED = 9;
/** @hide */
- @IntDef(prefix = "ERROR_", value = {
- ERROR_ISOLATED_SERVICE_FAILED,
- ERROR_PERSONALIZATION_DISABLED
- })
+ private static final Set<Integer> VALID_ERROR_CODE =
+ Set.of(
+ ERROR_ISOLATED_SERVICE_FAILED,
+ ERROR_PERSONALIZATION_DISABLED,
+ ERROR_ISOLATED_SERVICE_LOADING_FAILED,
+ ERROR_ISOLATED_SERVICE_MANIFEST_PARSING_FAILED,
+ ERROR_ISOLATED_SERVICE_TIMEOUT,
+ ERROR_SCHEDULE_TRAINING_FAILED,
+ ERROR_INVALID_TRAINING_MANIFEST,
+ ERROR_INFERENCE_MODEL_NOT_FOUND,
+ ERROR_INFERENCE_FAILED);
+
+ /** @hide */
+ @IntDef(
+ prefix = "ERROR_",
+ value = {
+ ERROR_ISOLATED_SERVICE_FAILED,
+ ERROR_PERSONALIZATION_DISABLED,
+ ERROR_ISOLATED_SERVICE_LOADING_FAILED,
+ ERROR_ISOLATED_SERVICE_MANIFEST_PARSING_FAILED,
+ ERROR_ISOLATED_SERVICE_TIMEOUT,
+ ERROR_SCHEDULE_TRAINING_FAILED,
+ ERROR_INVALID_TRAINING_MANIFEST,
+ ERROR_INFERENCE_MODEL_NOT_FOUND,
+ ERROR_INFERENCE_FAILED
+ })
@Retention(RetentionPolicy.SOURCE)
public @interface ErrorCode {}
@@ -101,8 +139,22 @@
mErrorCode = errorCode;
}
+ /** @hide */
+ public OnDevicePersonalizationException(
+ @ErrorCode int errorCode, String message, Throwable cause) {
+ super(message, cause);
+ mErrorCode = errorCode;
+ }
+
/** Returns the error code for this exception. */
public @ErrorCode int getErrorCode() {
return mErrorCode;
}
+
+ /**
+ * @hide Only used by internal error code validation.
+ */
+ public static boolean isValidErrorCode(int errorCode) {
+ return VALID_ERROR_CODE.contains(errorCode);
+ }
}
diff --git a/framework/java/android/adservices/ondevicepersonalization/OnDevicePersonalizationManager.java b/framework/java/android/adservices/ondevicepersonalization/OnDevicePersonalizationManager.java
index 5fa2297..28f8b7e 100644
--- a/framework/java/android/adservices/ondevicepersonalization/OnDevicePersonalizationManager.java
+++ b/framework/java/android/adservices/ondevicepersonalization/OnDevicePersonalizationManager.java
@@ -16,6 +16,7 @@
package android.adservices.ondevicepersonalization;
+
import android.adservices.ondevicepersonalization.aidl.IExecuteCallback;
import android.adservices.ondevicepersonalization.aidl.IOnDevicePersonalizationManagingService;
import android.adservices.ondevicepersonalization.aidl.IRequestSurfacePackageCallback;
@@ -39,6 +40,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.modules.utils.build.SdkLevel;
import com.android.ondevicepersonalization.internal.util.ByteArrayParceledSlice;
+import com.android.ondevicepersonalization.internal.util.ExceptionInfo;
import com.android.ondevicepersonalization.internal.util.LoggerFactory;
import com.android.ondevicepersonalization.internal.util.PersistableBundleUtils;
@@ -62,6 +64,7 @@
/** @hide */
public static final String ON_DEVICE_PERSONALIZATION_SERVICE =
"on_device_personalization_service";
+
private static final String INTENT_FILTER_ACTION = "android.OnDevicePersonalizationService";
private static final String ODP_MANAGING_SERVICE_PACKAGE_SUFFIX =
"com.android.ondevicepersonalization.services";
@@ -77,11 +80,22 @@
private static final String ODP_DISABLED_ERROR_MESSAGE =
"Personalization disabled by device configuration.";
+ private static final String ODP_MANIFEST_ERROR_MESSAGE =
+ "OnDevicePersonalization manifest invalid.";
+
+ private static final String ODP_SERVICE_LOADING_ERROR_MESSAGE =
+ "Failed to load the isolated service.";
+
+ private static final String ODP_SERVICE_TIMEOUT_ERROR_MESSAGE =
+ "The isolated service timed out without returning.";
+
private static final String TAG = OnDevicePersonalizationManager.class.getSimpleName();
private static final LoggerFactory.Logger sLogger = LoggerFactory.getLogger();
private final AbstractServiceBinder<IOnDevicePersonalizationManagingService> mServiceBinder;
private final Context mContext;
+ // TODO(b/358624224); deprecate {@link ExecuteResult} after partner migrates to use {@link
+ // #executeInIsolatedService}.
/**
* The result of a call to {@link OnDevicePersonalizationManager#execute(ComponentName,
* PersistableBundle, Executor, OutcomeReceiver)}
@@ -142,45 +156,42 @@
mServiceBinder = serviceBinder;
}
+ // TODO(b/358624224); deprecate {@link ExecuteResult} after partner migrates to use {@link
+ // #executeInIsolatedService}.
/**
- * Executes an {@link IsolatedService} in the OnDevicePersonalization sandbox. The
- * platform binds to the specified {@link IsolatedService} in an isolated process
- * and calls {@link IsolatedWorker#onExecute(ExecuteInput, android.os.OutcomeReceiver)}
- * with the caller-provided parameters. When the {@link IsolatedService} finishes execution,
- * the platform returns tokens that refer to the results from the service to the caller.
- * These tokens can be subsequently used to display results in a
- * {@link android.view.SurfaceView} within the calling app.
+ * Executes an {@link IsolatedService} in the OnDevicePersonalization sandbox. The platform
+ * binds to the specified {@link IsolatedService} in an isolated process and calls {@link
+ * IsolatedWorker#onExecute(ExecuteInput, android.os.OutcomeReceiver)} with the caller-provided
+ * parameters. When the {@link IsolatedService} finishes execution, the platform returns tokens
+ * that refer to the results from the service to the caller. These tokens can be subsequently
+ * used to display results in a {@link android.view.SurfaceView} within the calling app.
*
* @param service The {@link ComponentName} of the {@link IsolatedService}.
- * @param params a {@link PersistableBundle} that is passed from the calling app to the
- * {@link IsolatedService}. The expected contents of this parameter are defined
- * by the{@link IsolatedService}. The platform does not interpret this parameter.
+ * @param params a {@link PersistableBundle} that is passed from the calling app to the {@link
+ * IsolatedService}. The expected contents of this parameter are defined by the{@link
+ * IsolatedService}. The platform does not interpret this parameter.
* @param executor the {@link Executor} on which to invoke the callback.
- * @param receiver This returns a {@link ExecuteResult} object on success or an
- * {@link Exception} on failure. If the
- * {@link IsolatedService} returned a {@link RenderingConfig} to be displayed,
- * {@link ExecuteResult#getSurfacePackageToken()} will return a non-null
- * {@link SurfacePackageToken}.
- * The {@link SurfacePackageToken} object can be used in a subsequent
- * {@link #requestSurfacePackage(SurfacePackageToken, IBinder, int, int, int, Executor,
- * OutcomeReceiver)} call to display the result in a view. The returned
- * {@link SurfacePackageToken} may be null to indicate that no output is expected to be
- * displayed for this request. If the {@link IsolatedService} has returned any output data
- * and the calling app is allowlisted to receive data from this service, the
- * {@link ExecuteResult#getOutputData()} will return a non-null byte array.
- *
- * In case of an error, the receiver returns one of the following exceptions:
- * Returns a {@link android.content.pm.PackageManager.NameNotFoundException} if the handler
- * package is not installed or does not have a valid ODP manifest.
- * Returns {@link ClassNotFoundException} if the handler class is not found.
- * Returns an {@link OnDevicePersonalizationException} if execution of the handler fails.
+ * @param receiver This returns a {@link ExecuteResult} object on success or an {@link
+ * Exception} on failure. If the {@link IsolatedService} returned a {@link RenderingConfig}
+ * to be displayed, {@link ExecuteResult#getSurfacePackageToken()} will return a non-null
+ * {@link SurfacePackageToken}. The {@link SurfacePackageToken} object can be used in a
+ * subsequent {@link #requestSurfacePackage(SurfacePackageToken, IBinder, int, int, int,
+ * Executor, OutcomeReceiver)} call to display the result in a view. The returned {@link
+ * SurfacePackageToken} may be null to indicate that no output is expected to be displayed
+ * for this request. If the {@link IsolatedService} has returned any output data and the
+ * calling app is allowlisted to receive data from this service, the {@link
+ * ExecuteResult#getOutputData()} will return a non-null byte array.
+ * <p>In case of an error, the receiver returns one of the following exceptions: Returns a
+ * {@link android.content.pm.PackageManager.NameNotFoundException} if the handler package is
+ * not installed or does not have a valid ODP manifest. Returns {@link
+ * ClassNotFoundException} if the handler class is not found. Returns an {@link
+ * OnDevicePersonalizationException} if execution of the handler fails.
*/
public void execute(
@NonNull ComponentName service,
@NonNull PersistableBundle params,
@NonNull @CallbackExecutor Executor executor,
- @NonNull OutcomeReceiver<ExecuteResult, Exception> receiver
- ) {
+ @NonNull OutcomeReceiver<ExecuteResult, Exception> receiver) {
Objects.requireNonNull(service);
Objects.requireNonNull(params);
Objects.requireNonNull(executor);
@@ -223,12 +234,9 @@
tokenString);
}
}
- byte[] data =
- callbackResult.getByteArray(
- Constants.EXTRA_OUTPUT_DATA);
receiver.onResult(
new ExecuteResult(
- surfacePackageToken, data));
+ surfacePackageToken, null));
} catch (Exception e) {
receiver.onError(e);
}
@@ -240,7 +248,8 @@
service.getPackageName(),
Constants.API_NAME_EXECUTE,
SystemClock.elapsedRealtime() - startTimeMillis,
- calleeMetadata.getServiceEntryTimeMillis() - startTimeMillis,
+ calleeMetadata.getServiceEntryTimeMillis()
+ - startTimeMillis,
SystemClock.elapsedRealtime()
- calleeMetadata.getCallbackInvokeTimeMillis(),
Constants.STATUS_SUCCESS);
@@ -248,17 +257,20 @@
}
@Override
- public void onError(int errorCode, int isolatedServiceErrorCode,
- String message, CalleeMetadata calleeMetadata) {
+ public void onError(
+ int errorCode,
+ int isolatedServiceErrorCode,
+ byte[] serializedExceptionInfo,
+ CalleeMetadata calleeMetadata) {
final long token = Binder.clearCallingIdentity();
try {
executor.execute(
- () ->
- receiver.onError(
- createException(
- errorCode,
- isolatedServiceErrorCode,
- message)));
+ () -> {
+ receiver.onError(
+ createException(
+ errorCode, isolatedServiceErrorCode,
+ serializedExceptionInfo, mContext));
+ });
} finally {
Binder.restoreCallingIdentity(token);
logApiCallStats(
@@ -266,7 +278,8 @@
service.getPackageName(),
Constants.API_NAME_EXECUTE,
SystemClock.elapsedRealtime() - startTimeMillis,
- calleeMetadata.getServiceEntryTimeMillis() - startTimeMillis,
+ calleeMetadata.getServiceEntryTimeMillis()
+ - startTimeMillis,
SystemClock.elapsedRealtime()
- calleeMetadata.getCallbackInvokeTimeMillis(),
errorCode);
@@ -283,6 +296,7 @@
service,
wrappedParams,
new CallerMetadata.Builder().setStartTimeMillis(startTimeMillis).build(),
+ ExecuteOptionsParcel.DEFAULT,
callbackWrapper);
} catch (Exception e) {
logApiCallStats(
@@ -302,6 +316,159 @@
}
/**
+ * Executes an {@link IsolatedService} in the OnDevicePersonalization sandbox. The platform
+ * binds to the specified {@link IsolatedService} in an isolated process and calls {@link
+ * IsolatedWorker#onExecute(ExecuteInput, android.os.OutcomeReceiver)} with the caller-provided
+ * parameters. When the {@link IsolatedService} finishes execution, the platform returns tokens
+ * that refer to the results from the service to the caller. These tokens can be subsequently
+ * used to display results in a {@link android.view.SurfaceView} within the calling app.
+ *
+ * @param request the {@link ExecuteInIsolatedServiceRequest} request
+ * @param executor the {@link Executor} on which to invoke the callback.
+ * @param receiver This returns a {@link ExecuteInIsolatedServiceResponse} object on success or
+ * an {@link Exception} on failure. For success case, refer to {@link
+ * ExecuteInIsolatedServiceResponse}. For error case, the receiver returns an {@link
+ * OnDevicePersonalizationException} if execution of the handler fails.
+ */
+ @FlaggedApi(Flags.FLAG_EXECUTE_IN_ISOLATED_SERVICE_API_ENABLED)
+ public void executeInIsolatedService(
+ @NonNull ExecuteInIsolatedServiceRequest request,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OutcomeReceiver<ExecuteInIsolatedServiceResponse, Exception> receiver) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(receiver);
+ validateRequest(request);
+ long startTimeMillis = SystemClock.elapsedRealtime();
+
+ try {
+ final IOnDevicePersonalizationManagingService odpService =
+ mServiceBinder.getService(executor);
+
+ try {
+ IExecuteCallback callbackWrapper =
+ new IExecuteCallback.Stub() {
+ @Override
+ public void onSuccess(
+ Bundle callbackResult, CalleeMetadata calleeMetadata) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ executor.execute(
+ () -> {
+ try {
+ SurfacePackageToken surfacePackageToken = null;
+ if (callbackResult != null) {
+ String tokenString =
+ callbackResult.getString(
+ Constants
+ .EXTRA_SURFACE_PACKAGE_TOKEN_STRING);
+ if (tokenString != null
+ && !tokenString.isBlank()) {
+ surfacePackageToken =
+ new SurfacePackageToken(
+ tokenString);
+ }
+ }
+ int intValue = -1;
+ if (request.getOutputSpec().getOutputType()
+ == ExecuteInIsolatedServiceRequest
+ .OutputSpec
+ .OUTPUT_TYPE_BEST_VALUE) {
+ intValue =
+ callbackResult.getInt(
+ Constants
+ .EXTRA_OUTPUT_BEST_VALUE);
+ }
+
+ receiver.onResult(
+ new ExecuteInIsolatedServiceResponse(
+ surfacePackageToken, intValue));
+ } catch (Exception e) {
+ receiver.onError(e);
+ }
+ });
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ logApiCallStats(
+ odpService,
+ request.getService().getPackageName(),
+ Constants.API_NAME_EXECUTE,
+ SystemClock.elapsedRealtime() - startTimeMillis,
+ calleeMetadata.getServiceEntryTimeMillis()
+ - startTimeMillis,
+ SystemClock.elapsedRealtime()
+ - calleeMetadata.getCallbackInvokeTimeMillis(),
+ Constants.STATUS_SUCCESS);
+ }
+ }
+
+ @Override
+ public void onError(
+ int errorCode,
+ int isolatedServiceErrorCode,
+ byte[] serializedExceptionInfo,
+ CalleeMetadata calleeMetadata) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ executor.execute(
+ () -> {
+ receiver.onError(
+ // We can skip translating to legacy error
+ // codes for the new API.
+ createException(
+ errorCode,
+ isolatedServiceErrorCode,
+ serializedExceptionInfo,
+ mContext,
+ /* translateToLegacyErrorCode= */ false));
+ });
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ logApiCallStats(
+ odpService,
+ request.getService().getPackageName(),
+ Constants.API_NAME_EXECUTE,
+ SystemClock.elapsedRealtime() - startTimeMillis,
+ calleeMetadata.getServiceEntryTimeMillis()
+ - startTimeMillis,
+ SystemClock.elapsedRealtime()
+ - calleeMetadata.getCallbackInvokeTimeMillis(),
+ errorCode);
+ }
+ }
+ };
+
+ Bundle wrappedParams = new Bundle();
+ wrappedParams.putParcelable(
+ Constants.EXTRA_APP_PARAMS_SERIALIZED,
+ new ByteArrayParceledSlice(
+ PersistableBundleUtils.toByteArray(request.getAppParams())));
+ odpService.execute(
+ mContext.getPackageName(),
+ request.getService(),
+ wrappedParams,
+ new CallerMetadata.Builder().setStartTimeMillis(startTimeMillis).build(),
+ request.getOutputSpec() == null
+ ? ExecuteOptionsParcel.DEFAULT
+ : new ExecuteOptionsParcel(request.getOutputSpec()),
+ callbackWrapper);
+ } catch (Exception e) {
+ logApiCallStats(
+ odpService,
+ request.getService().getPackageName(),
+ Constants.API_NAME_EXECUTE,
+ SystemClock.elapsedRealtime() - startTimeMillis,
+ 0,
+ 0,
+ Constants.STATUS_INTERNAL_ERROR);
+ receiver.onError(e);
+ }
+
+ } catch (Exception e) {
+ receiver.onError(e);
+ }
+ }
+
+ /**
* Requests a {@link android.view.SurfaceControlViewHost.SurfacePackage} to be inserted into a
* {@link android.view.SurfaceView} inside the calling app. The surface package will contain an
* {@link android.view.View} with the content from a result of a prior call to
@@ -382,12 +549,14 @@
- calleeMetadata.getCallbackInvokeTimeMillis(),
Constants.STATUS_SUCCESS);
}
-
}
@Override
- public void onError(int errorCode, int isolatedServiceErrorCode,
- String message, CalleeMetadata calleeMetadata) {
+ public void onError(
+ int errorCode,
+ int isolatedServiceErrorCode,
+ byte[] serializedExceptionInfo,
+ CalleeMetadata calleeMetadata) {
final long token = Binder.clearCallingIdentity();
try {
executor.execute(
@@ -396,11 +565,13 @@
createException(
errorCode,
isolatedServiceErrorCode,
- message)));
+ serializedExceptionInfo,
+ mContext)));
} finally {
Binder.restoreCallingIdentity(token);
logApiCallStats(
- service, "",
+ service,
+ "",
Constants.API_NAME_REQUEST_SURFACE_PACKAGE,
SystemClock.elapsedRealtime() - startTimeMillis,
0,
@@ -445,13 +616,20 @@
}
}
- private static String convertMessage(int errorCode, String message) {
- // Defer to existing message received from service callback if it is non-empty, else
- // translate the internal error codes into error messages.
- if (message != null && !message.isBlank()) {
- return message;
+ private static void validateRequest(ExecuteInIsolatedServiceRequest request) {
+ Objects.requireNonNull(request.getService());
+ ComponentName service = request.getService();
+ Objects.requireNonNull(service.getPackageName());
+ Objects.requireNonNull(service.getClassName());
+ if (service.getPackageName().isEmpty()) {
+ throw new IllegalArgumentException("missing service package name");
}
+ if (service.getClassName().isEmpty()) {
+ throw new IllegalArgumentException("missing service class name");
+ }
+ }
+ private static String convertMessage(int errorCode) {
switch (errorCode) {
case Constants.STATUS_INTERNAL_ERROR:
return ODP_INTERNAL_ERROR_MESSAGE;
@@ -459,38 +637,124 @@
return ISOLATED_SERVICE_ERROR_MESSAGE;
case Constants.STATUS_PERSONALIZATION_DISABLED:
return ODP_DISABLED_ERROR_MESSAGE;
+ case Constants.STATUS_MANIFEST_PARSING_FAILED: // Intentional fallthrough
+ case Constants.STATUS_MANIFEST_MISCONFIGURED:
+ return ODP_MANIFEST_ERROR_MESSAGE;
+ case Constants.STATUS_ISOLATED_SERVICE_LOADING_FAILED:
+ return ODP_SERVICE_LOADING_ERROR_MESSAGE;
+ case Constants.STATUS_ISOLATED_SERVICE_TIMEOUT:
+ return ODP_SERVICE_TIMEOUT_ERROR_MESSAGE;
default:
sLogger.w(TAG + "Unexpected error code while creating exception: " + errorCode);
return "";
}
}
+ /**
+ * Convert granular error codes returned by the ODP Service to legacy error codes if required.
+ */
+ private static int translateErrorCode(int errorCode, Context context) {
+ if (errorCode < Constants.STATUS_MANIFEST_PARSING_FAILED) {
+ // Return code unchanged since either the error code does not require translation
+ // by virtue of being an old/original error code.
+ return errorCode;
+ }
+ // Translate to appropriate older error code if required.
+ sLogger.d(TAG, "Translating to legacy error codes for package " + context.getPackageName());
+ // TODO (b/342672147): add translation for newer error codes
+ int translatedCode = Constants.STATUS_INTERNAL_ERROR;
+ switch (errorCode) {
+ case Constants.STATUS_MANIFEST_PARSING_FAILED ->
+ translatedCode = Constants.STATUS_NAME_NOT_FOUND;
+ case Constants.STATUS_MANIFEST_MISCONFIGURED ->
+ translatedCode = Constants.STATUS_CLASS_NOT_FOUND;
+ case Constants.STATUS_ISOLATED_SERVICE_LOADING_FAILED ->
+ translatedCode = Constants.STATUS_SERVICE_FAILED;
+ case Constants.STATUS_ISOLATED_SERVICE_TIMEOUT ->
+ translatedCode = Constants.STATUS_SERVICE_FAILED;
+ }
+ return translatedCode;
+ }
+
+ /**
+ * Helper method to create appropriate Exception that translates error codes to legacy error
+ * codes for compatibility.
+ */
private static Exception createException(
- int errorCode, int isolatedServiceErrorCode, String message) {
- if (errorCode == Constants.STATUS_NAME_NOT_FOUND) {
- return new PackageManager.NameNotFoundException();
- } else if (errorCode == Constants.STATUS_CLASS_NOT_FOUND) {
- return new ClassNotFoundException();
- } else if (errorCode == Constants.STATUS_SERVICE_FAILED) {
- if (isolatedServiceErrorCode > 0 && isolatedServiceErrorCode < 128) {
+ int errorCode,
+ int isolatedServiceErrorCode,
+ byte[] serializedExceptionInfo,
+ Context context) {
+ return createException(
+ errorCode,
+ isolatedServiceErrorCode,
+ serializedExceptionInfo,
+ context,
+ /* translateToLegacyErrorCode= */ true);
+ }
+
+ private static Exception createException(
+ int errorCode,
+ int isolatedServiceErrorCode,
+ byte[] serializedExceptionInfo,
+ Context context,
+ boolean translateToLegacyErrorCode) {
+ if (translateToLegacyErrorCode) {
+ errorCode = translateErrorCode(errorCode, context);
+ }
+ Exception cause = ExceptionInfo.fromByteArray(serializedExceptionInfo);
+ switch (errorCode) {
+ case Constants.STATUS_NAME_NOT_FOUND:
+ Exception e = new PackageManager.NameNotFoundException();
+ try {
+ // NameNotFoundException does not have a constructor that takes a Throwable.
+ if (cause != null) {
+ e.initCause(cause);
+ }
+ } catch (Exception e2) {
+ sLogger.i(TAG + ": could not update cause", e2);
+ }
+ return e;
+ case Constants.STATUS_CLASS_NOT_FOUND:
+ return new ClassNotFoundException("", cause);
+ case Constants.STATUS_SERVICE_FAILED:
+ return (isolatedServiceErrorCode > 0 && isolatedServiceErrorCode < 128)
+ ? new OnDevicePersonalizationException(
+ OnDevicePersonalizationException.ERROR_ISOLATED_SERVICE_FAILED,
+ new IsolatedServiceException(isolatedServiceErrorCode))
+ : new OnDevicePersonalizationException(
+ OnDevicePersonalizationException.ERROR_ISOLATED_SERVICE_FAILED,
+ convertMessage(errorCode),
+ cause);
+ case Constants.STATUS_PERSONALIZATION_DISABLED:
return new OnDevicePersonalizationException(
- OnDevicePersonalizationException.ERROR_ISOLATED_SERVICE_FAILED,
- new IsolatedServiceException(isolatedServiceErrorCode));
- } else {
+ OnDevicePersonalizationException.ERROR_PERSONALIZATION_DISABLED,
+ convertMessage(errorCode),
+ cause);
+ case Constants.STATUS_MANIFEST_PARSING_FAILED:
+ // Intentional fallthrough
+ case Constants.STATUS_MANIFEST_MISCONFIGURED:
return new OnDevicePersonalizationException(
- OnDevicePersonalizationException.ERROR_ISOLATED_SERVICE_FAILED,
- convertMessage(errorCode, message));
- }
- } else if (errorCode == Constants.STATUS_PERSONALIZATION_DISABLED) {
- return new OnDevicePersonalizationException(
- OnDevicePersonalizationException.ERROR_PERSONALIZATION_DISABLED,
- convertMessage(errorCode, message));
- } else {
- return new IllegalStateException(convertMessage(errorCode, message));
+ OnDevicePersonalizationException
+ .ERROR_ISOLATED_SERVICE_MANIFEST_PARSING_FAILED,
+ convertMessage(errorCode),
+ cause);
+ case Constants.STATUS_ISOLATED_SERVICE_LOADING_FAILED:
+ return new OnDevicePersonalizationException(
+ OnDevicePersonalizationException.ERROR_ISOLATED_SERVICE_LOADING_FAILED,
+ convertMessage(errorCode),
+ cause);
+ case Constants.STATUS_ISOLATED_SERVICE_TIMEOUT:
+ return new OnDevicePersonalizationException(
+ OnDevicePersonalizationException.ERROR_ISOLATED_SERVICE_TIMEOUT,
+ convertMessage(errorCode),
+ cause);
+ default:
+ return new IllegalStateException(convertMessage(errorCode), cause);
}
}
- private void logApiCallStats(
+ private static void logApiCallStats(
IOnDevicePersonalizationManagingService service,
String sdkPackageName,
int apiName,
diff --git a/framework/java/android/adservices/ondevicepersonalization/RemoteDataImpl.java b/framework/java/android/adservices/ondevicepersonalization/RemoteDataImpl.java
index aaa32ef..fabc9b7 100644
--- a/framework/java/android/adservices/ondevicepersonalization/RemoteDataImpl.java
+++ b/framework/java/android/adservices/ondevicepersonalization/RemoteDataImpl.java
@@ -51,7 +51,7 @@
final long startTimeMillis = System.currentTimeMillis();
int responseCode = Constants.STATUS_SUCCESS;
try {
- BlockingQueue<Bundle> asyncResult = new ArrayBlockingQueue<>(1);
+ BlockingQueue<CallbackResult> asyncResult = new ArrayBlockingQueue<>(1);
Bundle params = new Bundle();
params.putString(Constants.EXTRA_LOOKUP_KEYS, key);
mDataAccessService.onRequest(
@@ -60,22 +60,30 @@
new IDataAccessServiceCallback.Stub() {
@Override
public void onSuccess(@NonNull Bundle result) {
- if (result != null) {
- asyncResult.add(result);
- } else {
- asyncResult.add(Bundle.EMPTY);
- }
+ asyncResult.add(new CallbackResult(result, 0));
}
@Override
public void onError(int errorCode) {
- asyncResult.add(Bundle.EMPTY);
+ asyncResult.add(new CallbackResult(Bundle.EMPTY, errorCode));
}
});
- Bundle result = asyncResult.take();
- ByteArrayParceledSlice data = result.getParcelable(
- Constants.EXTRA_RESULT, ByteArrayParceledSlice.class);
- return (data == null) ? null : data.getByteArray();
+
+ CallbackResult callbackResult = asyncResult.take();
+ if (callbackResult.mErrorCode != 0) {
+ responseCode = Constants.STATUS_INTERNAL_ERROR;
+ return null;
+ }
+ Bundle result = callbackResult.mResult;
+ if (result == null
+ || result.getParcelable(Constants.EXTRA_RESULT, ByteArrayParceledSlice.class)
+ == null) {
+ responseCode = Constants.STATUS_SUCCESS_EMPTY_RESULT;
+ return null;
+ }
+ ByteArrayParceledSlice data =
+ result.getParcelable(Constants.EXTRA_RESULT, ByteArrayParceledSlice.class);
+ return data.getByteArray();
} catch (InterruptedException | RemoteException e) {
sLogger.e(TAG + ": Failed to retrieve key from remoteData", e);
responseCode = Constants.STATUS_INTERNAL_ERROR;
@@ -97,34 +105,36 @@
final long startTimeMillis = System.currentTimeMillis();
int responseCode = Constants.STATUS_SUCCESS;
try {
- BlockingQueue<Bundle> asyncResult = new ArrayBlockingQueue<>(1);
+ BlockingQueue<CallbackResult> asyncResult = new ArrayBlockingQueue<>(1);
mDataAccessService.onRequest(
Constants.DATA_ACCESS_OP_REMOTE_DATA_KEYSET,
Bundle.EMPTY,
new IDataAccessServiceCallback.Stub() {
@Override
public void onSuccess(@NonNull Bundle result) {
- if (result != null) {
- asyncResult.add(result);
- } else {
- asyncResult.add(Bundle.EMPTY);
- }
+ asyncResult.add(new CallbackResult(result, 0));
}
@Override
public void onError(int errorCode) {
- asyncResult.add(Bundle.EMPTY);
+ asyncResult.add(new CallbackResult(null, errorCode));
}
});
- Bundle result = asyncResult.take();
- HashSet<String> resultSet =
- result.getSerializable(Constants.EXTRA_RESULT, HashSet.class);
- if (null == resultSet) {
+ CallbackResult callbackResult = asyncResult.take();
+ if (callbackResult.mErrorCode != 0) {
+ responseCode = callbackResult.mErrorCode;
return Collections.emptySet();
}
- return resultSet;
+ Bundle result = callbackResult.mResult;
+ if (result == null
+ || result.getSerializable(Constants.EXTRA_RESULT, HashSet.class) == null) {
+ responseCode = Constants.STATUS_SUCCESS_EMPTY_RESULT;
+ return Collections.emptySet();
+ }
+ return result.getSerializable(Constants.EXTRA_RESULT, HashSet.class);
} catch (InterruptedException | RemoteException e) {
sLogger.e(TAG + ": Failed to retrieve keySet from remoteData", e);
+ responseCode = Constants.STATUS_INTERNAL_ERROR;
throw new IllegalStateException(e);
} finally {
try {
@@ -142,4 +152,14 @@
public int getTableId() {
return ModelId.TABLE_ID_REMOTE_DATA;
}
+
+ private static class CallbackResult {
+ final Bundle mResult;
+ final int mErrorCode;
+
+ CallbackResult(Bundle result, int errorCode) {
+ mResult = result;
+ mErrorCode = errorCode;
+ }
+ }
}
diff --git a/framework/java/android/adservices/ondevicepersonalization/RenderInput.java b/framework/java/android/adservices/ondevicepersonalization/RenderInput.java
index 9549c73..aabb94f 100644
--- a/framework/java/android/adservices/ondevicepersonalization/RenderInput.java
+++ b/framework/java/android/adservices/ondevicepersonalization/RenderInput.java
@@ -21,7 +21,6 @@
import android.annotation.Nullable;
import com.android.adservices.ondevicepersonalization.flags.Flags;
-import com.android.ondevicepersonalization.internal.util.DataClass;
/**
* The input data for
@@ -29,7 +28,6 @@
*
*/
@FlaggedApi(Flags.FLAG_ON_DEVICE_PERSONALIZATION_APIS_ENABLED)
-@DataClass(genBuilder = false, genHiddenConstructor = true, genEqualsHashCode = true)
public final class RenderInput {
/** The width of the slot. */
private int mWidth = 0;
@@ -48,21 +46,6 @@
this(parcel.getWidth(), parcel.getHeight(), parcel.getRenderingConfig());
}
-
-
- // Code below generated by codegen v1.0.23.
- //
- // DO NOT MODIFY!
- // CHECKSTYLE:OFF Generated code
- //
- // To regenerate run:
- // $ codegen $ANDROID_BUILD_TOP/packages/modules/OnDevicePersonalization/framework/java/android/adservices/ondevicepersonalization/RenderInput.java
- //
- // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
- // Settings > Editor > Code Style > Formatter Control
- //@formatter:off
-
-
/**
* Creates a new RenderInput.
*
@@ -73,9 +56,8 @@
* @param renderingConfig
* A {@link RenderingConfig} within an {@link ExecuteOutput} that was returned by
* {@link IsolatedWorker#onExecute(ExecuteInput, android.os.OutcomeReceiver)}.
- * @hide
*/
- @DataClass.Generated.Member
+ @FlaggedApi(Flags.FLAG_DATA_CLASS_MISSING_CTORS_AND_GETTERS_ENABLED)
public RenderInput(
int width,
int height,
@@ -90,7 +72,6 @@
/**
* The width of the slot.
*/
- @DataClass.Generated.Member
public int getWidth() {
return mWidth;
}
@@ -98,7 +79,6 @@
/**
* The height of the slot.
*/
- @DataClass.Generated.Member
public int getHeight() {
return mHeight;
}
@@ -107,13 +87,11 @@
* A {@link RenderingConfig} within an {@link ExecuteOutput} that was returned by
* {@link IsolatedWorker#onExecute(ExecuteInput, android.os.OutcomeReceiver)}.
*/
- @DataClass.Generated.Member
public @Nullable RenderingConfig getRenderingConfig() {
return mRenderingConfig;
}
@Override
- @DataClass.Generated.Member
public boolean equals(@Nullable Object o) {
// You can override field equality logic by defining either of the methods like:
// boolean fieldNameEquals(RenderInput other) { ... }
@@ -131,7 +109,6 @@
}
@Override
- @DataClass.Generated.Member
public int hashCode() {
// You can override field hashCode logic by defining methods like:
// int fieldNameHashCode() { ... }
@@ -142,17 +119,4 @@
_hash = 31 * _hash + java.util.Objects.hashCode(mRenderingConfig);
return _hash;
}
-
- @DataClass.Generated(
- time = 1704831946167L,
- codegenVersion = "1.0.23",
- sourceFile = "packages/modules/OnDevicePersonalization/framework/java/android/adservices/ondevicepersonalization/RenderInput.java",
- inputSignatures = "private int mWidth\nprivate int mHeight\n @android.annotation.Nullable android.adservices.ondevicepersonalization.RenderingConfig mRenderingConfig\nclass RenderInput extends java.lang.Object implements []\[email protected](genBuilder=false, genHiddenConstructor=true, genEqualsHashCode=true)")
- @Deprecated
- private void __metadata() {}
-
-
- //@formatter:on
- // End of generated code
-
}
diff --git a/framework/java/android/adservices/ondevicepersonalization/SurfacePackageToken.java b/framework/java/android/adservices/ondevicepersonalization/SurfacePackageToken.java
index d412de9..93d93e0 100644
--- a/framework/java/android/adservices/ondevicepersonalization/SurfacePackageToken.java
+++ b/framework/java/android/adservices/ondevicepersonalization/SurfacePackageToken.java
@@ -25,19 +25,20 @@
/**
* An opaque reference to content that can be displayed in a {@link android.view.SurfaceView}. This
* maps to a {@link RenderingConfig} returned by an {@link IsolatedService}.
- *
*/
@FlaggedApi(Flags.FLAG_ON_DEVICE_PERSONALIZATION_APIS_ENABLED)
public class SurfacePackageToken {
@NonNull private final String mTokenString;
- SurfacePackageToken(@NonNull String tokenString) {
+ /** @hide */
+ public SurfacePackageToken(@NonNull String tokenString) {
mTokenString = tokenString;
}
/** @hide */
@VisibleForTesting
- @NonNull public String getTokenString() {
+ @NonNull
+ public String getTokenString() {
return mTokenString;
}
}
diff --git a/framework/java/android/adservices/ondevicepersonalization/TrainingExamplesInput.java b/framework/java/android/adservices/ondevicepersonalization/TrainingExamplesInput.java
index 447d6d0..ddd332e 100644
--- a/framework/java/android/adservices/ondevicepersonalization/TrainingExamplesInput.java
+++ b/framework/java/android/adservices/ondevicepersonalization/TrainingExamplesInput.java
@@ -21,12 +21,11 @@
import android.annotation.Nullable;
import com.android.adservices.ondevicepersonalization.flags.Flags;
-import com.android.ondevicepersonalization.internal.util.AnnotationValidations;
-import com.android.ondevicepersonalization.internal.util.DataClass;
+
+import java.util.Objects;
/** The input data for {@link IsolatedWorker#onTrainingExamples}. */
@FlaggedApi(Flags.FLAG_ON_DEVICE_PERSONALIZATION_APIS_ENABLED)
-@DataClass(genBuilder = false, genHiddenConstructor = true, genEqualsHashCode = true)
public final class TrainingExamplesInput {
/**
* The name of the federated compute population. It should match the population name in {@link
@@ -63,19 +62,6 @@
parcel.getCollectionName());
}
- // Code below generated by codegen v1.0.23.
- //
- // DO NOT MODIFY!
- // CHECKSTYLE:OFF Generated code
- //
- // To regenerate run:
- // $ codegen
- // $ANDROID_BUILD_TOP/packages/modules/OnDevicePersonalization/framework/java/android/adservices/ondevicepersonalization/TrainingExamplesInput.java
- //
- // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
- // Settings > Editor > Code Style > Formatter Control
- // @formatter:off
-
/**
* Creates a new TrainingExamplesInput.
*
@@ -90,29 +76,23 @@
* OnDevicePersonalization will store it and pass it here for generating new training
* examples.
* @param collectionName The data collection name to use to create training examples.
- * @hide
*/
- @DataClass.Generated.Member
+ @FlaggedApi(Flags.FLAG_DATA_CLASS_MISSING_CTORS_AND_GETTERS_ENABLED)
public TrainingExamplesInput(
@NonNull String populationName,
@NonNull String taskName,
@Nullable byte[] resumptionToken,
@Nullable String collectionName) {
- this.mPopulationName = populationName;
- AnnotationValidations.validate(NonNull.class, null, mPopulationName);
- this.mTaskName = taskName;
- AnnotationValidations.validate(NonNull.class, null, mTaskName);
+ this.mPopulationName = Objects.requireNonNull(populationName);
+ this.mTaskName = Objects.requireNonNull(taskName);
this.mResumptionToken = resumptionToken;
this.mCollectionName = collectionName;
-
- // onConstructed(); // You can define this method to get a callback
}
/**
* The name of the federated compute population. It should match the population name in {@link
* FederatedComputeInput#getPopulationName}.
*/
- @DataClass.Generated.Member
public @NonNull String getPopulationName() {
return mPopulationName;
}
@@ -122,7 +102,6 @@
* federated compute server. One population may have multiple tasks. The task name can be used
* to uniquely identify the job.
*/
- @DataClass.Generated.Member
public @NonNull String getTaskName() {
return mTaskName;
}
@@ -133,25 +112,18 @@
* {@link TrainingExampleRecord.Builder#setResumptionToken}, OnDevicePersonalization will store
* it and pass it here for generating new training examples.
*/
- @DataClass.Generated.Member
public @Nullable byte[] getResumptionToken() {
return mResumptionToken;
}
/** The data collection name to use to create training examples. */
- @DataClass.Generated.Member
@FlaggedApi(Flags.FLAG_FCP_MODEL_VERSION_ENABLED)
public @Nullable String getCollectionName() {
return mCollectionName;
}
@Override
- @DataClass.Generated.Member
public boolean equals(@Nullable Object o) {
- // You can override field equality logic by defining either of the methods like:
- // boolean fieldNameEquals(TrainingExamplesInput other) { ... }
- // boolean fieldNameEquals(FieldType otherValue) { ... }
-
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
@SuppressWarnings("unchecked")
@@ -165,11 +137,7 @@
}
@Override
- @DataClass.Generated.Member
public int hashCode() {
- // You can override field hashCode logic by defining methods like:
- // int fieldNameHashCode() { ... }
-
int _hash = 1;
_hash = 31 * _hash + java.util.Objects.hashCode(mPopulationName);
_hash = 31 * _hash + java.util.Objects.hashCode(mTaskName);
@@ -177,18 +145,4 @@
_hash = 31 * _hash + java.util.Objects.hashCode(mCollectionName);
return _hash;
}
-
- @DataClass.Generated(
- time = 1717540629847L,
- codegenVersion = "1.0.23",
- sourceFile =
- "packages/modules/OnDevicePersonalization/framework/java/android/adservices/ondevicepersonalization/TrainingExamplesInput.java",
- inputSignatures =
- "private @android.annotation.NonNull java.lang.String mPopulationName\nprivate @android.annotation.NonNull java.lang.String mTaskName\nprivate @android.annotation.Nullable byte[] mResumptionToken\nprivate @android.annotation.Nullable java.lang.String mCollectionName\nclass TrainingExamplesInput extends java.lang.Object implements []\[email protected](genBuilder=false, genHiddenConstructor=true, genEqualsHashCode=true)")
- @Deprecated
- private void __metadata() {}
-
- // @formatter:on
- // End of generated code
-
}
diff --git a/framework/java/android/adservices/ondevicepersonalization/WebTriggerInput.java b/framework/java/android/adservices/ondevicepersonalization/WebTriggerInput.java
index 5c57cb6..b73bedb 100644
--- a/framework/java/android/adservices/ondevicepersonalization/WebTriggerInput.java
+++ b/framework/java/android/adservices/ondevicepersonalization/WebTriggerInput.java
@@ -21,20 +21,19 @@
import android.net.Uri;
import com.android.adservices.ondevicepersonalization.flags.Flags;
-import com.android.ondevicepersonalization.internal.util.AnnotationValidations;
-import com.android.ondevicepersonalization.internal.util.DataClass;
+
+import java.util.Objects;
/**
* The input data for
* {@link IsolatedWorker#onWebTrigger(WebTriggerInput, android.os.OutcomeReceiver)}.
*/
@FlaggedApi(Flags.FLAG_ON_DEVICE_PERSONALIZATION_APIS_ENABLED)
-@DataClass(genBuilder = false, genHiddenConstructor = true, genEqualsHashCode = true)
public final class WebTriggerInput {
/** The destination URL (landing page) where the trigger event occurred. */
@NonNull private Uri mDestinationUrl;
- /** The app where the trigger event occurred */
+ /** The package name of the app where the trigger event occurred */
@NonNull private String mAppPackageName;
/**
@@ -49,64 +48,38 @@
this(parcel.getDestinationUrl(), parcel.getAppPackageName(), parcel.getData());
}
-
-
- // Code below generated by codegen v1.0.23.
- //
- // DO NOT MODIFY!
- // CHECKSTYLE:OFF Generated code
- //
- // To regenerate run:
- // $ codegen $ANDROID_BUILD_TOP/packages/modules/OnDevicePersonalization/framework/java/android/adservices/ondevicepersonalization/WebTriggerInput.java
- //
- // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
- // Settings > Editor > Code Style > Formatter Control
- //@formatter:off
-
-
/**
* Creates a new WebTriggerInput.
*
* @param destinationUrl
* The destination URL (landing page) where the trigger event occurred.
* @param appPackageName
- * The app where the trigger event occurred
+ * The package name of the app where the trigger event occurred
* @param data
* Additional data returned by the server as part of the web trigger registration
* to be sent to the {@link IsolatedService}. This can be {@code null} if the server
* does not need to send data to the service for processing web triggers.
- * @hide
*/
- @DataClass.Generated.Member
+ @FlaggedApi(Flags.FLAG_DATA_CLASS_MISSING_CTORS_AND_GETTERS_ENABLED)
public WebTriggerInput(
@NonNull Uri destinationUrl,
@NonNull String appPackageName,
@NonNull byte[] data) {
- this.mDestinationUrl = destinationUrl;
- AnnotationValidations.validate(
- NonNull.class, null, mDestinationUrl);
- this.mAppPackageName = appPackageName;
- AnnotationValidations.validate(
- NonNull.class, null, mAppPackageName);
- this.mData = data;
- AnnotationValidations.validate(
- NonNull.class, null, mData);
-
- // onConstructed(); // You can define this method to get a callback
+ this.mDestinationUrl = Objects.requireNonNull(destinationUrl);
+ this.mAppPackageName = Objects.requireNonNull(appPackageName);
+ this.mData = Objects.requireNonNull(data);
}
/**
* The destination URL (landing page) where the trigger event occurred.
*/
- @DataClass.Generated.Member
public @NonNull Uri getDestinationUrl() {
return mDestinationUrl;
}
/**
- * The app where the trigger event occurred
+ * The package name of the app where the trigger event occurred
*/
- @DataClass.Generated.Member
public @NonNull String getAppPackageName() {
return mAppPackageName;
}
@@ -116,18 +89,12 @@
* to be sent to the {@link IsolatedService}. This can be {@code null} if the server
* does not need to send data to the service for processing web triggers.
*/
- @DataClass.Generated.Member
public @NonNull byte[] getData() {
return mData;
}
@Override
- @DataClass.Generated.Member
public boolean equals(@android.annotation.Nullable Object o) {
- // You can override field equality logic by defining either of the methods like:
- // boolean fieldNameEquals(WebTriggerInput other) { ... }
- // boolean fieldNameEquals(FieldType otherValue) { ... }
-
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
@SuppressWarnings("unchecked")
@@ -140,28 +107,11 @@
}
@Override
- @DataClass.Generated.Member
public int hashCode() {
- // You can override field hashCode logic by defining methods like:
- // int fieldNameHashCode() { ... }
-
int _hash = 1;
_hash = 31 * _hash + java.util.Objects.hashCode(mDestinationUrl);
_hash = 31 * _hash + java.util.Objects.hashCode(mAppPackageName);
_hash = 31 * _hash + java.util.Arrays.hashCode(mData);
return _hash;
}
-
- @DataClass.Generated(
- time = 1707513068642L,
- codegenVersion = "1.0.23",
- sourceFile = "packages/modules/OnDevicePersonalization/framework/java/android/adservices/ondevicepersonalization/WebTriggerInput.java",
- inputSignatures = "private @android.annotation.NonNull android.net.Uri mDestinationUrl\nprivate @android.annotation.NonNull java.lang.String mAppPackageName\nprivate @android.annotation.NonNull byte[] mData\nclass WebTriggerInput extends java.lang.Object implements []\[email protected](genBuilder=false, genHiddenConstructor=true, genEqualsHashCode=true)")
- @Deprecated
- private void __metadata() {}
-
-
- //@formatter:on
- // End of generated code
-
}
diff --git a/framework/java/android/adservices/ondevicepersonalization/aidl/IExecuteCallback.aidl b/framework/java/android/adservices/ondevicepersonalization/aidl/IExecuteCallback.aidl
index fb6a30c..a6b86bb 100644
--- a/framework/java/android/adservices/ondevicepersonalization/aidl/IExecuteCallback.aidl
+++ b/framework/java/android/adservices/ondevicepersonalization/aidl/IExecuteCallback.aidl
@@ -22,5 +22,9 @@
/** @hide */
oneway interface IExecuteCallback {
void onSuccess(in Bundle result, in CalleeMetadata calleeMetadata);
- void onError(int errorCode, int isolatedServiceErrorCode, String errorMessage, in CalleeMetadata calleeMetadata);
+ void onError(
+ in int errorCode,
+ in int isolatedServiceErrorCode,
+ in byte[] serializedExceptionInfo,
+ in CalleeMetadata calleeMetadata);
}
diff --git a/framework/java/android/adservices/ondevicepersonalization/aidl/IIsolatedServiceCallback.aidl b/framework/java/android/adservices/ondevicepersonalization/aidl/IIsolatedServiceCallback.aidl
index eaaed5c..10fabeb 100644
--- a/framework/java/android/adservices/ondevicepersonalization/aidl/IIsolatedServiceCallback.aidl
+++ b/framework/java/android/adservices/ondevicepersonalization/aidl/IIsolatedServiceCallback.aidl
@@ -21,5 +21,8 @@
/** @hide */
oneway interface IIsolatedServiceCallback {
void onSuccess(in Bundle result);
- void onError(int errorCode, int isolatedServiceErrorCode);
+ void onError(
+ int errorCode,
+ int isolatedServiceErrorCode,
+ in byte[] serializedExceptionInfo);
}
diff --git a/framework/java/android/adservices/ondevicepersonalization/aidl/IOnDevicePersonalizationConfigService.aidl b/framework/java/android/adservices/ondevicepersonalization/aidl/IOnDevicePersonalizationConfigService.aidl
deleted file mode 100644
index f2d04e9..0000000
--- a/framework/java/android/adservices/ondevicepersonalization/aidl/IOnDevicePersonalizationConfigService.aidl
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.adservices.ondevicepersonalization.aidl;
-
-import android.adservices.ondevicepersonalization.aidl.IOnDevicePersonalizationConfigServiceCallback;
-
-/**
- * OnDevicePersonalization service that modifies
- * ODP's enablement status by GMS Core only.
- * @hide
- */
-interface IOnDevicePersonalizationConfigService {
-
- void setPersonalizationStatus(in boolean enabled,
- in IOnDevicePersonalizationConfigServiceCallback callback);
-}
\ No newline at end of file
diff --git a/framework/java/android/adservices/ondevicepersonalization/aidl/IOnDevicePersonalizationManagingService.aidl b/framework/java/android/adservices/ondevicepersonalization/aidl/IOnDevicePersonalizationManagingService.aidl
index 1c89a0f..eac9a76 100644
--- a/framework/java/android/adservices/ondevicepersonalization/aidl/IOnDevicePersonalizationManagingService.aidl
+++ b/framework/java/android/adservices/ondevicepersonalization/aidl/IOnDevicePersonalizationManagingService.aidl
@@ -18,6 +18,7 @@
import android.content.ComponentName;
import android.adservices.ondevicepersonalization.CallerMetadata;
+import android.adservices.ondevicepersonalization.ExecuteOptionsParcel;
import android.adservices.ondevicepersonalization.aidl.IExecuteCallback;
import android.adservices.ondevicepersonalization.aidl.IRegisterMeasurementEventCallback;
import android.adservices.ondevicepersonalization.aidl.IRequestSurfacePackageCallback;
@@ -32,6 +33,7 @@
in ComponentName handler,
in Bundle wrappedParams,
in CallerMetadata metadata,
+ in ExecuteOptionsParcel options,
in IExecuteCallback callback);
void requestSurfacePackage(
diff --git a/framework/java/android/adservices/ondevicepersonalization/aidl/IRequestSurfacePackageCallback.aidl b/framework/java/android/adservices/ondevicepersonalization/aidl/IRequestSurfacePackageCallback.aidl
index e8bf21b..0e51830 100644
--- a/framework/java/android/adservices/ondevicepersonalization/aidl/IRequestSurfacePackageCallback.aidl
+++ b/framework/java/android/adservices/ondevicepersonalization/aidl/IRequestSurfacePackageCallback.aidl
@@ -22,6 +22,9 @@
/** @hide */
oneway interface IRequestSurfacePackageCallback {
void onSuccess(in SurfacePackage surfacePackage, in CalleeMetadata calleeMetadata);
- void onError(int errorCode, int isolatedServiceErrorCode, String errorMessage,
- in CalleeMetadata calleeMetadata);
+ void onError(
+ in int errorCode,
+ in int isolatedServiceErrorCode,
+ in byte[] serializedExceptionInfo,
+ in CalleeMetadata calleeMetadata);
}
diff --git a/framework/java/com/android/federatedcompute/internal/util/AndroidServiceBinder.java b/framework/java/com/android/federatedcompute/internal/util/AndroidServiceBinder.java
index 6b2d94c..91490df 100644
--- a/framework/java/com/android/federatedcompute/internal/util/AndroidServiceBinder.java
+++ b/framework/java/com/android/federatedcompute/internal/util/AndroidServiceBinder.java
@@ -39,6 +39,8 @@
private static final String TAG = AndroidServiceBinder.class.getSimpleName();
private static final int BINDER_CONNECTION_TIMEOUT_MS = 5000;
+ private static final int MAX_GET_SERVICE_RETRIES = 2;
+ private static final long GET_SERVICE_RETRY_DELAY_MS = 100L;
private final String mServiceIntentActionOrName;
private final List<String> mServicePackages;
private final Function<IBinder, T> mBinderConverter;
@@ -143,6 +145,36 @@
@Override
public T getService(@NonNull Executor executor) {
+ int retryAttempts = 0;
+ T service;
+ IllegalStateException exceptionInfo = null;
+
+ while (retryAttempts < MAX_GET_SERVICE_RETRIES) {
+ try {
+ service = getServiceWithoutRetry(executor);
+ if (service != null) {
+ return service;
+ }
+ } catch (IllegalStateException e) {
+ LogUtil.e(TAG, e, "Failed to get service on attempt " + (retryAttempts + 1));
+ exceptionInfo = e;
+ }
+ retryAttempts++;
+ try {
+ Thread.sleep(GET_SERVICE_RETRY_DELAY_MS);
+ } catch (InterruptedException e) {
+ LogUtil.w(TAG, "Thread sleep interrupted");
+ }
+ }
+
+ throw exceptionInfo != null
+ ? exceptionInfo
+ : new IllegalStateException(
+ String.format("Failed to get non-null service %s after %d retries",
+ mServiceIntentActionOrName, retryAttempts));
+ }
+
+ private T getServiceWithoutRetry(@NonNull Executor executor) {
synchronized (mLock) {
if (mService != null) {
return mService;
@@ -183,7 +215,8 @@
LogUtil.i(TAG, "bindService() %s already pending...", mServiceIntentActionOrName);
}
}
- // Release the lock to let the ServiceConnection set the mService
+ // Release the lock to let the ServiceConnection set the mService. If unbind race condition
+ // happen here (e.g. onBindingDied called) client should retry
try {
mConnectionCountDownLatch.await(BINDER_CONNECTION_TIMEOUT_MS, MILLISECONDS);
} catch (InterruptedException e) {
@@ -271,7 +304,11 @@
synchronized (mLock) {
if (mServiceConnection != null) {
LogUtil.d(TAG, "unbinding %s...", mServiceIntentActionOrName);
- mContext.unbindService(mServiceConnection);
+ try {
+ mContext.unbindService(mServiceConnection);
+ } catch (IllegalArgumentException e) {
+ LogUtil.e(TAG, e, "unbinding failed %s", mServiceIntentActionOrName);
+ }
}
mServiceConnection = null;
mService = null;
diff --git a/framework/java/com/android/ondevicepersonalization/internal/util/ExceptionInfo.java b/framework/java/com/android/ondevicepersonalization/internal/util/ExceptionInfo.java
new file mode 100644
index 0000000..3482c33
--- /dev/null
+++ b/framework/java/com/android/ondevicepersonalization/internal/util/ExceptionInfo.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ondevicepersonalization.internal.util;
+
+import android.annotation.NonNull;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Objects;
+
+/**
+ * Information about an exception chain in the ODP service or an IsolatedService.
+ * @hide
+ */
+public class ExceptionInfo implements Serializable {
+ /** @hide */
+ static class ExceptionInfoElement implements Serializable {
+ String mExceptionClass;
+ String mMessage;
+ StackTraceElement[] mStackTrace;
+
+ ExceptionInfoElement(@NonNull Throwable t) {
+ Objects.requireNonNull(t);
+ mExceptionClass = t.getClass().getName();
+ mMessage = t.getMessage();
+ mStackTrace = t.getStackTrace();
+ }
+ }
+
+ private ArrayList<ExceptionInfoElement> mElements;
+
+ ExceptionInfo(Throwable t, int maxDepth) {
+ Objects.requireNonNull(t);
+ mElements = new ArrayList<>();
+ int count = 0;
+ while (t != null && count < maxDepth) {
+ mElements.add(new ExceptionInfoElement(t));
+ t = t.getCause();
+ ++count;
+ }
+ }
+
+ /** Serialize to byte array. */
+ public static byte[] toByteArray(Throwable t, int maxDepth) {
+ if (t == null) {
+ return null;
+ }
+ try {
+ ExceptionInfo info = new ExceptionInfo(t, maxDepth);
+ try (ByteArrayOutputStream bs = new ByteArrayOutputStream();
+ ObjectOutputStream os = new ObjectOutputStream(bs)) {
+ os.writeObject(info);
+ return bs.toByteArray();
+ }
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ /** Deserialize from byte array. */
+ public static Exception fromByteArray(byte[] bytes) {
+ if (bytes == null) {
+ return null;
+ }
+ try (ByteArrayInputStream bs = new ByteArrayInputStream(bytes);
+ ObjectInputStream os = new ObjectInputStream(bs)) {
+ ExceptionInfo info = (ExceptionInfo) os.readObject();
+ return info.toException();
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ Exception toException() {
+ try {
+ Exception e = null;
+ for (int i = mElements.size() - 1; i >= 0; --i) {
+ ExceptionInfoElement element = mElements.get(i);
+ Exception tmp = new Exception(element.mExceptionClass + ": " + element.mMessage, e);
+ tmp.setStackTrace(element.mStackTrace);
+ e = tmp;
+ }
+ return e;
+ } catch (Exception e) {
+ return null;
+ }
+ }
+}
diff --git a/samples/odpclient/src/main/AndroidManifest.xml b/samples/odpclient/src/main/AndroidManifest.xml
index 7b63946..a10e3d8 100644
--- a/samples/odpclient/src/main/AndroidManifest.xml
+++ b/samples/odpclient/src/main/AndroidManifest.xml
@@ -17,10 +17,10 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.odpclient"
- android:versionName="1.1.2" >
+ android:versionName="1.1.3" >
<queries>
<package android:name="com.example.odpsamplenetwork" />
- </queries>>
+ </queries>
<application
android:label="@string/title_activity_main">
diff --git a/samples/odpclient/src/main/java/com/example/odpclient/MainActivity.java b/samples/odpclient/src/main/java/com/example/odpclient/MainActivity.java
index 2f09dfb..258f1f1 100644
--- a/samples/odpclient/src/main/java/com/example/odpclient/MainActivity.java
+++ b/samples/odpclient/src/main/java/com/example/odpclient/MainActivity.java
@@ -30,6 +30,7 @@
import android.os.OutcomeReceiver;
import android.os.PersistableBundle;
import android.os.Trace;
+import android.text.method.ScrollingMovementMethod;
import android.util.Log;
import android.view.SurfaceControlViewHost.SurfacePackage;
import android.view.SurfaceHolder;
@@ -37,10 +38,13 @@
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
-import android.widget.Toast;
+import android.widget.TextView;
+import android.widget.ViewSwitcher;
import com.google.common.util.concurrent.Futures;
+import java.io.PrintWriter;
+import java.io.StringWriter;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
@@ -52,7 +56,8 @@
private static final String SERVICE_CLASS = "com.example.odpsamplenetwork.SampleService";
private static final String ODP_APEX = "com.google.android.ondevicepersonalization";
private static final String ADSERVICES_APEX = "com.google.android.adservices";
-
+ private static final int SURFACE_VIEW_INDEX = 0;
+ private static final int MESSAGE_BOX_INDEX = 1;
private EditText mTextBox;
private Button mGetAdButton;
private EditText mScheduleTrainingTextBox;
@@ -62,6 +67,8 @@
private EditText mReportConversionTextBox;
private Button mReportConversionButton;
private SurfaceView mRenderedView;
+ private TextView mMessageBox;
+ private ViewSwitcher mViewSwitcher;
private Context mContext;
private static Executor sCallbackExecutor = Executors.newSingleThreadExecutor();
@@ -95,6 +102,9 @@
mScheduleTrainingTextBox = findViewById(R.id.schedule_training_text_box);
mScheduleIntervalTextBox = findViewById(R.id.schedule_interval_text_box);
mReportConversionTextBox = findViewById(R.id.report_conversion_text_box);
+ mMessageBox = findViewById(R.id.message_box);
+ mMessageBox.setMovementMethod(new ScrollingMovementMethod());
+ mViewSwitcher = findViewById(R.id.view_switcher);
registerGetAdButton();
registerScheduleTrainingButton();
registerReportConversionButton();
@@ -114,7 +124,7 @@
mReportConversionButton.setOnClickListener(v -> reportConversion());
}
- private OnDevicePersonalizationManager getOdpManager() {
+ private OnDevicePersonalizationManager getOdpManager() throws NoClassDefFoundError {
return mContext.getSystemService(OnDevicePersonalizationManager.class);
}
@@ -125,7 +135,7 @@
Log.i(TAG, "Starting execute() " + getResources().getString(R.string.get_ad)
+ " with " + mTextBox.getHint().toString() + ": "
+ mTextBox.getText().toString());
- AtomicReference<ExecuteResult> slotResultHandle = new AtomicReference<>();
+ AtomicReference<ExecuteResult> executeResult = new AtomicReference<>();
PersistableBundle appParams = new PersistableBundle();
appParams.putString("keyword", mTextBox.getText().toString());
@@ -142,26 +152,33 @@
Trace.endAsyncSection("OdpClient:makeRequest:odpManager.execute", 0);
Log.i(TAG, "execute() success: " + result);
if (result != null) {
- slotResultHandle.set(result);
+ executeResult.set(result);
} else {
Log.e(TAG, "No results!");
}
+ clearText();
latch.countDown();
}
@Override
public void onError(Exception e) {
Trace.endAsyncSection("OdpClient:makeRequest:odpManager.execute", 0);
- makeToast("execute() error: " + e.toString());
+ showError("OdpClient:makeRequest:odpManager.execute", e);
latch.countDown();
}
});
latch.await();
Log.d(TAG, "makeRequest:odpManager.execute wait success");
+ if (executeResult.get() == null
+ || executeResult.get().getSurfacePackageToken() == null) {
+ Log.i(TAG, "No surfacePackageToken returned, skipping render.");
+ return;
+ }
+
Trace.beginAsyncSection("OdpClient:makeRequest:odpManager.requestSurfacePackage", 0);
odpManager.requestSurfacePackage(
- slotResultHandle.get().getSurfacePackageToken(),
+ executeResult.get().getSurfacePackageToken(),
mRenderedView.getHostToken(),
getDisplay().getDisplayId(),
mRenderedView.getWidth(),
@@ -175,6 +192,7 @@
Log.i(TAG,
"requestSurfacePackage() success: "
+ surfacePackage.toString());
+ clearText();
new Handler(Looper.getMainLooper()).post(() -> {
if (surfacePackage != null) {
mRenderedView.setChildSurfacePackage(
@@ -182,6 +200,7 @@
}
mRenderedView.setZOrderOnTop(true);
mRenderedView.setVisibility(View.VISIBLE);
+ mViewSwitcher.setDisplayedChild(SURFACE_VIEW_INDEX);
});
}
@@ -189,11 +208,12 @@
public void onError(Exception e) {
Trace.endAsyncSection(
"OdpClient:makeRequest:odpManager.requestSurfacePackage", 0);
- makeToast("requestSurfacePackage() error: " + e.toString());
+ showError(
+ "OdpClient:makeRequest:odpManager.requestSurfacePackage", e);
}
});
- } catch (Exception e) {
- Log.e(TAG, "Error", e);
+ } catch (Throwable e) {
+ showError("makeRequest", e);
}
}
@@ -237,6 +257,7 @@
Trace.endAsyncSection(
"OdpClient:scheduleTraining:odpManager.execute", 0);
Log.i(TAG, "execute() success: " + result);
+ clearText();
latch.countDown();
}
@@ -244,14 +265,14 @@
public void onError(Exception e) {
Trace.endAsyncSection(
"OdpClient:scheduleTraining:odpManager.execute", 0);
- makeToast("execute() error: " + e.toString());
+ showError("OdpClient:scheduleTraining:odpManager.execute", e);
latch.countDown();
}
});
latch.await();
Log.d(TAG, "scheduleTraining:odpManager.execute wait success");
- } catch (Exception e) {
- Log.e(TAG, "Error", e);
+ } catch (Throwable e) {
+ showError("scheduleTraining", e);
}
}
@@ -284,6 +305,7 @@
Trace.endAsyncSection(
"OdpClient:cancelTraining:odpManager.execute", 0);
Log.i(TAG, "execute() success: " + result);
+ clearText();
latch.countDown();
}
@@ -291,14 +313,14 @@
public void onError(Exception e) {
Trace.endAsyncSection(
"OdpClient:cancelTraining:odpManager.execute", 0);
- makeToast("execute() error: " + e.toString());
+ showError("OdpClient:cancelTraining:odpManager.execute", e);
latch.countDown();
}
});
latch.await();
Log.d(TAG, "cancelTraining:odpManager.execute wait success");
- } catch (Exception e) {
- Log.e(TAG, "Error", e);
+ } catch (Throwable e) {
+ showError("cancelTraining", e);
}
}
@@ -325,6 +347,7 @@
Trace.endAsyncSection(
"OdpClient:reportConversion:odpManager.execute", 0);
Log.i(TAG, "execute() success: " + result);
+ clearText();
latch.countDown();
}
@@ -332,20 +355,36 @@
public void onError(Exception e) {
Trace.endAsyncSection(
"OdpClient:reportConversion:odpManager.execute", 0);
- makeToast("execute() error: " + e.toString());
+ showError("OdpClient:reportConversion:odpManager.execute", e);
latch.countDown();
}
});
latch.await();
Log.d(TAG, "reportConversion:odpManager.execute wait success");
- } catch (Exception e) {
- Log.e(TAG, "Error", e);
+ } catch (Throwable e) {
+ showError("reportConversion", e);
}
}
- private void makeToast(String message) {
- Log.i(TAG, message);
- runOnUiThread(() -> Toast.makeText(MainActivity.this, message, Toast.LENGTH_LONG).show());
+ private void showError(String message, Throwable e) {
+ Log.i(TAG, "Error: " + message, e);
+ StringWriter out = new StringWriter();
+ PrintWriter pw = new PrintWriter(out);
+ pw.println("Error: " + message);
+ e.printStackTrace(pw);
+ pw.flush();
+ showText(out.toString());
+ }
+
+ private void showText(String s) {
+ runOnUiThread(() -> {
+ mMessageBox.setText(s);
+ mViewSwitcher.setDisplayedChild(MESSAGE_BOX_INDEX);
+ });
+ }
+
+ private void clearText() {
+ runOnUiThread(() -> mMessageBox.setText(""));
}
@Override
@@ -396,7 +435,7 @@
String versionName = packageInfo.versionName;
Log.i(TAG, "packageName: " + packageName + ", versionName: " + versionName);
} catch (PackageManager.NameNotFoundException e) {
- Log.e(TAG, "can't find package name " + packageName);
+ showError("can't find package name " + packageName, e);
}
}
@@ -409,7 +448,7 @@
Log.i(TAG, "apexName: " + apexName + ", longVersionCode: " + apexVersionCode);
}
} catch (PackageManager.NameNotFoundException e) {
- Log.e(TAG, "apex " + apexName + " not found");
+ showError("apex " + apexName + " not found", e);
}
}
diff --git a/samples/odpclient/src/main/res/drawable/border.xml b/samples/odpclient/src/main/res/drawable/border.xml
new file mode 100644
index 0000000..b552f83
--- /dev/null
+++ b/samples/odpclient/src/main/res/drawable/border.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle" >
+ <stroke
+ android:width="1dp"
+ android:color="@android:color/black" />
+
+ <padding
+ android:bottom="1dp"
+ android:left="1dp"
+ android:right="1dp"
+ android:top="1dp" />
+
+</shape>
+
diff --git a/samples/odpclient/src/main/res/layout/activity_main.xml b/samples/odpclient/src/main/res/layout/activity_main.xml
index ece5ec4..7d48eff 100644
--- a/samples/odpclient/src/main/res/layout/activity_main.xml
+++ b/samples/odpclient/src/main/res/layout/activity_main.xml
@@ -16,13 +16,14 @@
-->
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="vertical"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
+ <LinearLayout
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:paddingTop="@dimen/scrollview_padding_top">
<EditText
android:id="@+id/text_box"
@@ -48,6 +49,7 @@
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:hint="Population" />
+
<EditText
android:id="@+id/schedule_interval_text_box"
android:inputType="numberDecimal"
@@ -87,9 +89,23 @@
style="?android:attr/buttonBarButtonStyle"
android:text="@string/report_conversion" />
- <SurfaceView
- android:id="@+id/rendered_view"
- android:layout_width="200dp"
- android:layout_height="200dp" />
+ <ViewSwitcher
+ android:id="@+id/view_switcher"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent" >
+ <SurfaceView
+ android:id="@+id/rendered_view"
+ android:layout_width="200dp"
+ android:layout_height="200dp" />
+
+ <TextView
+ android:id="@+id/message_box"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minLines="20"
+ android:scrollHorizontally="true"
+ android:background="@drawable/border" />
+
+ </ViewSwitcher>
</LinearLayout>
-</ScrollView>
\ No newline at end of file
+</ScrollView>
diff --git a/samples/odpclient/src/main/res/values-v35/dimens.xml b/samples/odpclient/src/main/res/values-v35/dimens.xml
new file mode 100644
index 0000000..6125d30
--- /dev/null
+++ b/samples/odpclient/src/main/res/values-v35/dimens.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources>
+ <dimen name="scrollview_padding_top">128dp</dimen> <!-- Padding for API level 35 and higher -->
+</resources>
\ No newline at end of file
diff --git a/samples/odpclient/src/main/res/values/dimens.xml b/samples/odpclient/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..8360c27
--- /dev/null
+++ b/samples/odpclient/src/main/res/values/dimens.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources>
+ <dimen name="scrollview_padding_top">0dp</dimen> <!-- No padding for lower API levels -->
+</resources>
\ No newline at end of file
diff --git a/samples/odpsamplenetwork/src/main/AndroidManifest.xml b/samples/odpsamplenetwork/src/main/AndroidManifest.xml
index 2fcd2cb..a7461a7 100644
--- a/samples/odpsamplenetwork/src/main/AndroidManifest.xml
+++ b/samples/odpsamplenetwork/src/main/AndroidManifest.xml
@@ -17,7 +17,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.odpsamplenetwork"
- android:versionName="1.1.1" >
+ android:versionName="1.1.2" >
<application android:label="OdpSampleNetwork"
android:debuggable="true">
<property android:name="android.ondevicepersonalization.ON_DEVICE_PERSONALIZATION_CONFIG"
diff --git a/samples/odpsamplenetwork/src/main/java/com/example/odpsamplenetwork/SampleHandler.java b/samples/odpsamplenetwork/src/main/java/com/example/odpsamplenetwork/SampleHandler.java
index f57b31b..cfbd60c 100644
--- a/samples/odpsamplenetwork/src/main/java/com/example/odpsamplenetwork/SampleHandler.java
+++ b/samples/odpsamplenetwork/src/main/java/com/example/odpsamplenetwork/SampleHandler.java
@@ -111,6 +111,7 @@
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAA"
+ "AAXNSR0IArs4c6QAAAAtJREFUGFdjYAACAAAFAAGq1chRAAAAAElFTkSuQmCC";
private static final byte[] TRANSPARENT_PNG_BYTES = Base64.decode(TRANSPARENT_PNG_BASE64, 0);
+ private static final int ERROR_CODE = 10;
private static final ListeningExecutorService sBackgroundExecutor =
MoreExecutors.listeningDecorator(
@@ -178,6 +179,12 @@
@NonNull ExecuteInput input,
@NonNull OutcomeReceiver<ExecuteOutput, IsolatedServiceException> receiver) {
Log.d(TAG, "onExecute() started.");
+ if (input != null
+ && input.getAppParams() != null
+ && input.getAppParams().getString("keyword") != null
+ && input.getAppParams().getString("keyword").equalsIgnoreCase("crash")) {
+ throw new RuntimeException("Client-requested crash.");
+ }
sBackgroundExecutor.execute(() -> handleOnExecute(input, receiver));
}
@@ -385,12 +392,17 @@
if (exampleCache.containsKey(key)) {
example = convertToExample(exampleCache.get(key));
} else {
- String value =
+ try {
+ String value =
new String(
- mRemoteData.get(String.format("example%d", key)),
- StandardCharsets.UTF_8);
- exampleCache.put(key, value);
- example = convertToExample(value);
+ mRemoteData.get(String.format("example%d", key)),
+ StandardCharsets.UTF_8);
+ exampleCache.put(key, value);
+ example = convertToExample(value);
+ } catch (Throwable e) {
+ Log.w(TAG, "failure getting example from remote data store", e);
+ continue;
+ }
}
TrainingExampleRecord record =
new TrainingExampleRecord.Builder()
@@ -441,6 +453,13 @@
try {
if (input != null
&& input.getAppParams() != null
+ && input.getAppParams().getString("keyword") != null
+ && input.getAppParams().getString("keyword").equalsIgnoreCase("error")) {
+ receiver.onError(new IsolatedServiceException(ERROR_CODE));
+ return;
+ }
+ if (input != null
+ && input.getAppParams() != null
&& input.getAppParams().getString("schedule_training") != null) {
Log.d(TAG, "onExecute() performing schedule training.");
if (input.getAppParams().getString("schedule_training").isEmpty()) {
@@ -596,7 +615,7 @@
String content =
"<img src=\""
+ impressionUrl
- + "\">\n"
+ + "\" alt=\"\">\n"
+ "<a href=\""
+ clickUrl
+ "\">"
@@ -921,6 +940,12 @@
public void onResult(InferenceOutput result) {
completer.set(result);
}
+
+ @Override
+ public void onError(Exception e) {
+ Log.e(TAG, "modelManager.run() exception", e);
+ completer.set(null);
+ }
});
// Used only for debugging.
return "getModelInferenceResultFuture";
diff --git a/samples/odpsamplenetwork/src/main/res/raw/test_data1.json b/samples/odpsamplenetwork/src/main/res/raw/test_data1.json
index 4a4cfde..9482ea3 100644
--- a/samples/odpsamplenetwork/src/main/res/raw/test_data1.json
+++ b/samples/odpsamplenetwork/src/main/res/raw/test_data1.json
@@ -23,7 +23,7 @@
},
{
"key": "template1",
- "data": "<img src=\"$impressionUrl\">\n<a href=\"$clickUrl\">${adText}!</a>"
+ "data": "<img src=\"$impressionUrl\" alt=\"\">\n<a href=\"$clickUrl\">${adText}!</a>"
},
{
"key": "example1",
diff --git a/src/com/android/ondevicepersonalization/services/Flags.java b/src/com/android/ondevicepersonalization/services/Flags.java
index 11bd51b..5e64127 100644
--- a/src/com/android/ondevicepersonalization/services/Flags.java
+++ b/src/com/android/ondevicepersonalization/services/Flags.java
@@ -16,6 +16,9 @@
package com.android.ondevicepersonalization.services;
+import android.adservices.ondevicepersonalization.ExecuteInIsolatedServiceRequest;
+import android.adservices.ondevicepersonalization.OnDevicePersonalizationManager;
+
import com.android.adservices.shared.common.flags.ConfigFlag;
import com.android.adservices.shared.common.flags.FeatureFlag;
import com.android.adservices.shared.common.flags.ModuleSharedFlags;
@@ -177,18 +180,14 @@
return WEB_TRIGGER_FLOW_DEADLINE_SECONDS;
}
- /**
- * Executiton deadline for example store flow.
- */
+ /** Execution deadline for example store flow. */
int EXAMPLE_STORE_FLOW_DEADLINE_SECONDS = 30;
default int getExampleStoreFlowDeadlineSeconds() {
return EXAMPLE_STORE_FLOW_DEADLINE_SECONDS;
}
- /**
- * Executiton deadline for download flow.
- */
+ /** Execution deadline for download flow. */
int DOWNLOAD_FLOW_DEADLINE_SECONDS = 30;
default int getDownloadFlowDeadlineSeconds() {
@@ -240,14 +239,6 @@
return DEFAULT_SPE_PILOT_JOB_ENABLED;
}
- /** Set all stable flags. */
- default void setStableFlags() {}
-
- /** Get a stable flag based on the flag name. */
- default Object getStableFlag(String flagName) {
- return null;
- }
-
default boolean getEnableClientErrorLogging() {
return DEFAULT_CLIENT_ERROR_LOGGING_ENABLED;
}
@@ -266,4 +257,103 @@
default long getAppInstallHistoryTtlInMillis() {
return DEFAULT_APP_INSTALL_HISTORY_TTL_MILLIS;
}
+
+ /**
+ * The probability that we will return a random integer for {@link
+ * OnDevicePersonalizationManager#executeInIsolatedService}.
+ */
+ float DEFAULT_EXECUTE_BEST_VALUE_NOISE = 0.1f;
+
+ default float getNoiseForExecuteBestValue() {
+ return DEFAULT_EXECUTE_BEST_VALUE_NOISE;
+ }
+
+ /** Default value for flag that enables aggregated error code reporting. */
+ boolean DEFAULT_AGGREGATED_ERROR_REPORTING_ENABLED = false;
+
+ default boolean getAggregatedErrorReportingEnabled() {
+ return DEFAULT_AGGREGATED_ERROR_REPORTING_ENABLED;
+ }
+
+ int DEFAULT_AGGREGATED_ERROR_REPORT_TTL_DAYS = 30;
+
+ /**
+ * TTL for aggregate counts after which they will be deleted without waiting for a successful
+ * upload attempt.
+ */
+ default int getAggregatedErrorReportingTtlInDays() {
+ return DEFAULT_AGGREGATED_ERROR_REPORT_TTL_DAYS;
+ }
+
+ String DEFAULT_AGGREGATED_ERROR_REPORTING_URL_PATH =
+ "debugreporting/v1/exceptions:report-exceptions";
+
+ /**
+ * URL suffix that the reporting job will use to send adopters daily aggregated counts of {@link
+ * android.adservices.ondevicepersonalization.IsolatedServiceException}s.
+ */
+ default String getAggregatedErrorReportingServerPath() {
+ return DEFAULT_AGGREGATED_ERROR_REPORTING_URL_PATH;
+ }
+
+ int DEFAULT_AGGREGATED_ERROR_REPORTING_THRESHOLD = 0;
+
+ /**
+ * Minimum threshold for counts of {@link
+ * android.adservices.ondevicepersonalization.IsolatedServiceException} below which counts from
+ * device won't be reported.
+ *
+ * <p>This is applied per error code.
+ */
+ default int getAggregatedErrorMinThreshold() {
+ return DEFAULT_AGGREGATED_ERROR_REPORTING_THRESHOLD;
+ }
+
+ int DEFAULT_AGGREGATED_ERROR_REPORTING_INTERVAL_HOURS = 24;
+
+ /**
+ * Interval for the periodic runs of the {@link
+ * com.android.ondevicepersonalization.services.data.errors.AggregateErrorDataReportingService}
+ * that reports counts of {@link android.adservices.ondevicepersonalization.IsolatedService}.
+ */
+ default int getAggregatedErrorReportingIntervalInHours() {
+ return DEFAULT_AGGREGATED_ERROR_REPORTING_INTERVAL_HOURS;
+ }
+
+ /**
+ * Default value for maximum int value caller can set in {@link
+ * ExecuteInIsolatedServiceRequest.OutputSpec#buildBestValueSpec}.
+ */
+ int DEFAULT_MAX_INT_VALUES = 100;
+
+ 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;
+ }
+
+ String DEFAULT_PLATFORM_DATA_FOR_TRAINING_ALLOWLIST = "";
+
+ default String getPlatformDataForTrainingAllowlist() {
+ return DEFAULT_PLATFORM_DATA_FOR_TRAINING_ALLOWLIST;
+ }
+
+ String DEFAULT_PLATFORM_DATA_FOR_EXECUTE_ALLOWLIST = "";
+
+ default String getDefaultPlatformDataForExecuteAllowlist() {
+ return DEFAULT_PLATFORM_DATA_FOR_EXECUTE_ALLOWLIST;
+ }
+
+ String DEFAULT_LOG_ISOLATED_SERVICE_ERROR_CODE_NON_AGGREGATED_ALLOWLIST = "";
+
+ default String getLogIsolatedServiceErrorCodeNonAggregatedAllowlist() {
+ return DEFAULT_LOG_ISOLATED_SERVICE_ERROR_CODE_NON_AGGREGATED_ALLOWLIST;
+ }
}
diff --git a/src/com/android/ondevicepersonalization/services/OdpServiceException.java b/src/com/android/ondevicepersonalization/services/OdpServiceException.java
index d46a786..6089396 100644
--- a/src/com/android/ondevicepersonalization/services/OdpServiceException.java
+++ b/src/com/android/ondevicepersonalization/services/OdpServiceException.java
@@ -27,21 +27,21 @@
private final int mErrorCode;
public OdpServiceException(int errorCode) {
- this(errorCode, "");
+ this(errorCode, "ErrorCode: " + errorCode);
}
public OdpServiceException(int errorCode, @NonNull String errorMessage) {
- super("Error code: " + errorCode + " message: " + errorMessage);
+ super(errorMessage);
mErrorCode = errorCode;
}
public OdpServiceException(int errorCode, @NonNull Throwable cause) {
- this(errorCode, "", cause);
+ this(errorCode, "ErrorCode: " + errorCode, cause);
}
public OdpServiceException(
int errorCode, @NonNull String errorMessage, @NonNull Throwable cause) {
- super("Error code: " + errorCode + " message: " + errorMessage, cause);
+ super(errorMessage, cause);
mErrorCode = errorCode;
}
diff --git a/src/com/android/ondevicepersonalization/services/OnDevicePersonalizationConfig.java b/src/com/android/ondevicepersonalization/services/OnDevicePersonalizationConfig.java
index 7d995d4..398a6be 100644
--- a/src/com/android/ondevicepersonalization/services/OnDevicePersonalizationConfig.java
+++ b/src/com/android/ondevicepersonalization/services/OnDevicePersonalizationConfig.java
@@ -16,10 +16,6 @@
package com.android.ondevicepersonalization.services;
-import com.android.ondevicepersonalization.services.data.user.UserDataCollectionJobService;
-import com.android.ondevicepersonalization.services.download.OnDevicePersonalizationDownloadProcessingJobService;
-import com.android.ondevicepersonalization.services.maintenance.OnDevicePersonalizationMaintenanceJobService;
-
import java.util.Map;
/** Hard-coded configs for OnDevicePersonalization */
@@ -60,42 +56,60 @@
/**
* Job ID for Download Processing Task ({@link
- * OnDevicePersonalizationDownloadProcessingJobService})
+ * com.android.ondevicepersonalization.services.download.OnDevicePersonalizationDownloadProcessingJobService})
*/
public static final int DOWNLOAD_PROCESSING_TASK_JOB_ID = 1004;
+
public static final String DOWNLOAD_PROCESSING_TASK_JOB_NAME =
"DOWNLOAD_PROCESSING_TASK_JOB";
- /** Job ID for Maintenance Task ({@link OnDevicePersonalizationMaintenanceJobService}) */
+ /**
+ * Job ID for Maintenance Task ({@link
+ * com.android.ondevicepersonalization.services.maintenance.OnDevicePersonalizationMaintenanceJobService})
+ */
public static final int MAINTENANCE_TASK_JOB_ID = 1005;
+
public static final String MAINTENANCE_TASK_JOB_NAME =
"MAINTENANCE_TASK_JOB";
- /** Job ID for User Data Collection Task ({@link UserDataCollectionJobService}) */
+ /**
+ * Job ID for User Data Collection Task ({@link
+ * com.android.ondevicepersonalization.services.data.user.UserDataCollectionJobService})
+ */
public static final int USER_DATA_COLLECTION_ID = 1006;
+
public static final String USER_DATA_COLLECTION_JOB_NAME =
"USER_DATA_COLLECTION_JOB";
- /** Job ID for Reset Task ({@link ResetDataJobService}) */
+ /**
+ * Job ID for Reset Task ({@link
+ * com.android.ondevicepersonalization.services.reset.ResetDataJobService})
+ */
public static final int RESET_DATA_JOB_ID = 1007;
+
public static final String RESET_DATA_JOB_NAME = "RESET_JOB";
- public static final Map<Integer, String> JOB_ID_TO_NAME_MAP = Map.of(
- MDD_MAINTENANCE_PERIODIC_TASK_JOB_ID,
- MDD_MAINTENANCE_PERIODIC_TASK_JOB_NAME,
- MDD_CHARGING_PERIODIC_TASK_JOB_ID,
- MDD_CHARGING_PERIODIC_TASK_JOB_NAME,
- MDD_CELLULAR_CHARGING_PERIODIC_TASK_JOB_ID,
- MDD_CELLULAR_CHARGING_PERIODIC_TASK_JOB_NAME,
- MDD_WIFI_CHARGING_PERIODIC_TASK_JOB_ID,
- MDD_WIFI_CHARGING_PERIODIC_TASK_JOB_NAME,
- DOWNLOAD_PROCESSING_TASK_JOB_ID,
- DOWNLOAD_PROCESSING_TASK_JOB_NAME,
- MAINTENANCE_TASK_JOB_ID,
- MAINTENANCE_TASK_JOB_NAME,
- USER_DATA_COLLECTION_ID,
- USER_DATA_COLLECTION_JOB_NAME,
- RESET_DATA_JOB_ID,
- RESET_DATA_JOB_NAME
- );
+ public static final int AGGREGATE_ERROR_DATA_REPORTING_JOB_ID = 1008;
+ public static final String AGGREGATED_ERROR_DATA_REPORTING_JOB_NAME =
+ "ERROR_DATA_REPORTING_JOB";
+ public static final Map<Integer, String> JOB_ID_TO_NAME_MAP =
+ Map.of(
+ MDD_MAINTENANCE_PERIODIC_TASK_JOB_ID,
+ MDD_MAINTENANCE_PERIODIC_TASK_JOB_NAME,
+ MDD_CHARGING_PERIODIC_TASK_JOB_ID,
+ MDD_CHARGING_PERIODIC_TASK_JOB_NAME,
+ MDD_CELLULAR_CHARGING_PERIODIC_TASK_JOB_ID,
+ MDD_CELLULAR_CHARGING_PERIODIC_TASK_JOB_NAME,
+ MDD_WIFI_CHARGING_PERIODIC_TASK_JOB_ID,
+ MDD_WIFI_CHARGING_PERIODIC_TASK_JOB_NAME,
+ DOWNLOAD_PROCESSING_TASK_JOB_ID,
+ DOWNLOAD_PROCESSING_TASK_JOB_NAME,
+ MAINTENANCE_TASK_JOB_ID,
+ MAINTENANCE_TASK_JOB_NAME,
+ USER_DATA_COLLECTION_ID,
+ USER_DATA_COLLECTION_JOB_NAME,
+ RESET_DATA_JOB_ID,
+ RESET_DATA_JOB_NAME,
+ AGGREGATE_ERROR_DATA_REPORTING_JOB_ID,
+ AGGREGATED_ERROR_DATA_REPORTING_JOB_NAME);
}
diff --git a/src/com/android/ondevicepersonalization/services/OnDevicePersonalizationConfigServiceDelegate.java b/src/com/android/ondevicepersonalization/services/OnDevicePersonalizationConfigServiceDelegate.java
deleted file mode 100644
index 4769e24..0000000
--- a/src/com/android/ondevicepersonalization/services/OnDevicePersonalizationConfigServiceDelegate.java
+++ /dev/null
@@ -1,196 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ondevicepersonalization.services;
-
-import static android.adservices.ondevicepersonalization.OnDevicePersonalizationPermissions.MODIFY_ONDEVICEPERSONALIZATION_STATE;
-
-import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__API_CALLBACK_ERROR;
-import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__API_REMOTE_EXCEPTION;
-import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__ON_DEVICE_PERSONALIZATION_ERROR;
-import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__ODP;
-
-import android.adservices.ondevicepersonalization.Constants;
-import android.adservices.ondevicepersonalization.aidl.IOnDevicePersonalizationConfigService;
-import android.adservices.ondevicepersonalization.aidl.IOnDevicePersonalizationConfigServiceCallback;
-import android.annotation.NonNull;
-import android.annotation.RequiresPermission;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.ondevicepersonalization.IOnDevicePersonalizationSystemService;
-import android.ondevicepersonalization.IOnDevicePersonalizationSystemServiceCallback;
-import android.ondevicepersonalization.OnDevicePersonalizationSystemServiceManager;
-import android.os.Binder;
-import android.os.Bundle;
-import android.os.RemoteException;
-
-import com.android.modules.utils.build.SdkLevel;
-import com.android.ondevicepersonalization.internal.util.LoggerFactory;
-import com.android.ondevicepersonalization.services.data.user.RawUserData;
-import com.android.ondevicepersonalization.services.data.user.UserDataCollector;
-import com.android.ondevicepersonalization.services.data.user.UserPrivacyStatus;
-import com.android.ondevicepersonalization.services.statsd.errorlogging.ClientErrorLogger;
-
-import java.util.Objects;
-import java.util.concurrent.Executor;
-
-/**
- * ODP service that modifies and persists ODP enablement status
- */
-public class OnDevicePersonalizationConfigServiceDelegate
- extends IOnDevicePersonalizationConfigService.Stub {
- private static final LoggerFactory.Logger sLogger = LoggerFactory.getLogger();
- private static final String TAG = "OnDevicePersonalizationConfigServiceDelegate";
- private final Context mContext;
- private static final Executor sBackgroundExecutor =
- OnDevicePersonalizationExecutors.getBackgroundExecutor();
- private static final int SERVICE_NOT_IMPLEMENTED = 501;
-
- public OnDevicePersonalizationConfigServiceDelegate(Context context) {
- mContext = context;
- }
-
- @Override
- @RequiresPermission(MODIFY_ONDEVICEPERSONALIZATION_STATE)
- public void setPersonalizationStatus(boolean enabled,
- @NonNull IOnDevicePersonalizationConfigServiceCallback
- callback) {
- if (getGlobalKillSwitch()) {
- throw new IllegalStateException("Service skipped as the API flag is turned off.");
- }
-
- // Verify caller's permission
- if (mContext.checkCallingPermission(MODIFY_ONDEVICEPERSONALIZATION_STATE)
- != PackageManager.PERMISSION_GRANTED) {
- throw new SecurityException(
- "Permission denied: " + MODIFY_ONDEVICEPERSONALIZATION_STATE);
- }
- Objects.requireNonNull(callback);
-
- sBackgroundExecutor.execute(
- () -> {
- try {
- UserPrivacyStatus userPrivacyStatus = UserPrivacyStatus.getInstance();
-
- boolean oldStatus = userPrivacyStatus.isPersonalizationStatusEnabled();
- userPrivacyStatus.setPersonalizationStatusEnabled(enabled);
- boolean newStatus = userPrivacyStatus.isPersonalizationStatusEnabled();
-
- if (oldStatus == newStatus) {
- sendSuccess(callback);
- return;
- }
-
- // Rollback all user data if personalization status changes
- RawUserData userData = RawUserData.getInstance();
- UserDataCollector userDataCollector =
- UserDataCollector.getInstance(mContext);
- userDataCollector.clearUserData(userData);
- userDataCollector.clearMetadata();
-
- // TODO(b/302018665): replicate system server storage to T devices.
- if (!SdkLevel.isAtLeastU()) {
- userPrivacyStatus.setPersonalizationStatusEnabled(enabled);
- sendSuccess(callback);
- return;
- }
- // Persist in the system server for U+ devices
- OnDevicePersonalizationSystemServiceManager systemServiceManager =
- mContext.getSystemService(
- OnDevicePersonalizationSystemServiceManager.class);
- // Cannot find system server on U+.
- if (systemServiceManager == null) {
- sendError(callback, SERVICE_NOT_IMPLEMENTED);
- return;
- }
- IOnDevicePersonalizationSystemService systemService =
- systemServiceManager.getService();
- // The system service is not ready.
- if (systemService == null) {
- sendError(callback, SERVICE_NOT_IMPLEMENTED);
- return;
- }
- try {
- systemService.setPersonalizationStatus(
- enabled,
- new IOnDevicePersonalizationSystemServiceCallback.Stub() {
- @Override
- public void onResult(Bundle bundle) throws RemoteException {
- userPrivacyStatus.setPersonalizationStatusEnabled(
- enabled);
- callback.onSuccess();
- }
-
- @Override
- public void onError(int errorCode) throws RemoteException {
- callback.onFailure(errorCode);
- }
- });
- } catch (RemoteException re) {
- sLogger.e(TAG + ": Unable to send result to the callback.", re);
- ClientErrorLogger.getInstance()
- .logErrorWithExceptionInfo(
- re,
- AD_SERVICES_ERROR_REPORTED__ERROR_CODE__API_REMOTE_EXCEPTION,
- AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__ODP);
- }
- } catch (Exception e) {
- sLogger.e(TAG + ": Failed to set personalization status.", e);
- ClientErrorLogger.getInstance()
- .logErrorWithExceptionInfo(
- e,
- AD_SERVICES_ERROR_REPORTED__ERROR_CODE__ON_DEVICE_PERSONALIZATION_ERROR,
- AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__ODP);
- sendError(callback, Constants.STATUS_INTERNAL_ERROR);
- }
- });
- }
-
- private void sendSuccess(
- @NonNull IOnDevicePersonalizationConfigServiceCallback callback) {
- try {
- callback.onSuccess();
- } catch (RemoteException e) {
- sLogger.e(TAG + ": Callback error", e);
- ClientErrorLogger.getInstance()
- .logErrorWithExceptionInfo(
- e,
- AD_SERVICES_ERROR_REPORTED__ERROR_CODE__API_CALLBACK_ERROR,
- AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__ODP);
- }
- }
-
- private void sendError(
- @NonNull IOnDevicePersonalizationConfigServiceCallback callback, int errorCode) {
- try {
- callback.onFailure(errorCode);
- } catch (RemoteException e) {
- sLogger.e(TAG + ": Callback error", e);
- ClientErrorLogger.getInstance()
- .logErrorWithExceptionInfo(
- e,
- AD_SERVICES_ERROR_REPORTED__ERROR_CODE__API_CALLBACK_ERROR,
- AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__ODP);
- }
- }
-
- private boolean getGlobalKillSwitch() {
- long origId = Binder.clearCallingIdentity();
- boolean globalKillSwitch = FlagsFactory.getFlags().getGlobalKillSwitch();
- Binder.restoreCallingIdentity(origId);
- return globalKillSwitch;
- }
-}
diff --git a/src/com/android/ondevicepersonalization/services/OnDevicePersonalizationConfigServiceImpl.java b/src/com/android/ondevicepersonalization/services/OnDevicePersonalizationConfigServiceImpl.java
deleted file mode 100644
index 53b50e5..0000000
--- a/src/com/android/ondevicepersonalization/services/OnDevicePersonalizationConfigServiceImpl.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ondevicepersonalization.services;
-
-import android.app.Service;
-import android.content.Intent;
-import android.os.IBinder;
-
-/**
- * ODP service that modifies and persists user's privacy status.
- */
-public class OnDevicePersonalizationConfigServiceImpl extends Service {
-
- /** Binder interface. */
- private OnDevicePersonalizationConfigServiceDelegate mBinder;
-
- @Override
- public void onCreate() {
- mBinder = new OnDevicePersonalizationConfigServiceDelegate(this);
- }
-
- @Override
- public IBinder onBind(Intent intent) {
- return mBinder;
- }
-}
diff --git a/src/com/android/ondevicepersonalization/services/OnDevicePersonalizationExecutors.java b/src/com/android/ondevicepersonalization/services/OnDevicePersonalizationExecutors.java
index 8849a0e..5eb3819 100644
--- a/src/com/android/ondevicepersonalization/services/OnDevicePersonalizationExecutors.java
+++ b/src/com/android/ondevicepersonalization/services/OnDevicePersonalizationExecutors.java
@@ -39,7 +39,7 @@
private static final ListeningExecutorService sHighPriorityBackgroundExecutor =
MoreExecutors.listeningDecorator(
Executors.newFixedThreadPool(
- /* nThreads */ 2,
+ /* nThreads */ 4,
createThreadFactory(
"HPBG Thread",
Process.THREAD_PRIORITY_BACKGROUND
@@ -49,7 +49,7 @@
private static final ListeningExecutorService sLowPriorityBackgroundExecutor =
MoreExecutors.listeningDecorator(
Executors.newFixedThreadPool(
- /* nThreads */ 2,
+ /* nThreads */ 4,
createThreadFactory(
"LPBG Thread",
Process.THREAD_PRIORITY_BACKGROUND
diff --git a/src/com/android/ondevicepersonalization/services/OnDevicePersonalizationManagingServiceDelegate.java b/src/com/android/ondevicepersonalization/services/OnDevicePersonalizationManagingServiceDelegate.java
index b52a773..4d5d7ac 100644
--- a/src/com/android/ondevicepersonalization/services/OnDevicePersonalizationManagingServiceDelegate.java
+++ b/src/com/android/ondevicepersonalization/services/OnDevicePersonalizationManagingServiceDelegate.java
@@ -20,6 +20,8 @@
import android.adservices.ondevicepersonalization.CallerMetadata;
import android.adservices.ondevicepersonalization.Constants;
+import android.adservices.ondevicepersonalization.ExecuteInIsolatedServiceRequest;
+import android.adservices.ondevicepersonalization.ExecuteOptionsParcel;
import android.adservices.ondevicepersonalization.aidl.IExecuteCallback;
import android.adservices.ondevicepersonalization.aidl.IOnDevicePersonalizationManagingService;
import android.adservices.ondevicepersonalization.aidl.IRegisterMeasurementEventCallback;
@@ -34,6 +36,7 @@
import android.os.SystemClock;
import android.os.Trace;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.odp.module.common.DeviceUtils;
import com.android.ondevicepersonalization.internal.util.LoggerFactory;
import com.android.ondevicepersonalization.services.enrollment.PartnerEnrollmentChecker;
@@ -51,9 +54,23 @@
private static final String TAG = "OnDevicePersonalizationManagingServiceDelegate";
private static final ServiceFlowOrchestrator sSfo = ServiceFlowOrchestrator.getInstance();
@NonNull private final Context mContext;
+ private final Injector mInjector;
public OnDevicePersonalizationManagingServiceDelegate(@NonNull Context context) {
+ this(context, new Injector());
+ }
+
+ @VisibleForTesting
+ public OnDevicePersonalizationManagingServiceDelegate(
+ @NonNull Context context, Injector injector) {
mContext = Objects.requireNonNull(context);
+ mInjector = injector;
+ }
+
+ static class Injector {
+ Flags getFlags() {
+ return FlagsFactory.getFlags();
+ }
}
@Override
@@ -67,6 +84,7 @@
@NonNull ComponentName handler,
@NonNull Bundle wrappedParams,
@NonNull CallerMetadata metadata,
+ @NonNull ExecuteOptionsParcel options,
@NonNull IExecuteCallback callback) {
if (getGlobalKillSwitch()) {
throw new IllegalStateException("Service skipped as the global kill switch is on.");
@@ -95,13 +113,22 @@
throw new IllegalArgumentException("missing service class name");
}
+ checkExecutionsOptions(options);
+
final int uid = Binder.getCallingUid();
enforceCallingPackageBelongsToUid(callingPackageName, uid);
enforceEnrollment(callingPackageName, handler);
- sSfo.schedule(ServiceFlowType.APP_REQUEST_FLOW,
- callingPackageName, handler, wrappedParams,
- callback, mContext, metadata.getStartTimeMillis(), serviceEntryTimeMillis);
+ sSfo.schedule(
+ ServiceFlowType.APP_REQUEST_FLOW,
+ callingPackageName,
+ handler,
+ wrappedParams,
+ callback,
+ mContext,
+ metadata.getStartTimeMillis(),
+ serviceEntryTimeMillis,
+ options);
Trace.endSection();
}
@@ -217,8 +244,7 @@
private boolean getGlobalKillSwitch() {
long origId = Binder.clearCallingIdentity();
- boolean globalKillSwitch = FlagsFactory.getFlags().getGlobalKillSwitch();
- FlagsFactory.getFlags().setStableFlags();
+ boolean globalKillSwitch = mInjector.getFlags().getGlobalKillSwitch();
Binder.restoreCallingIdentity(origId);
return globalKillSwitch;
}
@@ -257,4 +283,19 @@
Binder.restoreCallingIdentity(origId);
}
}
+
+ private void checkExecutionsOptions(@NonNull ExecuteOptionsParcel options) {
+ long origId = Binder.clearCallingIdentity();
+ try {
+ if (options.getOutputType()
+ == ExecuteInIsolatedServiceRequest.OutputSpec.OUTPUT_TYPE_BEST_VALUE
+ && options.getMaxIntValue() > mInjector.getFlags().getMaxIntValuesLimit()) {
+ throw new IllegalArgumentException(
+ "The maxIntValue in OutputSpec can not exceed limit "
+ + mInjector.getFlags().getMaxIntValuesLimit());
+ }
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+ }
}
\ No newline at end of file
diff --git a/src/com/android/ondevicepersonalization/services/PhFlags.java b/src/com/android/ondevicepersonalization/services/PhFlags.java
index a71f6ec..00d89a2 100644
--- a/src/com/android/ondevicepersonalization/services/PhFlags.java
+++ b/src/com/android/ondevicepersonalization/services/PhFlags.java
@@ -18,7 +18,9 @@
import android.annotation.NonNull;
import android.provider.DeviceConfig;
+
import com.android.modules.utils.build.SdkLevel;
+
import java.util.HashMap;
import java.util.Map;
@@ -68,9 +70,6 @@
public static final String KEY_ODP_ENABLE_CLIENT_ERROR_LOGGING =
"odp_enable_client_error_logging";
- public static final String KEY_ODP_BACKGROUND_JOBS_LOGGING_ENABLED =
- "odp_background_jobs_logging_enabled";
-
public static final String KEY_ODP_BACKGROUND_JOB_SAMPLING_LOGGING_RATE =
"odp_background_job_sampling_logging_rate";
@@ -95,16 +94,41 @@
public static final String KEY_RESET_DATA_DEADLINE_SECONDS = "reset_data_deadline_seconds";
public static final String APP_INSTALL_HISTORY_TTL = "app_install_history_ttl";
+ public static final String EXECUTE_BEST_VALUE_NOISE = "noise_for_execute_best_value";
+
+ public static final String KEY_ENABLE_AGGREGATED_ERROR_REPORTING =
+ "enable_aggregated_error_reporting";
+
+ public static final String KEY_AGGREGATED_ERROR_REPORT_TTL_DAYS =
+ "aggregated_error_report_ttl_days";
+
+ public static final String KEY_AGGREGATED_ERROR_REPORTING_PATH =
+ "aggregated_error_reporting_path";
+
+ public static final String KEY_AGGREGATED_ERROR_REPORTING_THRESHOLD =
+ "aggregated_error_reporting_threshold";
+
+ public static final String KEY_AGGREGATED_ERROR_REPORTING_INTERVAL_HOURS =
+ "aggregated_error_reporting_interval_hours";
+
+ 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";
+ public static final String KEY_PLATFORM_DATA_FOR_TRAINING_ALLOWLIST =
+ "platform_data_for_training_allowlist";
+ public static final String KEY_PLATFORM_DATA_FOR_EXECUTE_ALLOWLIST =
+ "platform_data_for_execute_allowlist";
+
+ public static final String KEY_LOG_ISOLATED_SERVICE_ERROR_CODE_NON_AGGREGATED_ALLOWLIST =
+ "log_isolated_service_error_code_non_aggregated_allowlist";
// OnDevicePersonalization Namespace String from DeviceConfig class
public static final String NAMESPACE_ON_DEVICE_PERSONALIZATION = "on_device_personalization";
private final Map<String, Object> mStableFlags = new HashMap<>();
- PhFlags() {
- // This is only called onece so stable flags require process restart to be reset.
- setStableFlags();
- }
+ PhFlags() {}
/** Returns the singleton instance of the PhFlags. */
@NonNull
@@ -116,42 +140,6 @@
private static final PhFlags sSingleton = new PhFlags();
}
- /** Sets the stable flag map. */
- public void setStableFlags() {
- mStableFlags.put(KEY_APP_REQUEST_FLOW_DEADLINE_SECONDS,
- getAppRequestFlowDeadlineSeconds());
- mStableFlags.put(KEY_RENDER_FLOW_DEADLINE_SECONDS,
- getRenderFlowDeadlineSeconds());
- mStableFlags.put(KEY_WEB_TRIGGER_FLOW_DEADLINE_SECONDS,
- getWebTriggerFlowDeadlineSeconds());
- mStableFlags.put(KEY_WEB_VIEW_FLOW_DEADLINE_SECONDS,
- getWebViewFlowDeadlineSeconds());
- mStableFlags.put(KEY_EXAMPLE_STORE_FLOW_DEADLINE_SECONDS,
- getExampleStoreFlowDeadlineSeconds());
- mStableFlags.put(KEY_DOWNLOAD_FLOW_DEADLINE_SECONDS,
- getDownloadFlowDeadlineSeconds());
- mStableFlags.put(KEY_SHARED_ISOLATED_PROCESS_FEATURE_ENABLED,
- isSharedIsolatedProcessFeatureEnabled());
- mStableFlags.put(KEY_TRUSTED_PARTNER_APPS_LIST,
- getTrustedPartnerAppsList());
- mStableFlags.put(KEY_IS_ART_IMAGE_LOADING_OPTIMIZATION_ENABLED,
- isArtImageLoadingOptimizationEnabled());
- mStableFlags.put(KEY_ENABLE_PERSONALIZATION_STATUS_OVERRIDE,
- isPersonalizationStatusOverrideEnabled());
- mStableFlags.put(KEY_PERSONALIZATION_STATUS_OVERRIDE_VALUE,
- getPersonalizationStatusOverrideValue());
- mStableFlags.put(KEY_USER_CONTROL_CACHE_IN_MILLIS,
- getUserControlCacheInMillis());
- }
-
- /** Gets a stable flag value based on flag name. */
- public Object getStableFlag(String flagName) {
- if (!mStableFlags.containsKey(flagName)) {
- throw new IllegalArgumentException("Flag " + flagName + " is not stable.");
- }
- return mStableFlags.get(flagName);
- }
-
// Group of All Killswitches
@Override
public boolean getGlobalKillSwitch() {
@@ -313,18 +301,17 @@
/* defaultValue= */ DEFAULT_CLIENT_ERROR_LOGGING_ENABLED);
}
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>This method always return {@code true} because the underlying flag is fully launched on
+ * {@code OnDevicePersonalization} but the method cannot be removed (as it's defined on {@code
+ * ModuleSharedFlags}).
+ */
@Override
public boolean getBackgroundJobsLoggingEnabled() {
- // needs stable: execution stats may be less accurate if flag changed during job execution
- return (boolean)
- mStableFlags.computeIfAbsent(
- KEY_ODP_BACKGROUND_JOBS_LOGGING_ENABLED,
- key -> {
- return DeviceConfig.getBoolean(
- /* namespace= */ NAMESPACE_ON_DEVICE_PERSONALIZATION,
- /* name= */ KEY_ODP_BACKGROUND_JOBS_LOGGING_ENABLED,
- /* defaultValue= */ BACKGROUND_JOB_LOGGING_ENABLED);
- });
+ return true;
}
@Override
@@ -398,4 +385,93 @@
/* name= */ APP_INSTALL_HISTORY_TTL,
/* defaultValue= */ DEFAULT_APP_INSTALL_HISTORY_TTL_MILLIS);
}
+
+ @Override
+ public float getNoiseForExecuteBestValue() {
+ return DeviceConfig.getFloat(
+ /* namespace= */ NAMESPACE_ON_DEVICE_PERSONALIZATION,
+ /* name= */ EXECUTE_BEST_VALUE_NOISE,
+ /* defaultValue= */ DEFAULT_EXECUTE_BEST_VALUE_NOISE);
+ }
+
+ @Override
+ public boolean getAggregatedErrorReportingEnabled() {
+ return DeviceConfig.getBoolean(
+ /* namespace= */ NAMESPACE_ON_DEVICE_PERSONALIZATION,
+ /* name= */ KEY_ENABLE_AGGREGATED_ERROR_REPORTING,
+ /* defaultValue= */ DEFAULT_AGGREGATED_ERROR_REPORTING_ENABLED);
+ }
+
+ @Override
+ public int getAggregatedErrorReportingTtlInDays() {
+ return DeviceConfig.getInt(
+ /* namespace= */ NAMESPACE_ON_DEVICE_PERSONALIZATION,
+ /* name= */ KEY_AGGREGATED_ERROR_REPORT_TTL_DAYS,
+ /* defaultValue= */ DEFAULT_AGGREGATED_ERROR_REPORT_TTL_DAYS);
+ }
+
+ @Override
+ public String getAggregatedErrorReportingServerPath() {
+ return DeviceConfig.getString(
+ /* namespace= */ NAMESPACE_ON_DEVICE_PERSONALIZATION,
+ /* name= */ KEY_AGGREGATED_ERROR_REPORTING_PATH,
+ /* defaultValue= */ DEFAULT_AGGREGATED_ERROR_REPORTING_URL_PATH);
+ }
+
+ @Override
+ public int getAggregatedErrorMinThreshold() {
+ return DeviceConfig.getInt(
+ /* namespace= */ NAMESPACE_ON_DEVICE_PERSONALIZATION,
+ /* name= */ KEY_AGGREGATED_ERROR_REPORTING_THRESHOLD,
+ /* defaultValue= */ DEFAULT_AGGREGATED_ERROR_REPORTING_THRESHOLD);
+ }
+
+ @Override
+ public int getAggregatedErrorReportingIntervalInHours() {
+ return DeviceConfig.getInt(
+ /* namespace= */ NAMESPACE_ON_DEVICE_PERSONALIZATION,
+ /* name= */ KEY_AGGREGATED_ERROR_REPORTING_INTERVAL_HOURS,
+ /* defaultValue= */ DEFAULT_AGGREGATED_ERROR_REPORTING_INTERVAL_HOURS);
+ }
+
+ @Override
+ public int getMaxIntValuesLimit() {
+ return DeviceConfig.getInt(
+ /* namespace= */ NAMESPACE_ON_DEVICE_PERSONALIZATION,
+ /* 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);
+ }
+
+ @Override
+ public String getPlatformDataForTrainingAllowlist() {
+ return DeviceConfig.getString(
+ /* namespace= */ NAMESPACE_ON_DEVICE_PERSONALIZATION,
+ /* name= */ KEY_PLATFORM_DATA_FOR_TRAINING_ALLOWLIST,
+ /* defaultValue= */ DEFAULT_PLATFORM_DATA_FOR_TRAINING_ALLOWLIST);
+ }
+
+ @Override
+ public String getDefaultPlatformDataForExecuteAllowlist() {
+ return DeviceConfig.getString(
+ /* namespace= */ NAMESPACE_ON_DEVICE_PERSONALIZATION,
+ /* name= */ KEY_PLATFORM_DATA_FOR_EXECUTE_ALLOWLIST,
+ /* defaultValue= */ DEFAULT_PLATFORM_DATA_FOR_EXECUTE_ALLOWLIST);
+ }
+
+ @Override
+ public String getLogIsolatedServiceErrorCodeNonAggregatedAllowlist() {
+ return DeviceConfig.getString(
+ /* namespace= */ NAMESPACE_ON_DEVICE_PERSONALIZATION,
+ /* name= */ KEY_LOG_ISOLATED_SERVICE_ERROR_CODE_NON_AGGREGATED_ALLOWLIST,
+ /* defaultValue= */
+ DEFAULT_LOG_ISOLATED_SERVICE_ERROR_CODE_NON_AGGREGATED_ALLOWLIST);
+ }
}
diff --git a/src/com/android/ondevicepersonalization/services/StableFlags.java b/src/com/android/ondevicepersonalization/services/StableFlags.java
new file mode 100644
index 0000000..3a7141e
--- /dev/null
+++ b/src/com/android/ondevicepersonalization/services/StableFlags.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ondevicepersonalization.services;
+
+import android.os.Binder;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Container of process-stable flags.
+ */
+public class StableFlags {
+ private static final Object sLock = new Object();
+ private static volatile StableFlags sStableFlags = null;
+
+ private final Map<String, Object> mStableFlagsMap = new HashMap<>();
+
+ /** Returns the value of the named stable flag. */
+ public static Object get(String flagName) {
+ return getInstance().getStableFlag(flagName);
+
+ }
+
+ /** Returns the singleton instance of StableFlags. */
+ @VisibleForTesting
+ public static StableFlags getInstance() {
+ if (sStableFlags == null) {
+ synchronized (sLock) {
+ if (sStableFlags == null) {
+ long origId = Binder.clearCallingIdentity();
+ sStableFlags = new StableFlags(FlagsFactory.getFlags());
+ Binder.restoreCallingIdentity(origId);
+ }
+ }
+ }
+ return sStableFlags;
+ }
+
+ @VisibleForTesting
+ StableFlags(Flags flags) {
+ mStableFlagsMap.put(PhFlags.KEY_APP_REQUEST_FLOW_DEADLINE_SECONDS,
+ flags.getAppRequestFlowDeadlineSeconds());
+ mStableFlagsMap.put(PhFlags.KEY_RENDER_FLOW_DEADLINE_SECONDS,
+ flags.getRenderFlowDeadlineSeconds());
+ mStableFlagsMap.put(PhFlags.KEY_WEB_TRIGGER_FLOW_DEADLINE_SECONDS,
+ flags.getWebTriggerFlowDeadlineSeconds());
+ mStableFlagsMap.put(PhFlags.KEY_WEB_VIEW_FLOW_DEADLINE_SECONDS,
+ flags.getWebViewFlowDeadlineSeconds());
+ mStableFlagsMap.put(PhFlags.KEY_EXAMPLE_STORE_FLOW_DEADLINE_SECONDS,
+ flags.getExampleStoreFlowDeadlineSeconds());
+ mStableFlagsMap.put(PhFlags.KEY_DOWNLOAD_FLOW_DEADLINE_SECONDS,
+ flags.getDownloadFlowDeadlineSeconds());
+ mStableFlagsMap.put(PhFlags.KEY_SHARED_ISOLATED_PROCESS_FEATURE_ENABLED,
+ flags.isSharedIsolatedProcessFeatureEnabled());
+ mStableFlagsMap.put(PhFlags.KEY_TRUSTED_PARTNER_APPS_LIST,
+ flags.getTrustedPartnerAppsList());
+ mStableFlagsMap.put(PhFlags.KEY_IS_ART_IMAGE_LOADING_OPTIMIZATION_ENABLED,
+ flags.isArtImageLoadingOptimizationEnabled());
+ mStableFlagsMap.put(PhFlags.KEY_ENABLE_PERSONALIZATION_STATUS_OVERRIDE,
+ flags.isPersonalizationStatusOverrideEnabled());
+ mStableFlagsMap.put(PhFlags.KEY_PERSONALIZATION_STATUS_OVERRIDE_VALUE,
+ flags.getPersonalizationStatusOverrideValue());
+ mStableFlagsMap.put(PhFlags.KEY_USER_CONTROL_CACHE_IN_MILLIS,
+ flags.getUserControlCacheInMillis());
+ }
+
+ private Object getStableFlag(String flagName) {
+ if (!mStableFlagsMap.containsKey(flagName)) {
+ throw new IllegalArgumentException("Flag " + flagName + " is not stable.");
+ }
+ return mStableFlagsMap.get(flagName);
+ }
+}
diff --git a/src/com/android/ondevicepersonalization/services/data/errors/AggregateErrorDataReportingService.java b/src/com/android/ondevicepersonalization/services/data/errors/AggregateErrorDataReportingService.java
new file mode 100644
index 0000000..075290d
--- /dev/null
+++ b/src/com/android/ondevicepersonalization/services/data/errors/AggregateErrorDataReportingService.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ondevicepersonalization.services.data.errors;
+
+import static android.app.job.JobScheduler.RESULT_FAILURE;
+
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__SKIP_FOR_JOB_NOT_CONFIGURED;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__SKIP_FOR_KILL_SWITCH_ON;
+import static com.android.ondevicepersonalization.services.OnDevicePersonalizationConfig.AGGREGATE_ERROR_DATA_REPORTING_JOB_ID;
+
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.app.job.JobService;
+import android.content.ComponentName;
+import android.content.Context;
+
+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.statsd.joblogging.OdpJobServiceLogger;
+
+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.ListeningExecutorService;
+
+/** {@link JobService} to perform daily reporting of aggregated error codes. */
+public class AggregateErrorDataReportingService extends JobService {
+ private static final LoggerFactory.Logger sLogger = LoggerFactory.getLogger();
+ private static final String TAG = AggregateErrorDataReportingService.class.getSimpleName();
+
+ private ListenableFuture<Void> mFuture;
+
+ private final Injector mInjector;
+
+ public AggregateErrorDataReportingService() {
+ this(new Injector());
+ }
+
+ @VisibleForTesting
+ AggregateErrorDataReportingService(Injector injector) {
+ mInjector = injector;
+ }
+
+ static class Injector {
+ ListeningExecutorService getExecutor() {
+ return OnDevicePersonalizationExecutors.getBackgroundExecutor();
+ }
+
+ Flags getFlags() {
+ return FlagsFactory.getFlags();
+ }
+ }
+
+ /** Schedules a unique instance of the {@link AggregateErrorDataReportingService} to be run. */
+ public static int scheduleIfNeeded(Context context) {
+ return scheduleIfNeeded(context, FlagsFactory.getFlags());
+ }
+
+ @VisibleForTesting
+ static int scheduleIfNeeded(Context context, Flags flags) {
+ if (!flags.getAggregatedErrorReportingEnabled()) {
+ sLogger.d(TAG + ": Aggregate error reporting is disabled.");
+ return RESULT_FAILURE;
+ }
+
+ JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
+ if (jobScheduler.getPendingJob(AGGREGATE_ERROR_DATA_REPORTING_JOB_ID) != null) {
+ sLogger.d(TAG + ": Job is already scheduled. Doing nothing.");
+ return RESULT_FAILURE;
+ }
+
+ ComponentName serviceComponent =
+ new ComponentName(context, AggregateErrorDataReportingService.class);
+ JobInfo.Builder builder =
+ new JobInfo.Builder(AGGREGATE_ERROR_DATA_REPORTING_JOB_ID, serviceComponent);
+
+ // Constraints
+ builder.setRequiresDeviceIdle(true);
+ builder.setRequiresBatteryNotLow(true);
+ builder.setRequiresStorageNotLow(true);
+ builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED);
+ builder.setPeriodic(
+ 1000L
+ * FlagsFactory.getFlags().getAggregatedErrorReportingIntervalInHours()
+ * 3600L); // JobScheduler uses Milliseconds.
+ // persist this job across boots
+ builder.setPersisted(true);
+
+ return jobScheduler.schedule(builder.build());
+ }
+
+ @Override
+ public boolean onStartJob(JobParameters params) {
+ sLogger.d(TAG + ": onStartJob()");
+ OdpJobServiceLogger.getInstance(this)
+ .recordOnStartJob(AGGREGATE_ERROR_DATA_REPORTING_JOB_ID);
+ if (mInjector.getFlags().getGlobalKillSwitch()) {
+ sLogger.d(TAG + ": GlobalKillSwitch enabled, finishing job.");
+ return cancelAndFinishJob(
+ params,
+ AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__SKIP_FOR_KILL_SWITCH_ON);
+ }
+
+ if (!mInjector.getFlags().getAggregatedErrorReportingEnabled()) {
+ sLogger.d(TAG + ": aggregate error reporting disabled, finishing job.");
+ return cancelAndFinishJob(
+ params,
+ AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__SKIP_FOR_JOB_NOT_CONFIGURED);
+ }
+
+ mFuture =
+ Futures.submit(
+ new Runnable() {
+ @Override
+ public void run() {
+ // TODO(b/329921267): Add logic for reporting new data from DAO.
+ sLogger.d(
+ TAG + ": Running the aggregate error data collection job");
+ }
+ },
+ mInjector.getExecutor());
+
+ Futures.addCallback(
+ mFuture,
+ new FutureCallback<Void>() {
+ @Override
+ public void onSuccess(Void result) {
+ sLogger.d(TAG + ": Aggregate error reporting job completed successfully.");
+ boolean wantsReschedule = false;
+ OdpJobServiceLogger.getInstance(AggregateErrorDataReportingService.this)
+ .recordJobFinished(
+ AGGREGATE_ERROR_DATA_REPORTING_JOB_ID,
+ /* isSuccessful= */ true,
+ wantsReschedule);
+ jobFinished(params, wantsReschedule);
+ }
+
+ @Override
+ public void onFailure(Throwable t) {
+ sLogger.e(TAG + ": Failed to handle JobService: " + params.getJobId(), t);
+ boolean wantsReschedule = false;
+ OdpJobServiceLogger.getInstance(AggregateErrorDataReportingService.this)
+ .recordJobFinished(
+ AGGREGATE_ERROR_DATA_REPORTING_JOB_ID,
+ /* isSuccessful= */ false,
+ wantsReschedule);
+ // When failure, also tell the JobScheduler that the job has completed and
+ // does not need to be rescheduled.
+ jobFinished(params, wantsReschedule);
+ }
+ },
+ mInjector.getExecutor());
+
+ return true;
+ }
+
+ @Override
+ public boolean onStopJob(JobParameters params) {
+ if (mFuture != null) {
+ mFuture.cancel(true);
+ mFuture = null;
+ }
+
+ // Reschedule the job since it ended before finishing
+ boolean wantsReschedule = true;
+ OdpJobServiceLogger.getInstance(this)
+ .recordOnStopJob(params, AGGREGATE_ERROR_DATA_REPORTING_JOB_ID, wantsReschedule);
+ return wantsReschedule;
+ }
+
+ private boolean cancelAndFinishJob(final JobParameters params, int skipReason) {
+ JobScheduler jobScheduler = this.getSystemService(JobScheduler.class);
+ if (jobScheduler != null) {
+ jobScheduler.cancel(AGGREGATE_ERROR_DATA_REPORTING_JOB_ID);
+ }
+ OdpJobServiceLogger.getInstance(this)
+ .recordJobSkipped(AGGREGATE_ERROR_DATA_REPORTING_JOB_ID, skipReason);
+ jobFinished(params, /* wantsReschedule= */ false);
+ return true;
+ }
+}
diff --git a/src/com/android/ondevicepersonalization/services/data/errors/AggregatedErrorCodesContract.java b/src/com/android/ondevicepersonalization/services/data/errors/AggregatedErrorCodesContract.java
new file mode 100644
index 0000000..c9632a1
--- /dev/null
+++ b/src/com/android/ondevicepersonalization/services/data/errors/AggregatedErrorCodesContract.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ondevicepersonalization.services.data.errors;
+
+import android.provider.BaseColumns;
+
+/** Contract for the per vendor aggregated error code tables. Defines the table. */
+final class AggregatedErrorCodesContract {
+ private AggregatedErrorCodesContract() {}
+
+ /**
+ * Table containing aggregated error data associated with a particular vendor/adopter.
+ *
+ * <p>Each table is associated with a particular vendor.
+ */
+ public static class ErrorDataEntry implements BaseColumns {
+
+ /** The {@code isolatedServiceErrorCode} returned from the {@code IsolatedWorker}. */
+ public static final String EXCEPTION_ERROR_CODE = "exception_error_code";
+
+ /** The date that error was thrown. */
+ public static final String EXCEPTION_DATE = "exception_date";
+
+ /** The total count of the errors thrown by the vendor code on the given date. */
+ public static final String EXCEPTION_COUNT = "exception_count";
+
+ /**
+ * The version of the package of the {@code IsolatedService} when the error was reported.
+ */
+ public static final String SERVICE_PACKAGE_VERSION = "service_package_version";
+
+ private ErrorDataEntry() {}
+
+ /** Returns the statement for table creation for the given table name. */
+ public static String getCreateTableIfNotExistsStatement(final String tableName) {
+ return "CREATE TABLE IF NOT EXISTS "
+ + tableName
+ + " ("
+ + EXCEPTION_ERROR_CODE
+ + " INTEGER DEFAULT 0,"
+ + EXCEPTION_DATE
+ + " INTEGER DEFAULT 0,"
+ + EXCEPTION_COUNT
+ + " INTEGER DEFAULT 0,"
+ + SERVICE_PACKAGE_VERSION
+ + " INTEGER DEFAULT 0,"
+ + "PRIMARY KEY("
+ + EXCEPTION_ERROR_CODE
+ + ","
+ + EXCEPTION_DATE
+ + "))";
+ }
+ }
+}
diff --git a/src/com/android/ondevicepersonalization/services/data/errors/AggregatedErrorCodesLogger.java b/src/com/android/ondevicepersonalization/services/data/errors/AggregatedErrorCodesLogger.java
new file mode 100644
index 0000000..a8b47ac
--- /dev/null
+++ b/src/com/android/ondevicepersonalization/services/data/errors/AggregatedErrorCodesLogger.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ondevicepersonalization.services.data.errors;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+
+import com.android.odp.module.common.PackageUtils;
+import com.android.ondevicepersonalization.internal.util.LoggerFactory;
+import com.android.ondevicepersonalization.services.FlagsFactory;
+import com.android.ondevicepersonalization.services.OnDevicePersonalizationExecutors;
+import com.android.ondevicepersonalization.services.manifest.AppManifestConfigHelper;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+
+public final class AggregatedErrorCodesLogger {
+ private static final String TAG = AggregatedErrorCodesLogger.class.getSimpleName();
+ private static final LoggerFactory.Logger sLogger = LoggerFactory.getLogger();
+
+ /**
+ * Adds the given isolatedServiceError code into the package specific DB via the {@link
+ * OnDevicePersonalizationAggregatedErrorDataDao}.
+ *
+ * <p>No-op if the aggregate error reporting flag is disabled.
+ *
+ * @param isolatedServiceErrorCode the error code returned from the isolated service.
+ * @param componentName the name of the component hosting the isolated service.
+ * @param context calling service context.
+ * @return {@link ListenableFuture} that resolves successfully when the error code is
+ * successfully logged via the Dao.
+ */
+ public static ListenableFuture<Void> logIsolatedServiceErrorCode(
+ int isolatedServiceErrorCode, ComponentName componentName, Context context) {
+ if (!FlagsFactory.getFlags().getAggregatedErrorReportingEnabled()) {
+ sLogger.e(TAG + ": Skipping logging, aggregated error code logging disabled");
+ return Futures.immediateVoidFuture();
+ }
+
+ return (ListenableFuture<Void>)
+ OnDevicePersonalizationExecutors.getBackgroundExecutor()
+ .submit(() -> logError(isolatedServiceErrorCode, componentName, context));
+ }
+
+ private static void logError(
+ int isolatedServiceErrorCode, ComponentName componentName, Context context) {
+ String certDigest = "";
+ try {
+ certDigest = PackageUtils.getCertDigest(context, componentName.getPackageName());
+ } catch (PackageManager.NameNotFoundException nne) {
+ sLogger.e(TAG + ": failed to get cert digest.", nne);
+ return;
+ }
+
+ OnDevicePersonalizationAggregatedErrorDataDao dao =
+ OnDevicePersonalizationAggregatedErrorDataDao.getInstance(
+ context, componentName, certDigest);
+ dao.addExceptionCount(isolatedServiceErrorCode, /* exceptionCount= */ 1);
+ }
+
+ /**
+ * Deletes any aggregate error data tables on device except for those ODP services that are
+ * still installed and enrolled.
+ *
+ * <p>No-op if the aggregate error reporting flag is disabled.
+ *
+ * <p>Can use the {@link #cleanupErrorData(Context)} if already calling on an {@code executor}.
+ *
+ * @param context calling service context.
+ * @return {@link ListenableFuture} that resolves successfully when the deletion is successful.
+ */
+ public static ListenableFuture<Void> cleanupAggregatedErrorData(Context context) {
+ if (!FlagsFactory.getFlags().getAggregatedErrorReportingEnabled()) {
+ sLogger.e(TAG + ": Skipping cleanup, aggregated error code logging disabled");
+ return Futures.immediateVoidFuture();
+ }
+
+ return (ListenableFuture<Void>)
+ OnDevicePersonalizationExecutors.getBackgroundExecutor()
+ .submit(() -> cleanupErrorData(context));
+ }
+
+ /**
+ * Deletes any aggregate error data tables on device except for those ODP services that are
+ * still installed and enrolled.
+ *
+ * <p>No-op if the aggregate error reporting flag is disabled.
+ *
+ * <p>Should be called on an appropriate {@link OnDevicePersonalizationExecutors}.
+ *
+ * @param context calling service context.
+ */
+ public static void cleanupErrorData(Context context) {
+ // Delete all error data for any services that are no longer installed
+ ImmutableList<ComponentName> odpServices =
+ AppManifestConfigHelper.getOdpServices(context, /* enrolledOnly= */ true);
+ OnDevicePersonalizationAggregatedErrorDataDao.cleanupErrorData(context, odpServices);
+ }
+
+ /**
+ * Test only method that returns count of error data tables on device.
+ *
+ * @param context calling service context.
+ * @return the number of error data tables on device.
+ */
+ @VisibleForTesting
+ public static int getErrorDataTableCount(Context context) {
+ return OnDevicePersonalizationAggregatedErrorDataDao.getErrorDataTableNames(context).size();
+ }
+
+ private AggregatedErrorCodesLogger() {}
+}
diff --git a/src/com/android/ondevicepersonalization/services/data/errors/DateTimeUtils.java b/src/com/android/ondevicepersonalization/services/data/errors/DateTimeUtils.java
new file mode 100644
index 0000000..91c8a7a
--- /dev/null
+++ b/src/com/android/ondevicepersonalization/services/data/errors/DateTimeUtils.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ondevicepersonalization.services.data.errors;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.odp.module.common.Clock;
+import com.android.odp.module.common.MonotonicClock;
+import com.android.ondevicepersonalization.internal.util.LoggerFactory;
+
+import java.time.DateTimeException;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.ZoneOffset;
+
+/** Utilities for date/time transformations. */
+final class DateTimeUtils {
+ private static final String TAG = DateTimeUtils.class.getSimpleName();
+ private static final LoggerFactory.Logger sLogger = LoggerFactory.getLogger();
+
+ /**
+ * Get the day index in the UTC timezone.
+ *
+ * <p>Returns {@code -1} if unsuccessful.
+ */
+ public static int dayIndexUtc() {
+ return dayIndexUtc(MonotonicClock.getInstance());
+ }
+
+ @VisibleForTesting
+ static int dayIndexUtc(Clock clock) {
+ // Package-private method for easier testing, allows injecting a clock in tests.
+ Instant currentInstant = getCurrentInstant(clock);
+ try {
+ return (int) currentInstant.atZone(ZoneOffset.UTC).toLocalDate().toEpochDay();
+ } catch (DateTimeException e) {
+ sLogger.e(TAG + " : failed to get day index.", e);
+ return -1;
+ }
+ }
+
+ /**
+ * Get the day index in the local device's timezone.
+ *
+ * <p>Returns {@code -1} if unsuccessful.
+ */
+ public static int dayIndexLocal() {
+ return dayIndexLocal(MonotonicClock.getInstance());
+ }
+
+ @VisibleForTesting
+ static int dayIndexLocal(Clock clock) {
+ // Package-private method for easier testing, allows injecting a clock in tests.
+ Instant currentInstant = getCurrentInstant(clock);
+ try {
+ return (int) currentInstant.atZone(ZoneId.systemDefault()).toLocalDate().toEpochDay();
+ } catch (DateTimeException e) {
+ sLogger.e(TAG + " : failed to get day index.", e);
+ return -1;
+ }
+ }
+
+ private static Instant getCurrentInstant(Clock clock) {
+ long currentSystemTime = clock.currentTimeMillis();
+ sLogger.i(TAG + ": current system time = " + currentSystemTime);
+ return Instant.ofEpochMilli(currentSystemTime);
+ }
+
+ private DateTimeUtils() {}
+}
diff --git a/src/com/android/ondevicepersonalization/services/data/errors/ErrorData.java b/src/com/android/ondevicepersonalization/services/data/errors/ErrorData.java
new file mode 100644
index 0000000..4ced422
--- /dev/null
+++ b/src/com/android/ondevicepersonalization/services/data/errors/ErrorData.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ondevicepersonalization.services.data.errors;
+
+import android.annotation.NonNull;
+
+import com.android.ondevicepersonalization.internal.util.AnnotationValidations;
+import com.android.ondevicepersonalization.internal.util.DataClass;
+
+@DataClass(genBuilder = true, genEqualsHashCode = true)
+public class ErrorData {
+
+ /** The error code returned by the {@code IsolatedService}. */
+ @NonNull private final int mErrorCode;
+
+ /** The aggregated count of {@link #mErrorCode} on the given {@link #mEpochDay}. */
+ @NonNull private final int mErrorCount;
+
+ /** The date associated with this record of aggregated errors. */
+ @NonNull private final int mEpochDay;
+
+ /** The version of the package of the {@code IsolatedService}. */
+ @NonNull private final long mServicePackageVersion;
+
+ // Code below generated by codegen v1.0.23.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen
+ // $ANDROID_BUILD_TOP/packages/modules/OnDevicePersonalization/src/com/android/ondevicepersonalization/services/data/errors/ErrorData.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ // @formatter:off
+
+ @DataClass.Generated.Member
+ /* package-private */ ErrorData(
+ @NonNull int errorCode,
+ @NonNull int errorCount,
+ @NonNull int epochDay,
+ @NonNull long servicePackageVersion) {
+ this.mErrorCode = errorCode;
+ AnnotationValidations.validate(NonNull.class, null, mErrorCode);
+ this.mErrorCount = errorCount;
+ AnnotationValidations.validate(NonNull.class, null, mErrorCount);
+ this.mEpochDay = epochDay;
+ AnnotationValidations.validate(NonNull.class, null, mEpochDay);
+ this.mServicePackageVersion = servicePackageVersion;
+ AnnotationValidations.validate(NonNull.class, null, mServicePackageVersion);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull int getErrorCode() {
+ return mErrorCode;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull int getErrorCount() {
+ return mErrorCount;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull int getEpochDay() {
+ return mEpochDay;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull long getServicePackageVersion() {
+ return mServicePackageVersion;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public boolean equals(@android.annotation.Nullable Object o) {
+ // You can override field equality logic by defining either of the methods like:
+ // boolean fieldNameEquals(ErrorData other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ ErrorData that = (ErrorData) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && mErrorCode == that.mErrorCode
+ && mErrorCount == that.mErrorCount
+ && mEpochDay == that.mEpochDay
+ && mServicePackageVersion == that.mServicePackageVersion;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int hashCode() {
+ // You can override field hashCode logic by defining methods like:
+ // int fieldNameHashCode() { ... }
+
+ int _hash = 1;
+ _hash = 31 * _hash + mErrorCode;
+ _hash = 31 * _hash + mErrorCount;
+ _hash = 31 * _hash + mEpochDay;
+ _hash = 31 * _hash + Long.hashCode(mServicePackageVersion);
+ return _hash;
+ }
+
+ /** A builder for {@link ErrorData} */
+ @SuppressWarnings("WeakerAccess")
+ @DataClass.Generated.Member
+ public static class Builder {
+
+ private @NonNull int mErrorCode;
+ private @NonNull int mErrorCount;
+ private @NonNull int mEpochDay;
+ private @NonNull long mServicePackageVersion;
+
+ private long mBuilderFieldsSet = 0L;
+
+ public Builder(
+ @NonNull int errorCode,
+ @NonNull int errorCount,
+ @NonNull int epochDay,
+ @NonNull long servicePackageVersion) {
+ mErrorCode = errorCode;
+ AnnotationValidations.validate(NonNull.class, null, mErrorCode);
+ mErrorCount = errorCount;
+ AnnotationValidations.validate(NonNull.class, null, mErrorCount);
+ mEpochDay = epochDay;
+ AnnotationValidations.validate(NonNull.class, null, mEpochDay);
+ mServicePackageVersion = servicePackageVersion;
+ AnnotationValidations.validate(NonNull.class, null, mServicePackageVersion);
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull Builder setErrorCode(@NonNull int value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x1;
+ mErrorCode = value;
+ return this;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull Builder setErrorCount(@NonNull int value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x2;
+ mErrorCount = value;
+ return this;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull Builder setEpochDay(@NonNull int value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x4;
+ mEpochDay = value;
+ return this;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull Builder setServicePackageVersion(@NonNull long value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x8;
+ mServicePackageVersion = value;
+ return this;
+ }
+
+ /** Builds the instance. This builder should not be touched after calling this! */
+ public @NonNull ErrorData build() {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x10; // Mark builder used
+
+ ErrorData o = new ErrorData(mErrorCode, mErrorCount, mEpochDay, mServicePackageVersion);
+ return o;
+ }
+
+ private void checkNotUsed() {
+ if ((mBuilderFieldsSet & 0x10) != 0) {
+ throw new IllegalStateException(
+ "This Builder should not be reused. Use a new Builder instance instead");
+ }
+ }
+ }
+
+ @DataClass.Generated(
+ time = 1724390597119L,
+ codegenVersion = "1.0.23",
+ sourceFile =
+ "packages/modules/OnDevicePersonalization/src/com/android/ondevicepersonalization/services/data/errors/ErrorData.java",
+ inputSignatures =
+ "private final @android.annotation.NonNull int mErrorCode\n"
+ + "private final @android.annotation.NonNull int mErrorCount\n"
+ + "private final @android.annotation.NonNull int mEpochDay\n"
+ + "private final @android.annotation.NonNull long mServicePackageVersion\n"
+ + "class ErrorData extends java.lang.Object implements []\n"
+ + "@com.android.ondevicepersonalization.internal.util.DataClass(genBuilder=true,"
+ + " genEqualsHashCode=true)")
+ @Deprecated
+ private void __metadata() {}
+
+ // @formatter:on
+ // End of generated code
+
+}
diff --git a/src/com/android/ondevicepersonalization/services/data/errors/OnDevicePersonalizationAggregatedErrorDataDao.java b/src/com/android/ondevicepersonalization/services/data/errors/OnDevicePersonalizationAggregatedErrorDataDao.java
new file mode 100644
index 0000000..e7a996d
--- /dev/null
+++ b/src/com/android/ondevicepersonalization/services/data/errors/OnDevicePersonalizationAggregatedErrorDataDao.java
@@ -0,0 +1,458 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ondevicepersonalization.services.data.errors;
+
+import android.content.ComponentName;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.database.Cursor;
+import android.database.SQLException;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteException;
+
+import com.android.odp.module.common.PackageUtils;
+import com.android.ondevicepersonalization.internal.util.LoggerFactory;
+import com.android.ondevicepersonalization.services.data.DbUtils;
+import com.android.ondevicepersonalization.services.data.OnDevicePersonalizationDbHelper;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
+
+import java.io.File;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Dao used to manage access to per vendor aggregated error codes that are returned by {@link
+ * android.adservices.ondevicepersonalization.IsolatedService} implementations.
+ *
+ * <p>The Dao should all be called on appropriate {@code executor}.
+ */
+class OnDevicePersonalizationAggregatedErrorDataDao {
+ private static final LoggerFactory.Logger sLogger = LoggerFactory.getLogger();
+ private static final String TAG =
+ OnDevicePersonalizationAggregatedErrorDataDao.class.getSimpleName();
+ private static final String ERROR_DATA_TABLE_NAME_PREFIX = "errordata";
+
+ @VisibleForTesting static final int MAX_ALLOWED_ERROR_CODE = 32;
+
+ private static final Map<String, OnDevicePersonalizationAggregatedErrorDataDao>
+ sVendorDataDaos = new ConcurrentHashMap<>();
+ private final OnDevicePersonalizationDbHelper mDbHelper;
+ private final ComponentName mOwner;
+ private final String mCertDigest;
+ private final String mTableName;
+ private final long mPackageVersion;
+
+ private OnDevicePersonalizationAggregatedErrorDataDao(
+ OnDevicePersonalizationDbHelper dbHelper,
+ ComponentName owner,
+ String certDigest,
+ long packageVersion) {
+ this.mDbHelper = dbHelper;
+ this.mOwner = owner;
+ this.mCertDigest = certDigest;
+ this.mTableName = getTableName(owner, certDigest);
+ this.mPackageVersion = packageVersion;
+ }
+
+ /**
+ * Clears all the aggregated error data tables except for the provided excluded services.
+ *
+ * @param context The context of the application
+ * @param excludedServices the services whose tables/data that should not be cleaned up.
+ * <p>Synchronized to avoid any concurrent modifications to the underlying {@link
+ * #sVendorDataDaos}.
+ */
+ static synchronized void cleanupErrorData(
+ Context context, ImmutableList<ComponentName> excludedServices) {
+ ImmutableList<String> existingTables = getErrorDataTableNames(context);
+ if (existingTables.isEmpty()) {
+ sLogger.d(TAG + ": no tables found to delete");
+ return;
+ }
+
+ Set<String> excludedTableNames = new HashSet<>();
+ for (ComponentName service : excludedServices) {
+ String certDigest = getCertDigest(context, service.getPackageName());
+ if (certDigest.isEmpty()) {
+ sLogger.d(
+ TAG
+ + ": unable to get cert digest skipping deletion for service "
+ + service);
+ continue;
+ }
+
+ excludedTableNames.add(getTableName(service, certDigest));
+ }
+
+ OnDevicePersonalizationDbHelper dbHelper =
+ OnDevicePersonalizationDbHelper.getInstance(context);
+ SQLiteDatabase db = dbHelper == null ? null : dbHelper.safeGetWritableDatabase();
+ if (db == null) {
+ sLogger.e(TAG + ": failed to get the db while deleting exception data.");
+ return;
+ }
+
+ db.beginTransactionNonExclusive();
+ try {
+ for (String tableName : existingTables) {
+ if (excludedTableNames.contains(tableName)) {
+ sLogger.d(TAG + ": skipping deletion for " + tableName);
+ continue;
+ }
+ db.execSQL("DROP TABLE IF EXISTS " + tableName);
+ sVendorDataDaos.remove(tableName);
+ }
+ db.setTransactionSuccessful();
+ } catch (Exception e) {
+ sLogger.e(TAG + ": Failed to delete exception data.", e);
+ } finally {
+ db.endTransaction();
+ }
+ }
+
+ /**
+ * Helper method that returns an empty cert-digest if the underlying {@code PackageManager} call
+ * fails.
+ */
+ private static String getCertDigest(Context context, String packageName) {
+ try {
+ return PackageUtils.getCertDigest(context, packageName);
+ } catch (PackageManager.NameNotFoundException nne) {
+ sLogger.e(TAG + ": failed to get cert digest for " + packageName);
+ }
+ return "";
+ }
+
+ /**
+ * Returns an instance of the {@link OnDevicePersonalizationAggregatedErrorDataDao} for a given
+ * component and associated cert digest.
+ *
+ * @param context The context of the application
+ * @param owner ComponentName of the package whose errors will be aggregated in the table
+ * @param certDigest Hash of the certificate used to sign the package
+ * @return Instance of {@link OnDevicePersonalizationAggregatedErrorDataDao} for accessing the
+ * requested components aggregated error table.
+ */
+ public static OnDevicePersonalizationAggregatedErrorDataDao getInstance(
+ Context context, ComponentName owner, String certDigest) {
+ String tableName = getTableName(owner, certDigest);
+ OnDevicePersonalizationAggregatedErrorDataDao instance = sVendorDataDaos.get(tableName);
+ if (instance == null) {
+ synchronized (sVendorDataDaos) {
+ instance = sVendorDataDaos.get(tableName);
+ if (instance == null) {
+ OnDevicePersonalizationDbHelper dbHelper =
+ OnDevicePersonalizationDbHelper.getInstance(context);
+ instance =
+ new OnDevicePersonalizationAggregatedErrorDataDao(
+ dbHelper, owner, certDigest, getPackageVersion(owner, context));
+ sVendorDataDaos.put(tableName, instance);
+ }
+ }
+ }
+ return instance;
+ }
+
+ private static long getPackageVersion(ComponentName owner, Context context) {
+ long packageVersion = 0;
+ try {
+ String packageName = owner.getPackageName();
+ PackageInfo packageInfo =
+ context.getPackageManager().getPackageInfo(packageName, /* flags= */ 0);
+ packageVersion = packageInfo.getLongVersionCode();
+ } catch (PackageManager.NameNotFoundException nne) {
+ sLogger.e(TAG + ": Unable to find package " + owner.getPackageName(), nne);
+ }
+ return packageVersion;
+ }
+
+ /** Delete the existing aggregate exception data for this package. */
+ public boolean deleteExceptionData() {
+ SQLiteDatabase db = mDbHelper.safeGetWritableDatabase();
+ if (db == null) {
+ sLogger.e(TAG + ": failed to get the db while deleting exception data.");
+ return false;
+ }
+
+ try {
+ db.beginTransactionNonExclusive();
+ if (db.delete(mTableName, /* whereClause= */ "1", /* whereArgs= */ null) <= 0) {
+ sLogger.d(TAG + ": zero records deleted for " + mOwner);
+ return false;
+ }
+
+ db.setTransactionSuccessful();
+ } catch (SQLException exception) {
+ sLogger.e(TAG + ": failed to delete exception data for " + mOwner, exception);
+ } finally {
+ db.endTransaction();
+ }
+ return true;
+ }
+
+ /** Get the existing aggregate exception data for this package. */
+ public ImmutableList<ErrorData> getExceptionData() {
+ ImmutableList.Builder listBuilder = ImmutableList.builder();
+ try {
+ SQLiteDatabase db = mDbHelper.getReadableDatabase();
+ try (Cursor cursor =
+ db.query(
+ mTableName,
+ /* columns= */ null,
+ /* selection= */ null,
+ /* selectionArgs= */ null,
+ /* groupBy= */ null,
+ /* having= */ null,
+ /* orderBy= */ null)) {
+ while (cursor.moveToNext()) {
+ int errorCount =
+ cursor.getInt(
+ cursor.getColumnIndexOrThrow(
+ AggregatedErrorCodesContract.ErrorDataEntry
+ .EXCEPTION_COUNT));
+ int errorCode =
+ cursor.getInt(
+ cursor.getColumnIndexOrThrow(
+ AggregatedErrorCodesContract.ErrorDataEntry
+ .EXCEPTION_ERROR_CODE));
+ int epochDay =
+ cursor.getInt(
+ cursor.getColumnIndexOrThrow(
+ AggregatedErrorCodesContract.ErrorDataEntry
+ .EXCEPTION_DATE));
+ long packageVersion =
+ cursor.getLong(
+ cursor.getColumnIndexOrThrow(
+ AggregatedErrorCodesContract.ErrorDataEntry
+ .SERVICE_PACKAGE_VERSION));
+ listBuilder.add(
+ new ErrorData.Builder(errorCode, errorCount, epochDay, packageVersion)
+ .build());
+ }
+ cursor.close();
+ return listBuilder.build();
+ }
+ } catch (SQLiteException e) {
+ sLogger.e(TAG + ": Failed to read aggregate exception data for " + mOwner, e);
+ }
+ return ImmutableList.of();
+ }
+
+ /**
+ * Add or update the record of exception count for the provided error code.
+ *
+ * <p>Uses the current date as the date they exception was thrown.
+ *
+ * @return whether the exception was successfully recorded in the database.
+ */
+ public boolean addExceptionCount(int isolatedServiceErrorCode, int exceptionCount) {
+ if (isolatedServiceErrorCode > MAX_ALLOWED_ERROR_CODE) {
+ sLogger.e(
+ TAG
+ + ": failed to record exception "
+ + isolatedServiceErrorCode
+ + " for package "
+ + mOwner.getPackageName());
+ return false;
+ }
+
+ int epochDay = DateTimeUtils.dayIndexUtc();
+ if (epochDay == -1) {
+ sLogger.e(
+ TAG
+ + ": failed to get the epoch day, unable to add exception for package "
+ + mOwner.getPackageName());
+ return false;
+ }
+
+ int existingExceptionCount = getExceptionCount(isolatedServiceErrorCode, epochDay);
+ if (!createTableIfNotExists(mTableName)) {
+ sLogger.e(TAG + ": failed to create table " + mTableName);
+ return false;
+ }
+
+ SQLiteDatabase db = mDbHelper.safeGetWritableDatabase();
+ if (db == null) {
+ sLogger.e(TAG + " : failed to get the DB while inserting into DB.");
+ return false;
+ }
+
+ try {
+ db.beginTransactionNonExclusive();
+ if (!insertErrorData(
+ new ErrorData.Builder(
+ isolatedServiceErrorCode,
+ existingExceptionCount + exceptionCount,
+ epochDay,
+ mPackageVersion)
+ .build())) {
+ sLogger.e(TAG + ": failed to insert error data " + mTableName);
+ return false;
+ }
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
+
+ return true;
+ }
+
+ /**
+ * Updates the given vendor data row, adds it if it doesn't already exist.
+ *
+ * @return true if the update/insert succeeded, false otherwise
+ */
+ private boolean insertErrorData(ErrorData errorData) {
+ try {
+ SQLiteDatabase db = mDbHelper.getWritableDatabase();
+
+ ContentValues values = new ContentValues();
+ values.put(
+ AggregatedErrorCodesContract.ErrorDataEntry.EXCEPTION_ERROR_CODE,
+ errorData.getErrorCode());
+ values.put(
+ AggregatedErrorCodesContract.ErrorDataEntry.EXCEPTION_DATE,
+ errorData.getEpochDay());
+ values.put(
+ AggregatedErrorCodesContract.ErrorDataEntry.SERVICE_PACKAGE_VERSION,
+ errorData.getServicePackageVersion());
+ values.put(
+ AggregatedErrorCodesContract.ErrorDataEntry.EXCEPTION_COUNT,
+ errorData.getErrorCount());
+ return db.insertWithOnConflict(
+ mTableName, null, values, SQLiteDatabase.CONFLICT_REPLACE)
+ != -1;
+ } catch (SQLiteException e) {
+ sLogger.e(TAG + ": Failed to update or insert error data. ", e);
+ }
+ return false;
+ }
+
+ @VisibleForTesting
+ /** Returns the existing count associated with the given error code on the given day. */
+ int getExceptionCount(int isolatedServiceErrorCode, int epochDay) {
+ SQLiteDatabase db = mDbHelper.safeGetReadableDatabase();
+ if (db == null) {
+ sLogger.e(TAG + ": failed to get the DB while getting exception count.");
+ return 0;
+ }
+
+ String selection =
+ AggregatedErrorCodesContract.ErrorDataEntry.EXCEPTION_ERROR_CODE
+ + " = ? AND "
+ + AggregatedErrorCodesContract.ErrorDataEntry.EXCEPTION_DATE
+ + " = ?";
+ String[] selectionArgs = {
+ String.valueOf(isolatedServiceErrorCode), String.valueOf(epochDay)
+ };
+ String[] columns = {AggregatedErrorCodesContract.ErrorDataEntry.EXCEPTION_COUNT};
+ try (Cursor cursor =
+ db.query(
+ mTableName,
+ columns,
+ selection,
+ selectionArgs,
+ /* groupBy= */ null,
+ /* having= */ null,
+ /* orderBy= */ null)) {
+ if (cursor.moveToFirst()) {
+ return cursor.getInt(
+ cursor.getColumnIndexOrThrow(
+ AggregatedErrorCodesContract.ErrorDataEntry.EXCEPTION_COUNT));
+ }
+ } catch (SQLiteException e) {
+ sLogger.e(
+ TAG
+ + ": Failed to query existing error counts associated with error-code: "
+ + isolatedServiceErrorCode
+ + " on day: "
+ + epochDay,
+ e);
+ }
+ // No existing records or encountered exception
+ return 0;
+ }
+
+ /** Creates table name based on owner and certDigest */
+ public static String getTableName(ComponentName owner, String certDigest) {
+ return DbUtils.getTableName(ERROR_DATA_TABLE_NAME_PREFIX, owner, certDigest);
+ }
+
+ /** Creates file directory name based on table name and base directory */
+ public static String getFileDir(String tableName, File baseDir) {
+ return baseDir + "/VendorData/" + tableName;
+ }
+
+ private boolean createTableIfNotExists(String tableName) {
+ try {
+ SQLiteDatabase db = mDbHelper.getWritableDatabase();
+ db.execSQL(
+ AggregatedErrorCodesContract.ErrorDataEntry.getCreateTableIfNotExistsStatement(
+ tableName));
+ } catch (SQLException e) {
+ sLogger.e(TAG + ": Failed to create table: " + tableName, e);
+ return false;
+ }
+ sLogger.d(TAG + ": Successfully created table: " + tableName);
+ return true;
+ }
+
+ @VisibleForTesting
+ /** Get existing error data tables in the DB. */
+ static ImmutableList<String> getErrorDataTableNames(Context context) {
+ try {
+ OnDevicePersonalizationDbHelper db =
+ OnDevicePersonalizationDbHelper.getInstance(context);
+ return getMatchingTableNames(
+ db.safeGetReadableDatabase(), ERROR_DATA_TABLE_NAME_PREFIX);
+ } catch (SQLException e) {
+ sLogger.e(TAG + ": Failed to get matching tables ", e);
+ return ImmutableList.of();
+ }
+ }
+
+ private static ImmutableList<String> getMatchingTableNames(
+ SQLiteDatabase db, String tablePrefix) {
+ try (Cursor cursor =
+ db.rawQuery(
+ "SELECT name,sql FROM sqlite_master WHERE type='table' AND name LIKE '%"
+ + tablePrefix
+ + "%'",
+ /* selectionArgs= */ null)) {
+ if (!cursor.moveToFirst()) {
+ sLogger.d(TAG + ": no tables found.");
+ return ImmutableList.of();
+ }
+
+ ImmutableList.Builder<String> listBuilder = new ImmutableList.Builder<>();
+ do {
+ String name = cursor.getString(/* columnIndex= */ 0);
+ if (name != null) {
+ listBuilder.add(name);
+ }
+ } while (cursor.moveToNext());
+
+ return listBuilder.build();
+ }
+ }
+}
diff --git a/src/com/android/ondevicepersonalization/services/data/events/EventsDao.java b/src/com/android/ondevicepersonalization/services/data/events/EventsDao.java
index 3e6dc9b..cbc43f4 100644
--- a/src/com/android/ondevicepersonalization/services/data/events/EventsDao.java
+++ b/src/com/android/ondevicepersonalization/services/data/events/EventsDao.java
@@ -110,7 +110,11 @@
* @return true if all inserts succeeded, false otherwise.
*/
public boolean insertEvents(@NonNull List<Event> events) {
- SQLiteDatabase db = mDbHelper.getWritableDatabase();
+ SQLiteDatabase db = mDbHelper.safeGetWritableDatabase();
+ if (db == null) {
+ return false;
+ }
+
try {
db.beginTransactionNonExclusive();
for (Event event : events) {
@@ -180,7 +184,11 @@
* @return true if the all the update/inserts succeeded, false otherwise
*/
public boolean updateOrInsertEventStatesTransaction(List<EventState> eventStates) {
- SQLiteDatabase db = mDbHelper.getWritableDatabase();
+ SQLiteDatabase db = mDbHelper.safeGetWritableDatabase();
+ if (db == null) {
+ return false;
+ }
+
try {
db.beginTransactionNonExclusive();
for (EventState eventState : eventStates) {
@@ -205,7 +213,11 @@
* @return eventState if found, null otherwise
*/
public EventState getEventState(String taskIdentifier, ComponentName service) {
- SQLiteDatabase db = mDbHelper.getReadableDatabase();
+ SQLiteDatabase db = mDbHelper.safeGetReadableDatabase();
+ if (db == null) {
+ return null;
+ }
+
String selection = EventStateContract.EventStateEntry.TASK_IDENTIFIER + " = ? AND "
+ EventStateContract.EventStateEntry.SERVICE_NAME + " = ?";
String[] selectionArgs = {taskIdentifier, DbUtils.toTableValue(service)};
@@ -302,7 +314,11 @@
private List<Query> readQueryRows(String selection, String[] selectionArgs) {
List<Query> queries = new ArrayList<>();
- SQLiteDatabase db = mDbHelper.getReadableDatabase();
+ SQLiteDatabase db = mDbHelper.safeGetReadableDatabase();
+ if (db == null) {
+ return queries;
+ }
+
String orderBy = QueriesContract.QueriesEntry.QUERY_ID;
try (Cursor cursor = db.query(
QueriesContract.QueriesEntry.TABLE_NAME,
@@ -342,8 +358,11 @@
private List<JoinedEvent> readJoinedTableRows(String selection, String[] selectionArgs) {
List<JoinedEvent> joinedEventList = new ArrayList<>();
+ SQLiteDatabase db = mDbHelper.safeGetReadableDatabase();
+ if (db == null) {
+ return List.of();
+ }
- SQLiteDatabase db = mDbHelper.getReadableDatabase();
String select = "SELECT "
+ EventsContract.EventsEntry.EVENT_ID + ","
+ EventsContract.EventsEntry.ROW_INDEX + ","
@@ -414,7 +433,11 @@
* @return true if the delete executed successfully, false otherwise.
*/
public boolean deleteEventState(ComponentName service) {
- SQLiteDatabase db = mDbHelper.getWritableDatabase();
+ SQLiteDatabase db = mDbHelper.safeGetWritableDatabase();
+ if (db == null) {
+ return false;
+ }
+
try {
String selection = EventStateContract.EventStateEntry.SERVICE_NAME + " = ?";
String[] selectionArgs = {DbUtils.toTableValue(service)};
@@ -433,7 +456,11 @@
* @return true if the delete executed successfully, false otherwise.
*/
public boolean deleteEventsAndQueries(long timestamp) {
- SQLiteDatabase db = mDbHelper.getWritableDatabase();
+ SQLiteDatabase db = mDbHelper.safeGetWritableDatabase();
+ if (db == null) {
+ return false;
+ }
+
try {
db.beginTransactionNonExclusive();
// Delete from events table first to satisfy FK requirements.
@@ -521,7 +548,6 @@
*/
public boolean hasEvent(long queryId, int type, int rowIndex, ComponentName service) {
try {
- int count = 0;
SQLiteDatabase db = mDbHelper.getReadableDatabase();
String[] projection = {EventsContract.EventsEntry.EVENT_ID};
String selection = EventsContract.EventsEntry.QUERY_ID + " = ?"
diff --git a/src/com/android/ondevicepersonalization/services/data/user/AdServicesCommonStatesWrapper.java b/src/com/android/ondevicepersonalization/services/data/user/AdServicesCommonStatesWrapper.java
new file mode 100644
index 0000000..afd7031
--- /dev/null
+++ b/src/com/android/ondevicepersonalization/services/data/user/AdServicesCommonStatesWrapper.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ondevicepersonalization.services.data.user;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+/**
+ * A wrapper for the AdServicesCommonStates API.
+ */
+public interface AdServicesCommonStatesWrapper {
+ /** Wrapped result from AdServicesCommonStates API */
+ class CommonStatesResult {
+ private final int mPaState;
+ private final int mMeasurementState;
+
+ /** Creates a Result */
+ public CommonStatesResult(int paState, int measurementState) {
+ mPaState = paState;
+ mMeasurementState = measurementState;
+ }
+
+ /** Returns the ProtectedAudience allowed state. */
+ public int getPaState() {
+ return mPaState;
+ }
+
+ /** Returns the Measurement allowed state. */
+ public int getMeasurementState() {
+ return mMeasurementState;
+ }
+ }
+
+ /** Returns the wrapped CommonStatesResult */
+ ListenableFuture<CommonStatesResult> getCommonStates();
+}
diff --git a/src/com/android/ondevicepersonalization/services/data/user/AdServicesCommonStatesWrapperImpl.java b/src/com/android/ondevicepersonalization/services/data/user/AdServicesCommonStatesWrapperImpl.java
new file mode 100644
index 0000000..d08c8ab
--- /dev/null
+++ b/src/com/android/ondevicepersonalization/services/data/user/AdServicesCommonStatesWrapperImpl.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ondevicepersonalization.services.data.user;
+
+import android.adservices.common.AdServicesCommonManager;
+import android.adservices.common.AdServicesCommonStates;
+import android.adservices.common.AdServicesCommonStatesResponse;
+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;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+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) {
+ mContext = Objects.requireNonNull(context);
+ }
+
+ @Override public ListenableFuture<CommonStatesResult> getCommonStates() {
+ try {
+ AdServicesCommonManager manager =
+ Objects.requireNonNull(getAdServicesCommonManager());
+ 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());
+ } catch (Exception e) {
+ return Futures.immediateFailedFuture(e);
+ }
+ }
+
+ private AdServicesCommonManager getAdServicesCommonManager() {
+ try {
+ return mContext.getSystemService(AdServicesCommonManager.class);
+ } catch (NoClassDefFoundError e) {
+ throw new IllegalStateException("Cannot find AdServicesCommonManager.", e);
+ }
+ }
+
+ private static CommonStatesResult getResultFromResponse(
+ AdServicesCommonStatesResponse response) {
+ AdServicesCommonStates commonStates = response.getAdServicesCommonStates();
+ return new CommonStatesResult(
+ commonStates.getPaState(), commonStates.getMeasurementState());
+ }
+
+ private ListenableFuture<AdServicesCommonStatesResponse> getAdServicesResponse(
+ @NonNull AdServicesCommonManager adServicesCommonManager) {
+ return CallbackToFutureAdapter.getFuture(
+ completer -> {
+ adServicesCommonManager.getAdservicesCommonStates(
+ OnDevicePersonalizationExecutors.getBackgroundExecutor(),
+ new AdServicesOutcomeReceiver<AdServicesCommonStatesResponse,
+ 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);
+ }
+ });
+ // For debugging purpose only.
+ return "getAdServicesCommonStates";
+ }
+ );
+ }
+}
diff --git a/src/com/android/ondevicepersonalization/services/data/user/UserDataCollectionJobService.java b/src/com/android/ondevicepersonalization/services/data/user/UserDataCollectionJobService.java
index 68881fa..46356d7 100644
--- a/src/com/android/ondevicepersonalization/services/data/user/UserDataCollectionJobService.java
+++ b/src/com/android/ondevicepersonalization/services/data/user/UserDataCollectionJobService.java
@@ -29,7 +29,9 @@
import android.content.ComponentName;
import android.content.Context;
+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.statsd.joblogging.OdpJobServiceLogger;
@@ -37,10 +39,9 @@
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.ListeningExecutorService;
-/**
- * JobService to collect user data in the background thread.
- */
+/** JobService to collect user data in the background thread. */
public class UserDataCollectionJobService extends JobService {
private static final LoggerFactory.Logger sLogger = LoggerFactory.getLogger();
private static final String TAG = "UserDataCollectionJobService";
@@ -50,20 +51,37 @@
private UserDataCollector mUserDataCollector;
private RawUserData mUserData;
- /**
- * Schedules a unique instance of UserDataCollectionJobService to be run.
- */
+ private final Injector mInjector;
+
+ public UserDataCollectionJobService() {
+ mInjector = new Injector();
+ }
+
+ @VisibleForTesting
+ public UserDataCollectionJobService(Injector injector) {
+ mInjector = injector;
+ }
+
+ static class Injector {
+ ListeningExecutorService getExecutor() {
+ return OnDevicePersonalizationExecutors.getBackgroundExecutor();
+ }
+
+ Flags getFlags() {
+ return FlagsFactory.getFlags();
+ }
+ }
+
+ /** Schedules a unique instance of UserDataCollectionJobService to be run. */
public static int schedule(Context context) {
JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
- if (jobScheduler.getPendingJob(
- USER_DATA_COLLECTION_ID) != null) {
+ if (jobScheduler.getPendingJob(USER_DATA_COLLECTION_ID) != null) {
sLogger.d(TAG + ": Job is already scheduled. Doing nothing,");
return RESULT_FAILURE;
}
- ComponentName serviceComponent = new ComponentName(context,
- UserDataCollectionJobService.class);
- JobInfo.Builder builder = new JobInfo.Builder(
- USER_DATA_COLLECTION_ID, serviceComponent);
+ ComponentName serviceComponent =
+ new ComponentName(context, UserDataCollectionJobService.class);
+ JobInfo.Builder builder = new JobInfo.Builder(USER_DATA_COLLECTION_ID, serviceComponent);
// Constraints
builder.setRequiresDeviceIdle(true);
@@ -80,27 +98,47 @@
@Override
public boolean onStartJob(JobParameters params) {
sLogger.d(TAG + ": onStartJob()");
- OdpJobServiceLogger.getInstance(this)
- .recordOnStartJob(USER_DATA_COLLECTION_ID);
- if (FlagsFactory.getFlags().getGlobalKillSwitch()) {
+ OdpJobServiceLogger.getInstance(this).recordOnStartJob(USER_DATA_COLLECTION_ID);
+ if (mInjector.getFlags().getGlobalKillSwitch()) {
sLogger.d(TAG + ": GlobalKillSwitch enabled, finishing job.");
- return cancelAndFinishJob(params,
+ return cancelAndFinishJob(
+ params,
AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__SKIP_FOR_KILL_SWITCH_ON);
}
- if (!UserPrivacyStatus.getInstance().isProtectedAudienceEnabled()
- && !UserPrivacyStatus.getInstance().isMeasurementEnabled()) {
- sLogger.d(TAG + ": user control is revoked, "
- + "deleting existing user data and finishing job.");
- mUserDataCollector = UserDataCollector.getInstance(this);
- mUserData = RawUserData.getInstance();
- mUserDataCollector.clearUserData(mUserData);
- mUserDataCollector.clearMetadata();
- OdpJobServiceLogger.getInstance(this).recordJobSkipped(
- USER_DATA_COLLECTION_ID,
- AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__SKIP_FOR_PERSONALIZATION_NOT_ENABLED);
- jobFinished(params, /* wantsReschedule = */ false);
- return true;
- }
+ runPrivacyStatusChecksInBackground(params);
+ return true;
+ }
+
+ private void runPrivacyStatusChecksInBackground(final JobParameters params) {
+ OnDevicePersonalizationExecutors.getHighPriorityBackgroundExecutor().execute(() -> {
+ boolean isProtectedAudienceAndMeasurementBothDisabled =
+ UserPrivacyStatus.getInstance()
+ .isProtectedAudienceAndMeasurementBothDisabled();
+ sLogger.d(TAG + ": is ProtectedAudience and Measurement both disabled: %s",
+ isProtectedAudienceAndMeasurementBothDisabled);
+ if (isProtectedAudienceAndMeasurementBothDisabled) {
+ handlePrivacyControlsRevoked(params);
+ } else {
+ startUserDataCollectionJob(params);
+ }
+ });
+ }
+
+ private void handlePrivacyControlsRevoked(JobParameters params) {
+ sLogger.d(TAG
+ + ": user control is revoked, deleting existing user data and finishing job.");
+ mUserDataCollector = UserDataCollector.getInstance(this);
+ mUserData = RawUserData.getInstance();
+ mUserDataCollector.clearUserData(mUserData);
+ mUserDataCollector.clearMetadata();
+ OdpJobServiceLogger.getInstance(this)
+ .recordJobSkipped(
+ USER_DATA_COLLECTION_ID,
+ AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__SKIP_FOR_PERSONALIZATION_NOT_ENABLED);
+ jobFinished(params, /* wantsReschedule= */ false);
+ }
+
+ private void startUserDataCollectionJob(final JobParameters params) {
mUserDataCollector = UserDataCollector.getInstance(this);
mUserData = RawUserData.getInstance();
mFuture = Futures.submit(new Runnable() {
@@ -108,13 +146,12 @@
public void run() {
sLogger.d(TAG + ": Running user data collection job");
try {
- // TODO(b/262749958): add multi-threading support if necessary.
mUserDataCollector.updateUserData(mUserData);
} catch (Exception e) {
sLogger.e(TAG + ": Failed to collect user data", e);
}
}
- }, OnDevicePersonalizationExecutors.getBackgroundExecutor());
+ }, mInjector.getExecutor());
Futures.addCallback(
mFuture,
@@ -122,32 +159,27 @@
@Override
public void onSuccess(Void result) {
sLogger.d(TAG + ": User data collection job completed.");
- boolean wantsReschedule = false;
- OdpJobServiceLogger.getInstance(UserDataCollectionJobService.this)
- .recordJobFinished(
- USER_DATA_COLLECTION_ID,
- /* isSuccessful= */ true,
- wantsReschedule);
- jobFinished(params, wantsReschedule);
+ handleJobCompletion(params, /* isSuccessful= */ true);
}
@Override
public void onFailure(Throwable t) {
- sLogger.e(TAG + ": Failed to handle JobService: " + params.getJobId(), t);
- boolean wantsReschedule = false;
- OdpJobServiceLogger.getInstance(UserDataCollectionJobService.this)
- .recordJobFinished(
- USER_DATA_COLLECTION_ID,
- /* isSuccessful= */ false,
- wantsReschedule);
- // When failure, also tell the JobScheduler that the job has completed and
- // does not need to be rescheduled.
- jobFinished(params, wantsReschedule);
+ sLogger.e(t, TAG + ": Failed to handle JobService: " + params.getJobId());
+ handleJobCompletion(params, /* isSuccessful= */ false);
}
},
- OnDevicePersonalizationExecutors.getBackgroundExecutor());
+ mInjector.getExecutor()
+ );
+ }
- return true;
+ private void handleJobCompletion(JobParameters params, boolean isSuccessful) {
+ boolean wantsReschedule = false;
+ OdpJobServiceLogger.getInstance(UserDataCollectionJobService.this)
+ .recordJobFinished(
+ USER_DATA_COLLECTION_ID,
+ isSuccessful,
+ wantsReschedule);
+ jobFinished(params, wantsReschedule);
}
@Override
@@ -158,10 +190,7 @@
// Reschedule the job since it ended before finishing
boolean wantsReschedule = true;
OdpJobServiceLogger.getInstance(this)
- .recordOnStopJob(
- params,
- USER_DATA_COLLECTION_ID,
- wantsReschedule);
+ .recordOnStopJob(params, USER_DATA_COLLECTION_ID, wantsReschedule);
return wantsReschedule;
}
@@ -170,10 +199,8 @@
if (jobScheduler != null) {
jobScheduler.cancel(USER_DATA_COLLECTION_ID);
}
- OdpJobServiceLogger.getInstance(this).recordJobSkipped(
- USER_DATA_COLLECTION_ID,
- skipReason);
- jobFinished(params, /* wantsReschedule = */ false);
+ OdpJobServiceLogger.getInstance(this).recordJobSkipped(USER_DATA_COLLECTION_ID, skipReason);
+ jobFinished(params, /* wantsReschedule= */ false);
return true;
}
}
diff --git a/src/com/android/ondevicepersonalization/services/data/user/UserDataCollector.java b/src/com/android/ondevicepersonalization/services/data/user/UserDataCollector.java
index 013d0ed..de51898 100644
--- a/src/com/android/ondevicepersonalization/services/data/user/UserDataCollector.java
+++ b/src/com/android/ondevicepersonalization/services/data/user/UserDataCollector.java
@@ -47,10 +47,12 @@
/**
* A collector for getting user data signals. This class only exposes two public operations:
- * periodic update, and real-time update. Periodic update operation will be run every 4 hours in the
- * background, given several on-device resource constraints are satisfied. Real-time update
- * operation will be run before any ads serving request and update a few time-sensitive signals in
- * UserData to the latest version.
+ * periodic update, and real-time update.
+ *
+ * <p>Periodic update operation will be run every 4 hours in the background, given several on-device
+ * resource constraints are satisfied. Real-time update operation will be run before any ads serving
+ * request and update a few time-sensitive signals in {@link
+ * android.adservices.ondevicepersonalization.UserData} to the latest version.
*/
public class UserDataCollector {
private static final int MILLISECONDS_IN_MINUTE = 60000;
@@ -60,7 +62,7 @@
private static final String TAG = UserDataCollector.class.getSimpleName();
@VisibleForTesting
- public static final Set<Integer> ALLOWED_NETWORK_TYPE =
+ static final Set<Integer> ALLOWED_NETWORK_TYPE =
Set.of(
TelephonyManager.NETWORK_TYPE_UNKNOWN,
TelephonyManager.NETWORK_TYPE_GPRS,
@@ -117,7 +119,7 @@
* testing purpose.
*/
@VisibleForTesting
- public static UserDataCollector getInstanceForTest(Context context, UserDataDao userDataDao) {
+ static UserDataCollector getInstanceForTest(Context context, UserDataDao userDataDao) {
return new UserDataCollector(context, userDataDao);
}
@@ -179,8 +181,7 @@
}
/** Collects current device's time zone in +/- offset of minutes from UTC. */
- @VisibleForTesting
- public void getUtcOffset(RawUserData userData) {
+ private static void getUtcOffset(RawUserData userData) {
try {
userData.utcOffset =
TimeZone.getDefault().getOffset(System.currentTimeMillis())
@@ -191,8 +192,7 @@
}
/** Collects the current device orientation. */
- @VisibleForTesting
- public void getOrientation(RawUserData userData) {
+ private void getOrientation(RawUserData userData) {
try {
userData.orientation = mContext.getResources().getConfiguration().orientation;
} catch (Exception e) {
@@ -201,8 +201,7 @@
}
/** Collects available bytes and converts to MB. */
- @VisibleForTesting
- public void getAvailableStorageBytes(RawUserData userData) {
+ private static void getAvailableStorageBytes(RawUserData userData) {
try {
StatFs statFs = new StatFs(Environment.getDataDirectory().getPath());
userData.availableStorageBytes = statFs.getAvailableBytes();
@@ -212,8 +211,7 @@
}
/** Collects the battery percentage of the device. */
- @VisibleForTesting
- public void getBatteryPercentage(RawUserData userData) {
+ private void getBatteryPercentage(RawUserData userData) {
try {
IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
Intent batteryStatus = mContext.registerReceiver(null, ifilter);
@@ -230,7 +228,7 @@
/** Collects carrier info. */
@VisibleForTesting
- public void getCarrier(RawUserData userData) {
+ private void getCarrier(RawUserData userData) {
// TODO (b/307158231): handle i18n later if the carrier's name is in non-English script.
try {
switch (mTelephonyManager.getSimOperatorName().toUpperCase(Locale.US)) {
@@ -270,20 +268,24 @@
}
/** Collects network capabilities. */
- @VisibleForTesting
- public void getNetworkCapabilities(RawUserData userData) {
+ private void getNetworkCapabilities(RawUserData userData) {
try {
NetworkCapabilities networkCapabilities =
mConnectivityManager.getNetworkCapabilities(
mConnectivityManager.getActiveNetwork());
+ // Returns null if network is unknown.
+ if (networkCapabilities == null) {
+ sLogger.w(TAG + ": networkCapabilities is null");
+ return;
+ }
+ sLogger.d("Successfully collected network capabilities.");
userData.networkCapabilities = getFilteredNetworkCapabilities(networkCapabilities);
} catch (Exception e) {
sLogger.w(TAG + ": Failed to collect networkCapabilities.", e);
}
}
- @VisibleForTesting
- public void getDataNetworkType(RawUserData userData) {
+ private void getDataNetworkType(RawUserData userData) {
try {
int dataNetworkType = mTelephonyManager.getDataNetworkType();
if (!ALLOWED_NETWORK_TYPE.contains(dataNetworkType)) {
@@ -296,8 +298,8 @@
}
}
- /** Util to reset all fields in [UserData] to default for testing purpose */
- public void clearUserData(@NonNull RawUserData userData) {
+ /** Util to reset all fields in passed in {@link RawUserData} to default. */
+ public static void clearUserData(@NonNull RawUserData userData) {
userData.utcOffset = 0;
userData.orientation = Configuration.ORIENTATION_PORTRAIT;
userData.availableStorageBytes = 0;
@@ -307,7 +309,7 @@
userData.installedApps.clear();
}
- /** Util to reset all in-memory metadata for testing purpose. */
+ /** Util to reset all in-memory metadata. */
public void clearMetadata() {
mInitialized = false;
}
@@ -332,7 +334,7 @@
return builder.build();
}
- /** Initials the installed app list by reading from database. */
+ /** Initialize the installed app list by reading from database. */
public void initialInstalledApp(RawUserData userData) {
Map<String, Long> existingInstallApps = mUserDataDao.getAppInstallMap();
userData.installedApps = existingInstallApps.keySet();
@@ -340,7 +342,7 @@
/** Updates app installed list if necessary. */
@VisibleForTesting
- public void updateInstalledApps(RawUserData userData) {
+ void updateInstalledApps(RawUserData userData) {
try {
Map<String, Long> existingInstallApps = mUserDataDao.getAppInstallMap();
PackageManager packageManager = mContext.getPackageManager();
@@ -370,7 +372,7 @@
currentAppInstallMap.put(packageName, currentTime);
}
- // Iterator the new app install list and remove expired apps over 30 days (ttl).
+ // Iterate the new app install list and remove expired apps over 30 days (ttl).
long ttl = FlagsFactory.getFlags().getAppInstallHistoryTtlInMillis();
for (Map.Entry<String, Long> entry : existingInstallApps.entrySet()) {
String packageName = entry.getKey();
diff --git a/src/com/android/ondevicepersonalization/services/data/user/UserPrivacyStatus.java b/src/com/android/ondevicepersonalization/services/data/user/UserPrivacyStatus.java
index 4243abb..a27a0aa 100644
--- a/src/com/android/ondevicepersonalization/services/data/user/UserPrivacyStatus.java
+++ b/src/com/android/ondevicepersonalization/services/data/user/UserPrivacyStatus.java
@@ -16,38 +16,31 @@
package com.android.ondevicepersonalization.services.data.user;
+import static android.adservices.ondevicepersonalization.Constants.API_NAME_ADSERVICES_GET_COMMON_STATES;
+import static android.adservices.ondevicepersonalization.Constants.STATUS_CALLER_NOT_ALLOWED;
+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_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;
import static com.android.ondevicepersonalization.services.PhFlags.KEY_USER_CONTROL_CACHE_IN_MILLIS;
-import android.adservices.common.AdServicesCommonManager;
-import android.adservices.common.AdServicesCommonStates;
-import android.adservices.common.AdServicesCommonStatesResponse;
-import android.adservices.common.AdServicesOutcomeReceiver;
-import android.adservices.ondevicepersonalization.Constants;
-import android.annotation.NonNull;
-import android.content.Context;
-import android.ondevicepersonalization.IOnDevicePersonalizationSystemService;
-import android.ondevicepersonalization.IOnDevicePersonalizationSystemServiceCallback;
-import android.ondevicepersonalization.OnDevicePersonalizationSystemServiceManager;
-import android.os.Bundle;
-
-import androidx.concurrent.futures.CallbackToFutureAdapter;
-
import com.android.internal.annotations.VisibleForTesting;
-import com.android.modules.utils.build.SdkLevel;
import com.android.odp.module.common.Clock;
import com.android.odp.module.common.MonotonicClock;
import com.android.ondevicepersonalization.internal.util.LoggerFactory;
-import com.android.ondevicepersonalization.services.Flags;
-import com.android.ondevicepersonalization.services.FlagsFactory;
import com.android.ondevicepersonalization.services.OnDevicePersonalizationApplication;
-import com.android.ondevicepersonalization.services.OnDevicePersonalizationExecutors;
+import com.android.ondevicepersonalization.services.StableFlags;
import com.android.ondevicepersonalization.services.reset.ResetDataJobService;
import com.android.ondevicepersonalization.services.util.DebugUtils;
+import com.android.ondevicepersonalization.services.util.StatsUtils;
-
-import com.google.common.util.concurrent.ListenableFuture;
+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.
@@ -55,53 +48,44 @@
public final class UserPrivacyStatus {
private static final String TAG = "UserPrivacyStatus";
private static final LoggerFactory.Logger sLogger = LoggerFactory.getLogger();
- private static final Clock sClock = MonotonicClock.getInstance();
private static final String PERSONALIZATION_STATUS_KEY = "PERSONALIZATION_STATUS";
@VisibleForTesting
static final int CONTROL_GIVEN_STATUS_CODE = 3;
@VisibleForTesting
static final int CONTROL_REVOKED_STATUS_CODE = 2;
- static volatile UserPrivacyStatus sUserPrivacyStatus = null;
- private boolean mPersonalizationStatusEnabled;
+ private static final Object sLock = new Object();
+ private static volatile UserPrivacyStatus sUserPrivacyStatus = null;
private boolean mProtectedAudienceEnabled;
private boolean mMeasurementEnabled;
private boolean mProtectedAudienceReset;
private boolean mMeasurementReset;
private long mLastUserControlCacheUpdate;
+ private final Clock mClock;
+ private final AdServicesCommonStatesWrapper mAdServicesCommonStatesWrapper;
- private UserPrivacyStatus() {
+ @VisibleForTesting
+ UserPrivacyStatus(
+ AdServicesCommonStatesWrapper wrapper,
+ Clock clock) {
// Assume the more privacy-safe option until updated.
- mPersonalizationStatusEnabled = false;
mProtectedAudienceEnabled = false;
mMeasurementEnabled = false;
mProtectedAudienceReset = false;
mMeasurementReset = false;
mLastUserControlCacheUpdate = -1L;
+ mAdServicesCommonStatesWrapper = Objects.requireNonNull(wrapper);
+ mClock = Objects.requireNonNull(clock);
}
/** Returns an instance of UserPrivacyStatus. */
public static UserPrivacyStatus getInstance() {
if (sUserPrivacyStatus == null) {
- synchronized (UserPrivacyStatus.class) {
+ synchronized (sLock) {
if (sUserPrivacyStatus == null) {
- sUserPrivacyStatus = new UserPrivacyStatus();
- // Restore personalization status from the system server on U+ devices.
- if (SdkLevel.isAtLeastU()) {
- sUserPrivacyStatus.restorePersonalizationStatus();
- }
- }
- }
- }
- return sUserPrivacyStatus;
- }
-
- /** Returns an instance of UserPrivacyStatus. */
- @VisibleForTesting
- public static UserPrivacyStatus getInstanceForTest() {
- if (sUserPrivacyStatus == null) {
- synchronized (UserPrivacyStatus.class) {
- if (sUserPrivacyStatus == null) {
- sUserPrivacyStatus = new UserPrivacyStatus();
+ sUserPrivacyStatus = new UserPrivacyStatus(
+ new AdServicesCommonStatesWrapperImpl(
+ OnDevicePersonalizationApplication.getAppContext()),
+ MonotonicClock.getInstance());
}
}
}
@@ -109,34 +93,34 @@
}
private static boolean isOverrideEnabled() {
- Flags flags = FlagsFactory.getFlags();
return DebugUtils.isDeveloperModeEnabled(
OnDevicePersonalizationApplication.getAppContext())
- && (boolean) flags.getStableFlag(KEY_ENABLE_PERSONALIZATION_STATUS_OVERRIDE);
+ && (boolean) StableFlags.get(KEY_ENABLE_PERSONALIZATION_STATUS_OVERRIDE);
}
- public void setPersonalizationStatusEnabled(boolean personalizationStatusEnabled) {
- Flags flags = FlagsFactory.getFlags();
- if (!isOverrideEnabled()) {
- mPersonalizationStatusEnabled = personalizationStatusEnabled;
- }
- }
-
- public boolean isPersonalizationStatusEnabled() {
- Flags flags = FlagsFactory.getFlags();
+ /**
+ * Return if both Protected Audience (PA) and Measurement consent status are disabled
+ */
+ public boolean isProtectedAudienceAndMeasurementBothDisabled() {
if (isOverrideEnabled()) {
- return (boolean) flags.getStableFlag(KEY_PERSONALIZATION_STATUS_OVERRIDE_VALUE);
+ boolean overrideToBothEnabled =
+ (boolean) StableFlags.get(KEY_PERSONALIZATION_STATUS_OVERRIDE_VALUE);
+ return !overrideToBothEnabled;
}
- return mPersonalizationStatusEnabled;
+ if (isUserControlCacheValid()) {
+ return !mProtectedAudienceEnabled && !mMeasurementEnabled;
+ }
+ // make request to AdServices#getCommonStates API once
+ fetchStateFromAdServices();
+ return !mProtectedAudienceEnabled && !mMeasurementEnabled;
}
/**
* Returns the user control status of Protected Audience (PA).
*/
public boolean isProtectedAudienceEnabled() {
- Flags flags = FlagsFactory.getFlags();
if (isOverrideEnabled()) {
- return (boolean) flags.getStableFlag(KEY_PERSONALIZATION_STATUS_OVERRIDE_VALUE);
+ return (boolean) StableFlags.get(KEY_PERSONALIZATION_STATUS_OVERRIDE_VALUE);
}
if (isUserControlCacheValid()) {
return mProtectedAudienceEnabled;
@@ -150,9 +134,8 @@
* Returns the user control status of Measurement.
*/
public boolean isMeasurementEnabled() {
- Flags flags = FlagsFactory.getFlags();
if (isOverrideEnabled()) {
- return (boolean) flags.getStableFlag(KEY_PERSONALIZATION_STATUS_OVERRIDE_VALUE);
+ return (boolean) StableFlags.get(KEY_PERSONALIZATION_STATUS_OVERRIDE_VALUE);
}
if (isUserControlCacheValid()) {
return mMeasurementEnabled;
@@ -186,7 +169,7 @@
mMeasurementEnabled = (measurementState != CONTROL_REVOKED_STATUS_CODE);
mProtectedAudienceReset = (protectedAudienceState != CONTROL_GIVEN_STATUS_CODE);
mMeasurementReset = (measurementState != CONTROL_GIVEN_STATUS_CODE);
- mLastUserControlCacheUpdate = sClock.currentTimeMillis();
+ mLastUserControlCacheUpdate = mClock.currentTimeMillis();
handleResetIfNeeded();
}
@@ -198,10 +181,9 @@
if (mLastUserControlCacheUpdate == -1L) {
return false;
}
- long cacheDuration = sClock.currentTimeMillis() - mLastUserControlCacheUpdate;
+ long cacheDuration = mClock.currentTimeMillis() - mLastUserControlCacheUpdate;
return cacheDuration >= 0
- && cacheDuration < (long) FlagsFactory.getFlags().getStableFlag(
- KEY_USER_CONTROL_CACHE_IN_MILLIS);
+ && cacheDuration < (long) StableFlags.get(KEY_USER_CONTROL_CACHE_IN_MILLIS);
}
/**
@@ -221,24 +203,38 @@
*/
@VisibleForTesting
void invalidateUserControlCacheForTesting() {
- mLastUserControlCacheUpdate = sClock.currentTimeMillis()
- - 2 * (long) FlagsFactory.getFlags().getStableFlag(
- KEY_USER_CONTROL_CACHE_IN_MILLIS);
+ mLastUserControlCacheUpdate = mClock.currentTimeMillis()
+ - 2 * (long) StableFlags.get(KEY_USER_CONTROL_CACHE_IN_MILLIS);
}
private void fetchStateFromAdServices() {
+ long startTime = mClock.elapsedRealtime();
+ String packageName = OnDevicePersonalizationApplication.getAppContext().getPackageName();
try {
// IPC.
- AdServicesCommonManager adServicesCommonManager = getAdServicesCommonManager();
- AdServicesCommonStates commonStates =
- getAdServicesCommonStates(adServicesCommonManager);
-
+ AdServicesCommonStatesWrapper.CommonStatesResult commonStates =
+ mAdServicesCommonStatesWrapper.getCommonStates().get();
+ StatsUtils.writeServiceRequestMetrics(
+ API_NAME_ADSERVICES_GET_COMMON_STATES,
+ packageName,
+ null,
+ mClock,
+ STATUS_SUCCESS,
+ startTime);
// update cache.
int updatedProtectedAudienceState = commonStates.getPaState();
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,
+ null,
+ mClock,
+ statusCode,
+ startTime);
}
}
@@ -248,100 +244,20 @@
}
}
- /**
- * Get AdServices common manager from ODP.
- */
- private static AdServicesCommonManager getAdServicesCommonManager() {
- Context odpContext = OnDevicePersonalizationApplication.getAppContext();
- try {
- return odpContext.getSystemService(AdServicesCommonManager.class);
- } catch (NoClassDefFoundError e) {
- throw new IllegalStateException("Cannot find AdServicesCommonManager.", e);
+ @VisibleForTesting
+ int getExceptionStatus(Exception e) {
+ if (e instanceof ExecutionException && e.getCause() instanceof TimeoutException) {
+ return STATUS_TIMEOUT;
}
- }
-
- /**
- * Get common states from AdServices, such as user control.
- */
- private AdServicesCommonStates getAdServicesCommonStates(
- @NonNull AdServicesCommonManager adServicesCommonManager) {
- ListenableFuture<AdServicesCommonStatesResponse> response =
- getAdServicesResponse(adServicesCommonManager);
- try {
- return response.get().getAdServicesCommonStates();
- } catch (Exception e) {
- throw new IllegalStateException("Failed when calling "
- + "AdServicesCommonManager#getAdServicesCommonStates().", e);
+ if (e instanceof NoSuchMethodException) {
+ return STATUS_METHOD_NOT_FOUND;
}
- }
-
- /**
- * IPC to AdServices API.
- */
- private ListenableFuture<AdServicesCommonStatesResponse> getAdServicesResponse(
- @NonNull AdServicesCommonManager adServicesCommonManager) {
- return CallbackToFutureAdapter.getFuture(
- completer -> {
- adServicesCommonManager.getAdservicesCommonStates(
- OnDevicePersonalizationExecutors.getBackgroundExecutor(),
- new AdServicesOutcomeReceiver<AdServicesCommonStatesResponse,
- Exception>() {
- @Override
- public void onResult(AdServicesCommonStatesResponse result) {
- completer.set(result);
- }
-
- @Override
- public void onError(Exception error) {
- completer.setException(error);
- }
- });
- // For debugging purpose only.
- return "getAdServicesCommonStates";
- }
- );
- }
-
- // TODO (b/331684191): remove SecurityException after mocking all UserPrivacyStatus
- private void restorePersonalizationStatus() {
- if (isOverrideEnabled()) {
- return;
+ if (e instanceof SecurityException) {
+ return STATUS_CALLER_NOT_ALLOWED;
}
- Context odpContext = OnDevicePersonalizationApplication.getAppContext();
- OnDevicePersonalizationSystemServiceManager systemServiceManager =
- odpContext.getSystemService(OnDevicePersonalizationSystemServiceManager.class);
- if (systemServiceManager != null) {
- IOnDevicePersonalizationSystemService systemService =
- systemServiceManager.getService();
- if (systemService != null) {
- try {
- systemService.readPersonalizationStatus(
- new IOnDevicePersonalizationSystemServiceCallback.Stub() {
- @Override
- public void onResult(Bundle bundle) {
- boolean personalizationStatus =
- bundle.getBoolean(PERSONALIZATION_STATUS_KEY);
- setPersonalizationStatusEnabled(personalizationStatus);
- }
-
- @Override
- public void onError(int errorCode) {
- if (errorCode == Constants.STATUS_KEY_NOT_FOUND) {
- sLogger.d(
- TAG
- + ": Personalization status "
- + "not found in the system server");
- }
- }
- });
- } catch (Exception e) {
- sLogger.e(TAG + ": Error when reading personalization status.", e);
- }
- } else {
- sLogger.w(TAG + ": System service is not ready.");
- }
- } else {
- sLogger.w(TAG + ": Cannot find system server on U+ devices.");
+ if (e instanceof IllegalArgumentException) {
+ return STATUS_INTERNAL_ERROR;
}
+ return STATUS_REMOTE_EXCEPTION;
}
}
diff --git a/src/com/android/ondevicepersonalization/services/data/vendor/OnDevicePersonalizationLocalDataDao.java b/src/com/android/ondevicepersonalization/services/data/vendor/OnDevicePersonalizationLocalDataDao.java
index ad80adc..6ff2061 100644
--- a/src/com/android/ondevicepersonalization/services/data/vendor/OnDevicePersonalizationLocalDataDao.java
+++ b/src/com/android/ondevicepersonalization/services/data/vendor/OnDevicePersonalizationLocalDataDao.java
@@ -25,7 +25,6 @@
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
-
import com.android.internal.annotations.VisibleForTesting;
import com.android.ondevicepersonalization.internal.util.LoggerFactory;
import com.android.ondevicepersonalization.services.data.DbUtils;
@@ -151,7 +150,11 @@
* Creates local data tables and adds corresponding vendor_settings metadata
*/
public boolean createTable() {
- SQLiteDatabase db = mDbHelper.getWritableDatabase();
+ SQLiteDatabase db = mDbHelper.safeGetWritableDatabase();
+ if (db == null) {
+ return false;
+ }
+
try {
db.beginTransactionNonExclusive();
if (!createTableIfNotExists()) {
@@ -310,7 +313,12 @@
public static void deleteTable(Context context, ComponentName owner, String certDigest) {
OnDevicePersonalizationDbHelper dbHelper =
OnDevicePersonalizationDbHelper.getInstance(context);
- SQLiteDatabase db = dbHelper.getWritableDatabase();
+ SQLiteDatabase db = dbHelper.safeGetWritableDatabase();
+ if (db == null) {
+ sLogger.e(TAG + ": Failed to get database.");
+ return;
+ }
+
db.execSQL("DROP TABLE IF EXISTS " + getTableName(owner, certDigest));
}
}
diff --git a/src/com/android/ondevicepersonalization/services/data/vendor/OnDevicePersonalizationVendorDataDao.java b/src/com/android/ondevicepersonalization/services/data/vendor/OnDevicePersonalizationVendorDataDao.java
index 4b77876..2b8d57d 100644
--- a/src/com/android/ondevicepersonalization/services/data/vendor/OnDevicePersonalizationVendorDataDao.java
+++ b/src/com/android/ondevicepersonalization/services/data/vendor/OnDevicePersonalizationVendorDataDao.java
@@ -152,7 +152,12 @@
public static List<Map.Entry<String, String>> getVendors(Context context) {
OnDevicePersonalizationDbHelper dbHelper =
OnDevicePersonalizationDbHelper.getInstance(context);
- SQLiteDatabase db = dbHelper.getReadableDatabase();
+ List<Map.Entry<String, String>> result = new ArrayList<>();
+ SQLiteDatabase db = dbHelper.safeGetReadableDatabase();
+ if (db == null) {
+ return result;
+ }
+
String[] projection = {VendorSettingsContract.VendorSettingsEntry.OWNER,
VendorSettingsContract.VendorSettingsEntry.CERT_DIGEST};
Cursor cursor = db.query(
@@ -167,7 +172,7 @@
/* limit= */ null
);
- List<Map.Entry<String, String>> result = new ArrayList<>();
+
try {
while (cursor.moveToNext()) {
String owner = cursor.getString(cursor.getColumnIndexOrThrow(
@@ -191,7 +196,11 @@
Context context, ComponentName owner, String certDigest) {
OnDevicePersonalizationDbHelper dbHelper =
OnDevicePersonalizationDbHelper.getInstance(context);
- SQLiteDatabase db = dbHelper.getWritableDatabase();
+ SQLiteDatabase db = dbHelper.safeGetWritableDatabase();
+ if (db == null) {
+ return false;
+ }
+
String vendorDataTableName = getTableName(owner, certDigest);
try {
db.beginTransactionNonExclusive();
@@ -345,7 +354,11 @@
*/
public boolean batchUpdateOrInsertVendorDataTransaction(List<VendorData> vendorDataList,
List<String> retainedKeys, long syncToken) {
- SQLiteDatabase db = mDbHelper.getWritableDatabase();
+ SQLiteDatabase db = mDbHelper.safeGetWritableDatabase();
+ if (db == null) {
+ return false;
+ }
+
try {
db.beginTransactionNonExclusive();
if (!createTableIfNotExists(mTableName)) {
@@ -566,7 +579,11 @@
* @return syncToken if found, -1 otherwise
*/
public long getSyncToken() {
- SQLiteDatabase db = mDbHelper.getReadableDatabase();
+ SQLiteDatabase db = mDbHelper.safeGetReadableDatabase();
+ if (db == null) {
+ return -1;
+ }
+
String selection = VendorSettingsContract.VendorSettingsEntry.OWNER + " = ? AND "
+ VendorSettingsContract.VendorSettingsEntry.CERT_DIGEST + " = ?";
String[] selectionArgs = {DbUtils.toTableValue(mOwner), mCertDigest};
diff --git a/src/com/android/ondevicepersonalization/services/display/WebViewFlow.java b/src/com/android/ondevicepersonalization/services/display/WebViewFlow.java
index 1731520..c5892fe 100644
--- a/src/com/android/ondevicepersonalization/services/display/WebViewFlow.java
+++ b/src/com/android/ondevicepersonalization/services/display/WebViewFlow.java
@@ -159,28 +159,33 @@
@Override
public void uploadServiceFlowMetrics(ListenableFuture<Bundle> runServiceFuture) {
- var unused = FluentFuture.from(runServiceFuture)
- .transform(
- result -> {
- StatsUtils.writeServiceRequestMetrics(
- Constants.API_NAME_SERVICE_ON_EVENT,
- result, mInjector.getClock(),
- Constants.STATUS_SUCCESS,
- mStartServiceTimeMillis);
- return null;
- },
- mInjector.getExecutor())
- .catchingAsync(
- Exception.class,
- e -> {
- StatsUtils.writeServiceRequestMetrics(
- Constants.API_NAME_SERVICE_ON_EVENT,
- /* result= */ null, mInjector.getClock(),
- Constants.STATUS_INTERNAL_ERROR,
- mStartServiceTimeMillis);
- return Futures.immediateFailedFuture(e);
- },
- mInjector.getExecutor());
+ var unused =
+ FluentFuture.from(runServiceFuture)
+ .transform(
+ result -> {
+ StatsUtils.writeServiceRequestMetrics(
+ Constants.API_NAME_SERVICE_ON_EVENT,
+ mService.getPackageName(),
+ result,
+ mInjector.getClock(),
+ Constants.STATUS_SUCCESS,
+ mStartServiceTimeMillis);
+ return null;
+ },
+ mInjector.getExecutor())
+ .catchingAsync(
+ Exception.class,
+ e -> {
+ StatsUtils.writeServiceRequestMetrics(
+ Constants.API_NAME_SERVICE_ON_EVENT,
+ mService.getPackageName(),
+ /* result= */ null,
+ mInjector.getClock(),
+ Constants.STATUS_INTERNAL_ERROR,
+ mStartServiceTimeMillis);
+ return Futures.immediateFailedFuture(e);
+ },
+ mInjector.getExecutor());
}
@Override
diff --git a/src/com/android/ondevicepersonalization/services/download/DownloadFlow.java b/src/com/android/ondevicepersonalization/services/download/DownloadFlow.java
index f8746a0..118e66a 100644
--- a/src/com/android/ondevicepersonalization/services/download/DownloadFlow.java
+++ b/src/com/android/ondevicepersonalization/services/download/DownloadFlow.java
@@ -153,10 +153,10 @@
long existingSyncToken = mDao.getSyncToken();
// If existingToken is greaterThan or equal to the new token, skip as there is
- // no new data.
+ // no new data. Mark success to upstream caller for reporting purpose
if (existingSyncToken >= syncToken) {
sLogger.d(TAG + ": syncToken is not newer than existing token.");
- mCallback.onFailure(new IllegalArgumentException("SyncToken is stale."));
+ mCallback.onSuccess(null);
return false;
}
@@ -218,27 +218,33 @@
@Override
public void uploadServiceFlowMetrics(ListenableFuture<Bundle> runServiceFuture) {
- var unused = FluentFuture.from(runServiceFuture)
- .transform(
- val -> {
- StatsUtils.writeServiceRequestMetrics(
- Constants.API_NAME_SERVICE_ON_DOWNLOAD_COMPLETED,
- val, mInjector.getClock(), Constants.STATUS_SUCCESS,
- mStartServiceTimeMillis);
- return val;
- },
- mInjector.getExecutor())
- .catchingAsync(
- Exception.class,
- e -> {
- StatsUtils.writeServiceRequestMetrics(
- Constants.API_NAME_SERVICE_ON_DOWNLOAD_COMPLETED,
- /* result= */ null, mInjector.getClock(),
- Constants.STATUS_INTERNAL_ERROR,
- mStartServiceTimeMillis);
- return Futures.immediateFailedFuture(e);
- },
- mInjector.getExecutor());
+ var unused =
+ FluentFuture.from(runServiceFuture)
+ .transform(
+ val -> {
+ StatsUtils.writeServiceRequestMetrics(
+ Constants.API_NAME_SERVICE_ON_DOWNLOAD_COMPLETED,
+ mService.getPackageName(),
+ val,
+ mInjector.getClock(),
+ Constants.STATUS_SUCCESS,
+ mStartServiceTimeMillis);
+ return val;
+ },
+ mInjector.getExecutor())
+ .catchingAsync(
+ Exception.class,
+ e -> {
+ StatsUtils.writeServiceRequestMetrics(
+ Constants.API_NAME_SERVICE_ON_DOWNLOAD_COMPLETED,
+ mService.getPackageName(),
+ /* result= */ null,
+ mInjector.getClock(),
+ Constants.STATUS_INTERNAL_ERROR,
+ mStartServiceTimeMillis);
+ return Futures.immediateFailedFuture(e);
+ },
+ mInjector.getExecutor());
}
@Override
@@ -388,7 +394,8 @@
if (cfg == null || cfg.getStatus() != ClientConfigProto.ClientFileGroup.Status.DOWNLOADED) {
sLogger.d(TAG + mPackageName + " has no completed downloads.");
- mCallback.onFailure(new IllegalArgumentException("No completed downloads."));
+ // No completed downloads is a valid case. Mark as success and return null.
+ mCallback.onSuccess(null);
return null;
}
diff --git a/src/com/android/ondevicepersonalization/services/download/OnDevicePersonalizationDataProcessingAsyncCallable.java b/src/com/android/ondevicepersonalization/services/download/OnDevicePersonalizationDataProcessingAsyncCallable.java
index 9774129..008f6cf 100644
--- a/src/com/android/ondevicepersonalization/services/download/OnDevicePersonalizationDataProcessingAsyncCallable.java
+++ b/src/com/android/ondevicepersonalization/services/download/OnDevicePersonalizationDataProcessingAsyncCallable.java
@@ -30,10 +30,8 @@
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture;
-/**
- * AsyncCallable to handle the processing of the downloaded vendor data
- */
-public class OnDevicePersonalizationDataProcessingAsyncCallable implements AsyncCallable {
+/** AsyncCallable to handle the processing of the downloaded vendor data */
+class OnDevicePersonalizationDataProcessingAsyncCallable implements AsyncCallable {
private static final LoggerFactory.Logger sLogger = LoggerFactory.getLogger();
private static final ServiceFlowOrchestrator sSfo = ServiceFlowOrchestrator.getInstance();
@@ -43,7 +41,7 @@
private final Injector mInjector;
@VisibleForTesting
- public static class Injector {
+ static class Injector {
FutureCallback<DownloadCompletedOutputParcel> getFutureCallback(
SettableFuture<Boolean> downloadFlowFuture) {
return new FutureCallback<>() {
@@ -60,23 +58,23 @@
}
}
- public OnDevicePersonalizationDataProcessingAsyncCallable(String packageName,
- Context context) {
+ OnDevicePersonalizationDataProcessingAsyncCallable(String packageName, Context context) {
this(packageName, context, new Injector());
}
@VisibleForTesting
- public OnDevicePersonalizationDataProcessingAsyncCallable(String packageName,
- Context context, Injector injector) {
+ OnDevicePersonalizationDataProcessingAsyncCallable(
+ String packageName, Context context, Injector injector) {
mPackageName = packageName;
mContext = context;
mInjector = injector;
}
/**
- * Processes the downloaded files for the given package and stores the data into sqlite
- * vendor tables.
+ * Processes the downloaded files for the given package and stores the data into sqlite vendor
+ * tables.
*/
+ @Override
public ListenableFuture<Boolean> call() {
SettableFuture<Boolean> downloadFlowFuture = SettableFuture.create();
FutureCallback<DownloadCompletedOutputParcel> callback =
diff --git a/src/com/android/ondevicepersonalization/services/download/OnDevicePersonalizationDownloadProcessingJobService.java b/src/com/android/ondevicepersonalization/services/download/OnDevicePersonalizationDownloadProcessingJobService.java
index 8c5dbfc..36a3669 100644
--- a/src/com/android/ondevicepersonalization/services/download/OnDevicePersonalizationDownloadProcessingJobService.java
+++ b/src/com/android/ondevicepersonalization/services/download/OnDevicePersonalizationDownloadProcessingJobService.java
@@ -17,7 +17,6 @@
package com.android.ondevicepersonalization.services.download;
import static android.app.job.JobScheduler.RESULT_FAILURE;
-import static android.content.pm.PackageManager.GET_META_DATA;
import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__SKIP_FOR_KILL_SWITCH_ON;
import static com.android.ondevicepersonalization.services.OnDevicePersonalizationConfig.DOWNLOAD_PROCESSING_TASK_JOB_ID;
@@ -28,13 +27,10 @@
import android.app.job.JobService;
import android.content.ComponentName;
import android.content.Context;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
import com.android.ondevicepersonalization.internal.util.LoggerFactory;
import com.android.ondevicepersonalization.services.FlagsFactory;
import com.android.ondevicepersonalization.services.OnDevicePersonalizationExecutors;
-import com.android.ondevicepersonalization.services.enrollment.PartnerEnrollmentChecker;
import com.android.ondevicepersonalization.services.manifest.AppManifestConfigHelper;
import com.android.ondevicepersonalization.services.statsd.joblogging.OdpJobServiceLogger;
@@ -90,44 +86,68 @@
return true;
}
- mFutures = new ArrayList<>();
- for (PackageInfo packageInfo : this.getPackageManager().getInstalledPackages(
- PackageManager.PackageInfoFlags.of(GET_META_DATA))) {
- String packageName = packageInfo.packageName;
- if (AppManifestConfigHelper.manifestContainsOdpSettings(
- this, packageName)) {
- if (!PartnerEnrollmentChecker.isIsolatedServiceEnrolled(packageName)) {
- sLogger.d(TAG + ": service %s has ODP manifest, but not enrolled",
- packageName);
- continue;
- }
- sLogger.d(TAG + ": service %s has ODP manifest and is enrolled", packageName);
- mFutures.add(Futures.submitAsync(
- new OnDevicePersonalizationDataProcessingAsyncCallable(packageName,
- this),
- OnDevicePersonalizationExecutors.getBackgroundExecutor()));
- }
- }
- var unused = Futures.whenAllComplete(mFutures).call(() -> {
- boolean wantsReschedule = false;
- boolean allSuccess = true;
- for (ListenableFuture<Void> future : mFutures) {
- try {
- future.get();
- } catch (Exception e) {
- allSuccess = false;
- break;
- }
- }
- OdpJobServiceLogger.getInstance(
- OnDevicePersonalizationDownloadProcessingJobService.this)
- .recordJobFinished(
- DOWNLOAD_PROCESSING_TASK_JOB_ID,
- /* isSuccessful= */ allSuccess,
- wantsReschedule);
- jobFinished(params, wantsReschedule);
- return null;
- }, OnDevicePersonalizationExecutors.getLightweightExecutor());
+ OnDevicePersonalizationExecutors.getHighPriorityBackgroundExecutor()
+ .execute(
+ () -> {
+ mFutures = new ArrayList<>();
+ // Processing installed packages
+ for (String packageName :
+ AppManifestConfigHelper.getOdpPackages(
+ /* context= */ this, /* enrolledOnly= */ true)) {
+ mFutures.add(
+ Futures.submitAsync(
+ new OnDevicePersonalizationDataProcessingAsyncCallable(
+ packageName, /* context= */ this),
+ OnDevicePersonalizationExecutors
+ .getBackgroundExecutor()));
+ }
+
+ // Handling task completion asynchronously
+ var unused =
+ Futures.whenAllComplete(mFutures)
+ .call(
+ () -> {
+ boolean wantsReschedule = false;
+ boolean allSuccess = true;
+ int successTaskCount = 0;
+ int failureTaskCount = 0;
+ for (ListenableFuture<Void> future :
+ mFutures) {
+ try {
+ future.get();
+ successTaskCount++;
+ } catch (Exception e) {
+ sLogger.e(
+ e,
+ TAG
+ + ": Error"
+ + " processing"
+ + " future");
+ failureTaskCount++;
+ allSuccess = false;
+ }
+ }
+ sLogger.d(
+ TAG
+ + ": all download"
+ + " processing tasks"
+ + " finished, %d succeeded,"
+ + " %d failed",
+ successTaskCount,
+ failureTaskCount);
+ OdpJobServiceLogger.getInstance(
+ OnDevicePersonalizationDownloadProcessingJobService
+ .this)
+ .recordJobFinished(
+ DOWNLOAD_PROCESSING_TASK_JOB_ID,
+ /* isSuccessful= */ allSuccess,
+ wantsReschedule);
+ jobFinished(params, wantsReschedule);
+ return null;
+ },
+ OnDevicePersonalizationExecutors
+ .getLightweightExecutor());
+ });
return true;
}
diff --git a/src/com/android/ondevicepersonalization/services/download/mdd/MddJobService.java b/src/com/android/ondevicepersonalization/services/download/mdd/MddJobService.java
index 4e7c686..fd16918 100644
--- a/src/com/android/ondevicepersonalization/services/download/mdd/MddJobService.java
+++ b/src/com/android/ondevicepersonalization/services/download/mdd/MddJobService.java
@@ -25,10 +25,11 @@
import android.app.job.JobParameters;
import android.app.job.JobScheduler;
import android.app.job.JobService;
-import android.content.Context;
import android.os.PersistableBundle;
+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.user.UserPrivacyStatus;
@@ -39,6 +40,7 @@
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.ListeningExecutorService;
/**
* MDD JobService. This will download MDD files in background tasks.
@@ -49,72 +51,102 @@
private String mMddTaskTag;
+ private final Injector mInjector;
+
+ public MddJobService() {
+ mInjector = new Injector();
+ }
+
+ @VisibleForTesting
+ public MddJobService(Injector injector) {
+ mInjector = injector;
+ }
+
+ static class Injector {
+ ListeningExecutorService getBackgroundExecutor() {
+ return OnDevicePersonalizationExecutors.getBackgroundExecutor();
+ }
+
+ Flags getFlags() {
+ return FlagsFactory.getFlags();
+ }
+ }
+
@Override
public boolean onStartJob(JobParameters params) {
- int jobId = getMddTaskJobId(params);
sLogger.d(TAG + ": onStartJob()");
- OdpJobServiceLogger.getInstance(this).recordOnStartJob(jobId);
- if (FlagsFactory.getFlags().getGlobalKillSwitch()) {
+ OdpJobServiceLogger.getInstance(this).recordOnStartJob(getMddTaskJobId(params));
+
+ if (mInjector.getFlags().getGlobalKillSwitch()) {
sLogger.d(TAG + ": GlobalKillSwitch enabled, finishing job.");
return cancelAndFinishJob(params,
AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__SKIP_FOR_KILL_SWITCH_ON);
}
- if (!UserPrivacyStatus.getInstance().isMeasurementEnabled()
- && !UserPrivacyStatus.getInstance().isProtectedAudienceEnabled()) {
- sLogger.d(TAG + ": User control is not given for all ODP services.");
- OdpJobServiceLogger.getInstance(this).recordJobSkipped(jobId,
- AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__SKIP_FOR_PERSONALIZATION_NOT_ENABLED);
- jobFinished(params, false);
- return true;
- }
-
- mMddTaskTag = getMddTaskTag(params);
-
- ListenableFuture<Void> handleTaskFuture =
- PropagatedFutures.submitAsync(
- () -> MobileDataDownloadFactory.getMdd(this).handleTask(mMddTaskTag),
- OnDevicePersonalizationExecutors.getBackgroundExecutor());
-
- Context context = this;
- Futures.addCallback(
- handleTaskFuture,
- new FutureCallback<Void>() {
- @Override
- public void onSuccess(Void result) {
- sLogger.d(TAG + ": MddJobService.MddHandleTask succeeded!");
- // Attempt to process any data downloaded
- if (WIFI_CHARGING_PERIODIC_TASK.equals(mMddTaskTag)) {
- OnDevicePersonalizationDownloadProcessingJobService.schedule(context);
- }
- boolean wantsReschedule = false;
- OdpJobServiceLogger.getInstance(MddJobService.this)
- .recordJobFinished(jobId,
- /* isSuccessful= */ true,
- wantsReschedule);
- // Tell the JobScheduler that the job has completed and does not needs to be
- // rescheduled.
- jobFinished(params, wantsReschedule);
- }
-
- @Override
- public void onFailure(Throwable t) {
- sLogger.e(TAG + ": Failed to handle JobService: " + jobId, t);
- boolean wantsReschedule = false;
- OdpJobServiceLogger.getInstance(MddJobService.this)
- .recordJobFinished(jobId,
- /* isSuccessful= */ false,
- wantsReschedule);
- // When failure, also tell the JobScheduler that the job has completed and
- // does not need to be rescheduled.
- jobFinished(params, wantsReschedule);
- }
- },
- OnDevicePersonalizationExecutors.getBackgroundExecutor());
-
+ // Run privacy status checks in the background
+ runPrivacyStatusChecksInBackgroundAndExecute(params);
return true;
}
+ private void runPrivacyStatusChecksInBackgroundAndExecute(final JobParameters params) {
+ int jobId = getMddTaskJobId(params);
+ OnDevicePersonalizationExecutors.getHighPriorityBackgroundExecutor().execute(() -> {
+ if (UserPrivacyStatus.getInstance().isProtectedAudienceAndMeasurementBothDisabled()) {
+ // User control is revoked; handle this case
+ sLogger.d(TAG + ": User control is not given for all ODP services.");
+ OdpJobServiceLogger.getInstance(MddJobService.this)
+ .recordJobSkipped(jobId,
+ AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__SKIP_FOR_PERSONALIZATION_NOT_ENABLED);
+ jobFinished(params, false);
+ } else {
+ // User control is given; handle the MDD task
+ mMddTaskTag = getMddTaskTag(params);
+
+ ListenableFuture<Void> handleTaskFuture =
+ PropagatedFutures.submitAsync(
+ () -> MobileDataDownloadFactory.getMdd(this)
+ .handleTask(mMddTaskTag),
+ mInjector.getBackgroundExecutor());
+
+ Futures.addCallback(
+ handleTaskFuture,
+ new FutureCallback<Void>() {
+ @Override
+ public void onSuccess(Void result) {
+ handleSuccess(jobId, params);
+ }
+
+ @Override
+ public void onFailure(Throwable t) {
+ handleFailure(jobId, params, t);
+ }
+ },
+ mInjector.getBackgroundExecutor());
+ }
+ });
+ }
+
+ private void handleSuccess(int jobId, JobParameters params) {
+ sLogger.d(TAG + ": MddJobService.MddHandleTask succeeded!");
+ if (WIFI_CHARGING_PERIODIC_TASK.equals(mMddTaskTag)) {
+ OnDevicePersonalizationDownloadProcessingJobService.schedule(this);
+ }
+ recordJobFinished(jobId, true);
+ jobFinished(params, false);
+ }
+
+ private void handleFailure(int jobId, JobParameters params, Throwable throwable) {
+ sLogger.e(TAG + ": Failed to handle JobService: " + jobId, throwable);
+ recordJobFinished(jobId, false);
+ jobFinished(params, false);
+ }
+
+ private void recordJobFinished(int jobId, boolean isSuccessful) {
+ boolean wantsReschedule = false;
+ OdpJobServiceLogger.getInstance(this)
+ .recordJobFinished(jobId, isSuccessful, wantsReschedule);
+ }
+
@Override
public boolean onStopJob(JobParameters params) {
// Attempt to process any data downloaded before the worker was stopped.
diff --git a/src/com/android/ondevicepersonalization/services/download/mdd/MddTaskScheduler.java b/src/com/android/ondevicepersonalization/services/download/mdd/MddTaskScheduler.java
index dd5edd4..a3b8cd3 100644
--- a/src/com/android/ondevicepersonalization/services/download/mdd/MddTaskScheduler.java
+++ b/src/com/android/ondevicepersonalization/services/download/mdd/MddTaskScheduler.java
@@ -28,12 +28,16 @@
import android.content.SharedPreferences;
import android.os.PersistableBundle;
+import com.android.ondevicepersonalization.internal.util.LoggerFactory;
+
import com.google.android.libraries.mobiledatadownload.TaskScheduler;
/**
* MddTaskScheduler that uses JobScheduler to schedule MDD background tasks
*/
public class MddTaskScheduler implements TaskScheduler {
+ private static final LoggerFactory.Logger sLogger = LoggerFactory.getLogger();
+ private static final String TAG = MddTaskScheduler.class.getSimpleName();
static final String MDD_TASK_TAG_KEY = "MDD_TASK_TAG_KEY";
private static final String MDD_TASK_SHARED_PREFS = "mdd_worker_task_periods";
private final Context mContext;
@@ -78,21 +82,28 @@
// When the period change, we will need to update the existing works.
boolean updateCurrent = false;
- if (prefs.getLong(mddTaskTag, 0) != periodSeconds) {
+ if (getCurrentPeriodValue(prefs, mddTaskTag) != periodSeconds) {
SharedPreferences.Editor editor = prefs.edit();
editor.putLong(mddTaskTag, periodSeconds);
editor.apply();
updateCurrent = true;
}
- if (updateCurrent) {
- schedulePeriodicTaskWithUpdate(mddTaskTag, periodSeconds, networkState);
+ JobScheduler jobScheduler = mContext.getSystemService(JobScheduler.class);
+ if (jobScheduler.getPendingJob(getMddTaskJobId(mddTaskTag)) == null) {
+ sLogger.d(TAG + ": MddJob %s is not scheduled, scheduling now", mddTaskTag);
+ schedulePeriodicTaskWithUpdate(jobScheduler, mddTaskTag, periodSeconds, networkState);
+ } else if (updateCurrent) {
+ sLogger.d(TAG + ": scheduling MddJob %s with frequency update", mddTaskTag);
+ schedulePeriodicTaskWithUpdate(jobScheduler, mddTaskTag, periodSeconds, networkState);
+ } else {
+ sLogger.d(TAG + ": MddJob %s already scheduled and frequency unchanged,"
+ + " not scheduling", mddTaskTag);
}
}
- private void schedulePeriodicTaskWithUpdate(String mddTaskTag, long periodSeconds,
- NetworkState networkState) {
- final JobScheduler jobScheduler = mContext.getSystemService(JobScheduler.class);
+ private void schedulePeriodicTaskWithUpdate(JobScheduler jobScheduler, String mddTaskTag,
+ long periodSeconds, NetworkState networkState) {
// We use Extra to pass the MDD Task Tag. This will be used in the MddJobService.
PersistableBundle extras = new PersistableBundle();
@@ -113,4 +124,14 @@
.build();
jobScheduler.schedule(job);
}
+
+ private long getCurrentPeriodValue(SharedPreferences prefs, String mddTaskTag) {
+ try {
+ return prefs.getLong(mddTaskTag, 0);
+ } catch (ClassCastException e) {
+ sLogger.w(e, TAG + ": ClassCastException retrieving long value from prefs for tag: %s",
+ mddTaskTag);
+ return 0;
+ }
+ }
}
diff --git a/src/com/android/ondevicepersonalization/services/download/mdd/OnDevicePersonalizationFileGroupPopulator.java b/src/com/android/ondevicepersonalization/services/download/mdd/OnDevicePersonalizationFileGroupPopulator.java
index 482de97..476eec1 100644
--- a/src/com/android/ondevicepersonalization/services/download/mdd/OnDevicePersonalizationFileGroupPopulator.java
+++ b/src/com/android/ondevicepersonalization/services/download/mdd/OnDevicePersonalizationFileGroupPopulator.java
@@ -16,11 +16,8 @@
package com.android.ondevicepersonalization.services.download.mdd;
-import static android.content.pm.PackageManager.GET_META_DATA;
-
import android.content.ComponentName;
import android.content.Context;
-import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.SystemProperties;
@@ -30,7 +27,6 @@
import com.android.ondevicepersonalization.internal.util.LoggerFactory;
import com.android.ondevicepersonalization.services.OnDevicePersonalizationExecutors;
import com.android.ondevicepersonalization.services.data.vendor.OnDevicePersonalizationVendorDataDao;
-import com.android.ondevicepersonalization.services.enrollment.PartnerEnrollmentChecker;
import com.android.ondevicepersonalization.services.manifest.AppManifestConfigHelper;
import com.google.android.libraries.mobiledatadownload.AddFileGroupRequest;
@@ -77,10 +73,9 @@
this.mContext = context;
}
- /**
- * A helper function to create a DataFilegroup.
- */
- public static DataFileGroup createDataFileGroup(
+ /** A helper function to create a DataFilegroup. */
+ @VisibleForTesting
+ static DataFileGroup createDataFileGroup(
String groupName,
String ownerPackage,
String[] fileId,
@@ -138,12 +133,12 @@
* Creates the MDD download URL for the given package
*
* @param packageName PackageName of the package owning the fileGroup
- * @param context Context of the calling service/application
+ * @param context Context of the calling service/application
* @return The created MDD URL for the package.
*/
@VisibleForTesting
- public static String createDownloadUrl(String packageName, Context context) throws
- PackageManager.NameNotFoundException {
+ static String createDownloadUrl(String packageName, Context context)
+ throws PackageManager.NameNotFoundException {
String baseURL = AppManifestConfigHelper.getDownloadUrlFromOdpSettings(
context, packageName);
@@ -202,76 +197,75 @@
GetFileGroupsByFilterRequest request =
GetFileGroupsByFilterRequest.newBuilder().setIncludeAllGroups(true).build();
return FluentFuture.from(mobileDataDownload.getFileGroupsByFilter(request))
- .transformAsync(fileGroupList -> {
- Set<String> fileGroupsToRemove = new HashSet<>();
- for (ClientConfigProto.ClientFileGroup fileGroup : fileGroupList) {
- fileGroupsToRemove.add(fileGroup.getGroupName());
- }
- List<ListenableFuture<Boolean>> mFutures = new ArrayList<>();
- for (PackageInfo packageInfo : mContext.getPackageManager()
- .getInstalledPackages(
- PackageManager.PackageInfoFlags.of(GET_META_DATA))) {
- String packageName = packageInfo.packageName;
- if (AppManifestConfigHelper.manifestContainsOdpSettings(
- mContext, packageName)) {
- if (!PartnerEnrollmentChecker.isIsolatedServiceEnrolled(packageName)) {
- sLogger.d(TAG + ": service %s has ODP manifest, "
- + "but not enrolled", packageName);
- continue;
+ .transformAsync(
+ fileGroupList -> {
+ Set<String> fileGroupsToRemove = new HashSet<>();
+ for (ClientConfigProto.ClientFileGroup fileGroup : fileGroupList) {
+ fileGroupsToRemove.add(fileGroup.getGroupName());
}
- sLogger.d(TAG + ": service %s has ODP manifest and is enrolled",
- packageName);
- try {
- String groupName = createPackageFileGroupName(
- packageName,
- mContext);
- fileGroupsToRemove.remove(groupName);
- String ownerPackage = mContext.getPackageName();
- String fileId = groupName;
- int byteSize = 0;
- String checksum = "";
- ChecksumType checksumType = ChecksumType.NONE;
- String downloadUrl = createDownloadUrl(packageName,
- mContext);
- DeviceNetworkPolicy deviceNetworkPolicy =
- DeviceNetworkPolicy.DOWNLOAD_ONLY_ON_WIFI;
- DataFileGroup dataFileGroup = createDataFileGroup(
- groupName,
- ownerPackage,
- new String[]{fileId},
- new int[]{byteSize},
- new String[]{checksum},
- new ChecksumType[]{checksumType},
- new String[]{downloadUrl},
- deviceNetworkPolicy);
- mFutures.add(mobileDataDownload.addFileGroup(
- AddFileGroupRequest.newBuilder().setDataFileGroup(
- dataFileGroup).build()));
- } catch (Exception e) {
- sLogger.e(TAG + ": Failed to create file group for "
- + packageName, e);
- }
- }
- }
-
- for (String group : fileGroupsToRemove) {
- sLogger.d(TAG + ": Removing file group: " + group);
- mFutures.add(mobileDataDownload.removeFileGroup(
- RemoveFileGroupRequest.newBuilder().setGroupName(group).build()));
- }
-
- return PropagatedFutures.transform(
- Futures.successfulAsList(mFutures),
- result -> {
- if (result.contains(null)) {
- sLogger.d(TAG + ": Failed to add or remove a file group");
- } else {
- sLogger.d(TAG + ": Successfully updated all file groups");
+ List<ListenableFuture<Boolean>> mFutures = new ArrayList<>();
+ for (String packageName :
+ AppManifestConfigHelper.getOdpPackages(
+ mContext, /* enrolledOnly= */ true)) {
+ try {
+ String groupName =
+ createPackageFileGroupName(packageName, mContext);
+ fileGroupsToRemove.remove(groupName);
+ String ownerPackage = mContext.getPackageName();
+ String fileId = groupName;
+ int byteSize = 0;
+ String checksum = "";
+ ChecksumType checksumType = ChecksumType.NONE;
+ String downloadUrl = createDownloadUrl(packageName, mContext);
+ DeviceNetworkPolicy deviceNetworkPolicy =
+ DeviceNetworkPolicy.DOWNLOAD_ONLY_ON_WIFI;
+ DataFileGroup dataFileGroup =
+ createDataFileGroup(
+ groupName,
+ ownerPackage,
+ new String[] {fileId},
+ new int[] {byteSize},
+ new String[] {checksum},
+ new ChecksumType[] {checksumType},
+ new String[] {downloadUrl},
+ deviceNetworkPolicy);
+ mFutures.add(
+ mobileDataDownload.addFileGroup(
+ AddFileGroupRequest.newBuilder()
+ .setDataFileGroup(dataFileGroup)
+ .build()));
+ } catch (Exception e) {
+ sLogger.e(
+ TAG
+ + ": Failed to create file group for "
+ + packageName,
+ e);
}
- return null;
- },
- OnDevicePersonalizationExecutors.getBackgroundExecutor()
- );
- }, OnDevicePersonalizationExecutors.getBackgroundExecutor());
+ }
+
+ for (String group : fileGroupsToRemove) {
+ sLogger.d(TAG + ": Removing file group: " + group);
+ mFutures.add(
+ mobileDataDownload.removeFileGroup(
+ RemoveFileGroupRequest.newBuilder()
+ .setGroupName(group)
+ .build()));
+ }
+
+ return PropagatedFutures.transform(
+ Futures.successfulAsList(mFutures),
+ result -> {
+ if (result.contains(null)) {
+ sLogger.d(
+ TAG + ": Failed to add or remove a file group");
+ } else {
+ sLogger.d(
+ TAG + ": Successfully updated all file groups");
+ }
+ return null;
+ },
+ OnDevicePersonalizationExecutors.getBackgroundExecutor());
+ },
+ OnDevicePersonalizationExecutors.getBackgroundExecutor());
}
}
diff --git a/src/com/android/ondevicepersonalization/services/federatedcompute/FederatedComputeServiceImpl.java b/src/com/android/ondevicepersonalization/services/federatedcompute/FederatedComputeServiceImpl.java
index ef6657d..5a395ba 100644
--- a/src/com/android/ondevicepersonalization/services/federatedcompute/FederatedComputeServiceImpl.java
+++ b/src/com/android/ondevicepersonalization/services/federatedcompute/FederatedComputeServiceImpl.java
@@ -16,29 +16,27 @@
package com.android.ondevicepersonalization.services.federatedcompute;
+import android.adservices.ondevicepersonalization.Constants;
import android.adservices.ondevicepersonalization.aidl.IFederatedComputeCallback;
import android.adservices.ondevicepersonalization.aidl.IFederatedComputeService;
import android.annotation.NonNull;
import android.content.ComponentName;
import android.content.Context;
-import android.content.pm.PackageManager;
import android.federatedcompute.FederatedComputeManager;
import android.federatedcompute.common.ClientConstants;
import android.federatedcompute.common.ScheduleFederatedComputeRequest;
import android.federatedcompute.common.TrainingOptions;
import android.os.OutcomeReceiver;
import android.os.RemoteException;
-import android.os.SystemProperties;
-import android.provider.DeviceConfig;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.odp.module.common.PackageUtils;
import com.android.ondevicepersonalization.internal.util.LoggerFactory;
import com.android.ondevicepersonalization.services.OnDevicePersonalizationExecutors;
import com.android.ondevicepersonalization.services.data.events.EventState;
import com.android.ondevicepersonalization.services.data.events.EventsDao;
import com.android.ondevicepersonalization.services.data.user.UserPrivacyStatus;
import com.android.ondevicepersonalization.services.manifest.AppManifestConfigHelper;
+import com.android.ondevicepersonalization.services.util.DebugUtils;
import com.google.common.util.concurrent.ListeningExecutorService;
@@ -53,13 +51,8 @@
private static final LoggerFactory.Logger sLogger = LoggerFactory.getLogger();
private static final String TAG = "FederatedComputeServiceImpl";
- private static final String OVERRIDE_FC_SERVER_URL_PACKAGE =
- "debug.ondevicepersonalization.override_fc_server_url_package";
- private static final String OVERRIDE_FC_SERVER_URL =
- "debug.ondevicepersonalization.override_fc_server_url";
-
@NonNull private final Context mApplicationContext;
- @NonNull private ComponentName mCallingService;
+ @NonNull private final ComponentName mCallingService;
@NonNull private final Injector mInjector;
@NonNull private final FederatedComputeManager mFederatedComputeManager;
@@ -91,44 +84,18 @@
try {
if (!UserPrivacyStatus.getInstance().isMeasurementEnabled()) {
sLogger.d(TAG + ": measurement control is revoked.");
- sendError(callback);
+ sendError(callback, Constants.STATUS_PERSONALIZATION_DISABLED);
return;
}
String url =
AppManifestConfigHelper.getFcRemoteServerUrlFromOdpSettings(
mApplicationContext, mCallingService.getPackageName());
-
- // Check for override manifest url property, if package is debuggable
- if (PackageUtils.isPackageDebuggable(
- mApplicationContext, mCallingService.getPackageName())) {
- if (SystemProperties.get(OVERRIDE_FC_SERVER_URL_PACKAGE, "")
- .equals(mCallingService.getPackageName())) {
- String overrideManifestUrl = SystemProperties.get(OVERRIDE_FC_SERVER_URL, "");
- if (!overrideManifestUrl.isEmpty()) {
- sLogger.d(
- TAG
- + ": Overriding fc server URL for package "
- + mCallingService.getPackageName()
- + " to "
- + overrideManifestUrl);
- url = overrideManifestUrl;
- }
- String deviceConfigOverrideUrl =
- DeviceConfig.getString(
- /* namespace= */ "on_device_personalization",
- /* name= */ OVERRIDE_FC_SERVER_URL,
- /* defaultValue= */ "");
- if (!deviceConfigOverrideUrl.isEmpty()) {
- sLogger.d(
- TAG
- + ": Overriding fc server URL for package "
- + mCallingService.getPackageName()
- + " to "
- + deviceConfigOverrideUrl);
- url = deviceConfigOverrideUrl;
- }
- }
+ String overrideUrl =
+ DebugUtils.getFcServerOverrideUrl(
+ mApplicationContext, mCallingService.getPackageName());
+ if (!overrideUrl.isEmpty()) {
+ url = overrideUrl;
}
if (url == null) {
@@ -136,7 +103,7 @@
TAG
+ ": Missing remote server URL for package: "
+ mCallingService.getPackageName());
- sendError(callback);
+ sendError(callback, Constants.STATUS_FCP_MANIFEST_INVALID);
return;
}
@@ -179,9 +146,11 @@
sendError(callback);
}
});
- } catch (IOException | PackageManager.NameNotFoundException e) {
+ } catch (IOException | IllegalArgumentException e) {
+ // The AppManifestConfigHelper methods throw IllegalArgumentExceptions when
+ // parsings fails or the fc settings URL is missing.
sLogger.e(TAG + ": Error while scheduling federatedCompute", e);
- sendError(callback);
+ sendError(callback, Constants.STATUS_FCP_MANIFEST_INVALID);
}
}
@@ -217,7 +186,7 @@
});
}
- private void sendSuccess(@NonNull IFederatedComputeCallback callback) {
+ private static void sendSuccess(@NonNull IFederatedComputeCallback callback) {
try {
callback.onSuccess();
} catch (RemoteException e) {
@@ -225,9 +194,13 @@
}
}
- private void sendError(@NonNull IFederatedComputeCallback callback) {
+ private static void sendError(@NonNull IFederatedComputeCallback callback) {
+ sendError(callback, ClientConstants.STATUS_INTERNAL_ERROR);
+ }
+
+ private static void sendError(@NonNull IFederatedComputeCallback callback, int errorCode) {
try {
- callback.onFailure(ClientConstants.STATUS_INTERNAL_ERROR);
+ callback.onFailure(errorCode);
} catch (RemoteException e) {
sLogger.e(TAG + ": Callback error", e);
}
diff --git a/src/com/android/ondevicepersonalization/services/federatedcompute/OdpExampleStoreService.java b/src/com/android/ondevicepersonalization/services/federatedcompute/OdpExampleStoreService.java
index c351ec6..d6c23b5 100644
--- a/src/com/android/ondevicepersonalization/services/federatedcompute/OdpExampleStoreService.java
+++ b/src/com/android/ondevicepersonalization/services/federatedcompute/OdpExampleStoreService.java
@@ -17,6 +17,7 @@
package com.android.ondevicepersonalization.services.federatedcompute;
import android.adservices.ondevicepersonalization.Constants;
+import android.adservices.ondevicepersonalization.IsolatedServiceException;
import android.adservices.ondevicepersonalization.TrainingExampleRecord;
import android.adservices.ondevicepersonalization.TrainingExamplesInputParcel;
import android.adservices.ondevicepersonalization.TrainingExamplesOutputParcel;
@@ -32,10 +33,12 @@
import com.android.odp.module.common.Clock;
import com.android.odp.module.common.MonotonicClock;
+import com.android.odp.module.common.PackageUtils;
import com.android.ondevicepersonalization.internal.util.LoggerFactory;
import com.android.ondevicepersonalization.internal.util.OdpParceledListSlice;
import com.android.ondevicepersonalization.services.Flags;
import com.android.ondevicepersonalization.services.FlagsFactory;
+import com.android.ondevicepersonalization.services.OdpServiceException;
import com.android.ondevicepersonalization.services.OnDevicePersonalizationExecutors;
import com.android.ondevicepersonalization.services.data.DataAccessPermission;
import com.android.ondevicepersonalization.services.data.DataAccessServiceImpl;
@@ -48,6 +51,7 @@
import com.android.ondevicepersonalization.services.process.PluginProcessRunner;
import com.android.ondevicepersonalization.services.process.ProcessRunner;
import com.android.ondevicepersonalization.services.process.SharedIsolatedProcessRunner;
+import com.android.ondevicepersonalization.services.util.AllowListUtils;
import com.android.ondevicepersonalization.services.util.StatsUtils;
import com.google.common.util.concurrent.FluentFuture;
@@ -58,6 +62,7 @@
import java.util.Objects;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
/** Implementation of ExampleStoreService for OnDevicePersonalization */
public final class OdpExampleStoreService extends ExampleStoreService {
@@ -106,6 +111,7 @@
@Override
public void startQuery(@NonNull Bundle params, @NonNull QueryCallback callback) {
try {
+ long startTime = mInjector.getClock().currentTimeMillis();
ContextData contextData =
ContextData.fromByteArray(
Objects.requireNonNull(
@@ -126,6 +132,13 @@
if (!UserPrivacyStatus.getInstance().isMeasurementEnabled()) {
privacyStatusEligible = false;
sLogger.w(TAG + ": Measurement control is not given.");
+ StatsUtils.writeServiceRequestMetrics(
+ Constants.API_NAME_SERVICE_ON_TRAINING_EXAMPLE,
+ packageName,
+ null,
+ mInjector.getClock(),
+ Constants.STATUS_PERSONALIZATION_DISABLED,
+ startTime);
}
// Cancel job if on longer valid. This is written to the table during scheduling
@@ -133,6 +146,15 @@
// during maintenance for uninstalled packages.
ComponentName owner = ComponentName.createRelative(packageName, ownerClassName);
EventState eventStatePopulation = eventDao.getEventState(populationName, owner);
+ if (eventStatePopulation == null) {
+ StatsUtils.writeServiceRequestMetrics(
+ Constants.API_NAME_SERVICE_ON_TRAINING_EXAMPLE,
+ packageName,
+ null,
+ mInjector.getClock(),
+ Constants.STATUS_KEY_NOT_FOUND,
+ startTime);
+ }
if (!privacyStatusEligible || eventStatePopulation == null) {
sLogger.w("Job was either cancelled or package was uninstalled");
// Cancel job.
@@ -194,20 +216,13 @@
.loadIsolatedService(
TASK_NAME,
ComponentName.createRelative(packageName, className));
- ListenableFuture<TrainingExamplesOutputParcel> resultFuture =
+ ListenableFuture<Bundle> resultFuture =
FluentFuture.from(loadFuture)
.transformAsync(
result ->
executeOnTrainingExamples(
result, input.build(), packageName),
OnDevicePersonalizationExecutors.getBackgroundExecutor())
- .transform(
- result -> {
- return result.getParcelable(
- Constants.EXTRA_RESULT,
- TrainingExamplesOutputParcel.class);
- },
- OnDevicePersonalizationExecutors.getBackgroundExecutor())
.withTimeout(
mInjector.getFlags().getIsolatedServiceDeadlineSeconds(),
TimeUnit.SECONDS,
@@ -215,29 +230,72 @@
Futures.addCallback(
resultFuture,
- new FutureCallback<TrainingExamplesOutputParcel>() {
+ new FutureCallback<Bundle>() {
@Override
- public void onSuccess(
- TrainingExamplesOutputParcel trainingExamplesOutputParcel) {
- OdpParceledListSlice<TrainingExampleRecord> trainingExampleRecordList =
- trainingExamplesOutputParcel.getTrainingExampleRecords();
-
- if (trainingExampleRecordList == null
- || trainingExampleRecordList.getList().size()
- < eligibilityMinExample) {
- callback.onStartQueryFailure(
- ClientConstants.STATUS_NOT_ENOUGH_DATA);
- } else {
- callback.onStartQuerySuccess(
- OdpExampleStoreIteratorFactory.getInstance()
- .createIterator(
- trainingExampleRecordList.getList()));
+ public void onSuccess(Bundle result) {
+ int status = Constants.STATUS_SUCCESS;
+ try {
+ TrainingExamplesOutputParcel trainingExamplesOutputParcel =
+ result.getParcelable(
+ Constants.EXTRA_RESULT,
+ TrainingExamplesOutputParcel.class);
+ if (trainingExamplesOutputParcel == null) {
+ status = Constants.STATUS_NAME_NOT_FOUND;
+ callback.onStartQueryFailure(
+ ClientConstants.STATUS_INTERNAL_ERROR);
+ return;
+ }
+ OdpParceledListSlice<TrainingExampleRecord>
+ trainingExampleRecordList =
+ trainingExamplesOutputParcel
+ .getTrainingExampleRecords();
+ if (trainingExampleRecordList == null
+ || trainingExampleRecordList.getList().isEmpty()
+ || trainingExampleRecordList.getList().size()
+ < eligibilityMinExample) {
+ status = Constants.STATUS_SUCCESS_EMPTY_RESULT;
+ callback.onStartQueryFailure(
+ ClientConstants.STATUS_NOT_ENOUGH_DATA);
+ } else {
+ callback.onStartQuerySuccess(
+ OdpExampleStoreIteratorFactory.getInstance()
+ .createIterator(
+ trainingExampleRecordList.getList()));
+ }
+ } finally {
+ StatsUtils.writeServiceRequestMetrics(
+ Constants.API_NAME_SERVICE_ON_TRAINING_EXAMPLE,
+ packageName,
+ result,
+ mInjector.getClock(),
+ status,
+ startTime);
}
}
@Override
public void onFailure(Throwable t) {
+ int status = Constants.STATUS_INTERNAL_ERROR;
+ if (t instanceof TimeoutException) {
+ status = Constants.STATUS_TIMEOUT;
+ } else if (t instanceof OdpServiceException exp) {
+ if (exp.getCause() instanceof IsolatedServiceException
+ && isLogIsolatedServiceErrorCodeNonAggregatedAllowed(
+ packageName)) {
+ status = ((IsolatedServiceException) exp.getCause())
+ .getErrorCode();
+ } else {
+ status = exp.getErrorCode();
+ }
+ }
sLogger.w(t, "%s : Request failed.", TAG);
+ StatsUtils.writeServiceRequestMetrics(
+ Constants.API_NAME_SERVICE_ON_TRAINING_EXAMPLE,
+ packageName,
+ null,
+ mInjector.getClock(),
+ status,
+ startTime);
callback.onStartQueryFailure(ClientConstants.STATUS_INTERNAL_ERROR);
}
},
@@ -253,6 +311,9 @@
OnDevicePersonalizationExecutors.getBackgroundExecutor());
} catch (Exception e) {
sLogger.w(e, "%s : Start query failed.", TAG);
+ StatsUtils.writeServiceRequestMetrics(
+ Constants.API_NAME_SERVICE_ON_TRAINING_EXAMPLE,
+ Constants.STATUS_INTERNAL_ERROR);
callback.onStartQueryFailure(ClientConstants.STATUS_INTERNAL_ERROR);
}
}
@@ -276,39 +337,46 @@
/* eventDataPermission */ DataAccessPermission.READ_ONLY);
serviceParams.putBinder(Constants.EXTRA_DATA_ACCESS_SERVICE_BINDER, binder);
UserDataAccessor userDataAccessor = new UserDataAccessor();
- UserData userData = userDataAccessor.getUserDataWithAppInstall();
+ UserData userData;
+ // By default, we don't provide platform data for federated learning flow.
+ if (isPlatformDataProvided(packageName)) {
+ userData = userDataAccessor.getUserDataWithAppInstall();
+ } else {
+ userData = userDataAccessor.getUserData();
+ }
serviceParams.putParcelable(Constants.EXTRA_USER_DATA, userData);
- ListenableFuture<Bundle> result =
- mInjector
- .getProcessRunner()
- .runIsolatedService(
- isolatedServiceInfo, Constants.OP_TRAINING_EXAMPLE, serviceParams);
- return FluentFuture.from(result)
- .transform(
- val -> {
- StatsUtils.writeServiceRequestMetrics(
- Constants.API_NAME_SERVICE_ON_TRAINING_EXAMPLE,
- val, mInjector.getClock(),
- Constants.STATUS_SUCCESS,
- isolatedServiceInfo.getStartTimeMillis());
- return val;
- },
- OnDevicePersonalizationExecutors.getBackgroundExecutor())
- .catchingAsync(
- Exception.class,
- e -> {
- StatsUtils.writeServiceRequestMetrics(
- Constants.API_NAME_SERVICE_ON_TRAINING_EXAMPLE,
- /* result= */ null, mInjector.getClock(),
- Constants.STATUS_INTERNAL_ERROR,
- isolatedServiceInfo.getStartTimeMillis());
- return Futures.immediateFailedFuture(e);
- },
- OnDevicePersonalizationExecutors.getBackgroundExecutor());
+ return mInjector
+ .getProcessRunner()
+ .runIsolatedService(
+ isolatedServiceInfo, Constants.OP_TRAINING_EXAMPLE, serviceParams);
}
// used for tests to provide mock/real implementation of context.
private Context getContext() {
return this.getApplicationContext();
}
+
+ private boolean isPlatformDataProvided(String packageName) {
+ try {
+ return AllowListUtils.isAllowListed(
+ packageName,
+ PackageUtils.getCertDigest(getContext(), packageName),
+ mInjector.getFlags().getDefaultPlatformDataForExecuteAllowlist());
+ } catch (Exception e) {
+ sLogger.d(TAG + ": allow list error", e);
+ return false;
+ }
+ }
+
+ private boolean isLogIsolatedServiceErrorCodeNonAggregatedAllowed(String packageName) {
+ try {
+ return AllowListUtils.isAllowListed(
+ packageName,
+ null,
+ mInjector.getFlags().getLogIsolatedServiceErrorCodeNonAggregatedAllowlist());
+ } catch (Exception e) {
+ sLogger.d(e, TAG + ": check isLogIsolatedServiceErrorCodeNonAggregatedAllowed error");
+ return false;
+ }
+ }
}
diff --git a/src/com/android/ondevicepersonalization/services/inference/IsolatedModelServiceImpl.java b/src/com/android/ondevicepersonalization/services/inference/IsolatedModelServiceImpl.java
index b14f519..86d1203 100644
--- a/src/com/android/ondevicepersonalization/services/inference/IsolatedModelServiceImpl.java
+++ b/src/com/android/ondevicepersonalization/services/inference/IsolatedModelServiceImpl.java
@@ -21,6 +21,7 @@
import android.adservices.ondevicepersonalization.InferenceOutput;
import android.adservices.ondevicepersonalization.InferenceOutputParcel;
import android.adservices.ondevicepersonalization.ModelId;
+import android.adservices.ondevicepersonalization.OnDevicePersonalizationException;
import android.adservices.ondevicepersonalization.aidl.IDataAccessService;
import android.adservices.ondevicepersonalization.aidl.IDataAccessServiceCallback;
import android.adservices.ondevicepersonalization.aidl.IIsolatedModelService;
@@ -94,28 +95,37 @@
IIsolatedModelServiceCallback callback) {
try {
Trace.beginSection("IsolatedModelService#RunInference");
+ // We already validate requests in ModelManager and double check in case.
Object[] inputs = convertToObjArray(inputParcel.getInputData().getList());
- if (inputs.length == 0) {
- sendError(callback);
+ if (inputs == null || inputs.length == 0) {
+ sLogger.e("Input data can not be empty for inference.");
+ sendError(callback, OnDevicePersonalizationException.ERROR_INFERENCE_FAILED);
+ }
+ Map<Integer, Object> outputs = outputParcel.getData();
+ if (outputs.isEmpty()) {
+ sLogger.e("Output data can not be empty for inference.");
+ sendError(callback, OnDevicePersonalizationException.ERROR_INFERENCE_FAILED);
}
ModelId modelId = inputParcel.getModelId();
ParcelFileDescriptor modelFd = fetchModel(binder, modelId);
+ if (modelFd == null) {
+ sLogger.e(TAG + ": Failed to fetch model %s.", modelId.getKey());
+ sendError(
+ callback, OnDevicePersonalizationException.ERROR_INFERENCE_MODEL_NOT_FOUND);
+ return;
+ }
ByteBuffer byteBuffer = IoUtils.getByteBufferFromFd(modelFd);
if (byteBuffer == null) {
closeFd(modelFd);
- sendError(callback);
+ sendError(
+ callback, OnDevicePersonalizationException.ERROR_INFERENCE_MODEL_NOT_FOUND);
}
InterpreterApi interpreter =
InterpreterApi.create(
byteBuffer,
new InterpreterApi.Options()
.setNumThreads(inputParcel.getCpuNumThread()));
- Map<Integer, Object> outputs = outputParcel.getData();
- if (outputs.isEmpty() || inputs.length == 0) {
- closeFd(modelFd);
- sendError(callback);
- }
// TODO(b/323469981): handle batch size better. Currently TFLite will throws error if
// batchSize doesn't match input data size.
@@ -138,7 +148,7 @@
} catch (Exception e) {
// Catch all exceptions including TFLite errors.
sLogger.e(e, TAG + ": Failed to run inference job.");
- sendError(callback);
+ sendError(callback, OnDevicePersonalizationException.ERROR_INFERENCE_FAILED);
}
}
@@ -149,8 +159,9 @@
try {
ObjectInputStream ois = new ObjectInputStream(bais);
output[i] = ois.readObject();
- } catch (IOException | ClassNotFoundException e) {
- throw new RuntimeException(e);
+ } catch (Exception e) {
+ sLogger.e(e, "Failed to parse inference input");
+ return null;
}
}
return output;
@@ -191,23 +202,22 @@
Bundle result = asyncResult.take();
ParcelFileDescriptor modelFd =
result.getParcelable(Constants.EXTRA_RESULT, ParcelFileDescriptor.class);
- Objects.requireNonNull(modelFd);
return modelFd;
- } catch (InterruptedException | RemoteException e) {
- sLogger.e(TAG + ": Failed to fetch model from DataAccessService", e);
- throw new IllegalStateException(e);
+ } catch (Exception e) {
+ sLogger.e(e, TAG + ": Failed to fetch model from DataAccessService");
+ return null;
}
}
- private void sendError(@NonNull IIsolatedModelServiceCallback callback) {
+ private static void sendError(@NonNull IIsolatedModelServiceCallback callback, int errorCode) {
try {
- callback.onError(Constants.STATUS_INTERNAL_ERROR);
+ callback.onError(errorCode);
} catch (RemoteException e) {
sLogger.e(TAG + ": Callback error", e);
}
}
- private void sendResult(
+ private static void sendResult(
@NonNull Bundle result, @NonNull IIsolatedModelServiceCallback callback) {
try {
callback.onSuccess(result);
diff --git a/src/com/android/ondevicepersonalization/services/maintenance/OnDevicePersonalizationMaintenanceJob.java b/src/com/android/ondevicepersonalization/services/maintenance/OnDevicePersonalizationMaintenanceJob.java
index a489ec1..5202cb2 100644
--- a/src/com/android/ondevicepersonalization/services/maintenance/OnDevicePersonalizationMaintenanceJob.java
+++ b/src/com/android/ondevicepersonalization/services/maintenance/OnDevicePersonalizationMaintenanceJob.java
@@ -16,8 +16,6 @@
package com.android.ondevicepersonalization.services.maintenance;
-import static android.content.pm.PackageManager.GET_META_DATA;
-
import static com.android.adservices.shared.proto.JobPolicy.BatteryType.BATTERY_TYPE_REQUIRE_NOT_LOW;
import static com.android.adservices.shared.spe.JobServiceConstants.JOB_ENABLED_STATUS_DISABLED_FOR_KILL_SWITCH_ON;
import static com.android.adservices.shared.spe.JobServiceConstants.JOB_ENABLED_STATUS_ENABLED;
@@ -25,8 +23,6 @@
import android.content.ComponentName;
import android.content.Context;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
import com.android.adservices.shared.proto.JobPolicy;
import com.android.adservices.shared.spe.framework.ExecutionResult;
@@ -39,9 +35,9 @@
import com.android.ondevicepersonalization.internal.util.LoggerFactory;
import com.android.ondevicepersonalization.services.FlagsFactory;
import com.android.ondevicepersonalization.services.OnDevicePersonalizationExecutors;
+import com.android.ondevicepersonalization.services.data.errors.AggregatedErrorCodesLogger;
import com.android.ondevicepersonalization.services.data.events.EventsDao;
import com.android.ondevicepersonalization.services.data.vendor.OnDevicePersonalizationVendorDataDao;
-import com.android.ondevicepersonalization.services.enrollment.PartnerEnrollmentChecker;
import com.android.ondevicepersonalization.services.manifest.AppManifestConfigHelper;
import com.android.ondevicepersonalization.services.sharedlibrary.spe.OdpJobScheduler;
import com.android.ondevicepersonalization.services.sharedlibrary.spe.OdpJobServiceFactory;
@@ -49,7 +45,7 @@
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
-import java.util.ArrayList;
+import java.util.List;
/** The background job to handle the OnDevicePersonalization maintenance. */
public final class OnDevicePersonalizationMaintenanceJob implements JobWorker {
@@ -138,29 +134,11 @@
@VisibleForTesting
void cleanupVendorData(Context context) throws Exception {
- ArrayList<ComponentName> services = new ArrayList<>();
-
- for (PackageInfo packageInfo :
- context.getPackageManager()
- .getInstalledPackages(PackageManager.PackageInfoFlags.of(GET_META_DATA))) {
- String packageName = packageInfo.packageName;
-
- if (AppManifestConfigHelper.manifestContainsOdpSettings(context, packageName)) {
- if (!PartnerEnrollmentChecker.isIsolatedServiceEnrolled(packageName)) {
- sLogger.d(TAG + ": service %s has ODP manifest, but not enrolled", packageName);
- continue;
- }
-
- sLogger.d(TAG + ": service %s has ODP manifest and is enrolled", packageName);
-
- String serviceClass =
- AppManifestConfigHelper.getServiceNameFromOdpSettings(context, packageName);
- ComponentName service = ComponentName.createRelative(packageName, serviceClass);
- services.add(service);
- }
- }
+ List<ComponentName> services =
+ AppManifestConfigHelper.getOdpServices(context, /* enrolledOnly= */ true);
OnDevicePersonalizationVendorDataDao.deleteVendorTables(context, services);
deleteEventsAndQueries(context);
+ AggregatedErrorCodesLogger.cleanupErrorData(context);
}
}
diff --git a/src/com/android/ondevicepersonalization/services/maintenance/OnDevicePersonalizationMaintenanceJobService.java b/src/com/android/ondevicepersonalization/services/maintenance/OnDevicePersonalizationMaintenanceJobService.java
index 01da274..fa32e29 100644
--- a/src/com/android/ondevicepersonalization/services/maintenance/OnDevicePersonalizationMaintenanceJobService.java
+++ b/src/com/android/ondevicepersonalization/services/maintenance/OnDevicePersonalizationMaintenanceJobService.java
@@ -17,7 +17,6 @@
package com.android.ondevicepersonalization.services.maintenance;
import static android.app.job.JobScheduler.RESULT_SUCCESS;
-import static android.content.pm.PackageManager.GET_META_DATA;
import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__SKIP_FOR_KILL_SWITCH_ON;
import static com.android.adservices.shared.spe.JobServiceConstants.SCHEDULING_RESULT_CODE_FAILED;
@@ -31,18 +30,15 @@
import android.app.job.JobService;
import android.content.ComponentName;
import android.content.Context;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
import com.android.adservices.shared.spe.JobServiceConstants.JobSchedulingResultCode;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.odp.module.common.PackageUtils;
import com.android.ondevicepersonalization.internal.util.LoggerFactory;
import com.android.ondevicepersonalization.services.FlagsFactory;
import com.android.ondevicepersonalization.services.OnDevicePersonalizationExecutors;
+import com.android.ondevicepersonalization.services.data.errors.AggregatedErrorCodesLogger;
import com.android.ondevicepersonalization.services.data.events.EventsDao;
import com.android.ondevicepersonalization.services.data.vendor.OnDevicePersonalizationVendorDataDao;
-import com.android.ondevicepersonalization.services.enrollment.PartnerEnrollmentChecker;
import com.android.ondevicepersonalization.services.manifest.AppManifestConfigHelper;
import com.android.ondevicepersonalization.services.statsd.joblogging.OdpJobServiceLogger;
@@ -50,7 +46,7 @@
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
-import java.util.ArrayList;
+import java.util.List;
/** JobService to handle the OnDevicePersonalization maintenance */
public class OnDevicePersonalizationMaintenanceJobService extends JobService {
@@ -109,29 +105,12 @@
@VisibleForTesting
static void cleanupVendorData(Context context) throws Exception {
- ArrayList<ComponentName> services = new ArrayList<>();
-
- for (PackageInfo packageInfo : context.getPackageManager().getInstalledPackages(
- PackageManager.PackageInfoFlags.of(GET_META_DATA))) {
- String packageName = packageInfo.packageName;
- if (AppManifestConfigHelper.manifestContainsOdpSettings(
- context, packageName)) {
- if (!PartnerEnrollmentChecker.isIsolatedServiceEnrolled(packageName)) {
- sLogger.d(TAG + ": service %s has ODP manifest, but not enrolled",
- packageName);
- continue;
- }
- sLogger.d(TAG + ": service %s has ODP manifest and is enrolled", packageName);
- String certDigest = PackageUtils.getCertDigest(context, packageName);
- String serviceClass = AppManifestConfigHelper.getServiceNameFromOdpSettings(
- context, packageName);
- ComponentName service = ComponentName.createRelative(packageName, serviceClass);
- services.add(service);
- }
- }
+ List<ComponentName> services =
+ AppManifestConfigHelper.getOdpServices(context, /* enrolledOnly= */ true);
OnDevicePersonalizationVendorDataDao.deleteVendorTables(context, services);
deleteEventsAndQueries(context);
+ AggregatedErrorCodesLogger.cleanupErrorData(context);
}
@Override
@@ -162,15 +141,12 @@
mFuture =
Futures.submit(
- new Runnable() {
- @Override
- public void run() {
- sLogger.d(TAG + ": Running maintenance job");
- try {
- cleanupVendorData(context);
- } catch (Exception e) {
- sLogger.e(TAG + ": Failed to cleanup vendorData", e);
- }
+ () -> {
+ sLogger.d(TAG + ": Running maintenance job");
+ try {
+ cleanupVendorData(context);
+ } catch (Exception e) {
+ sLogger.e(TAG + ": Failed to cleanup vendorData", e);
}
},
OnDevicePersonalizationExecutors.getBackgroundExecutor());
@@ -182,8 +158,7 @@
public void onSuccess(Void result) {
sLogger.d(TAG + ": Maintenance job completed.");
boolean wantsReschedule = false;
- OdpJobServiceLogger.getInstance(
- OnDevicePersonalizationMaintenanceJobService.this)
+ OdpJobServiceLogger.getInstance(context)
.recordJobFinished(
MAINTENANCE_TASK_JOB_ID,
/* isSuccessful= */ true,
@@ -197,8 +172,7 @@
public void onFailure(Throwable t) {
sLogger.e(TAG + ": Failed to handle JobService: " + params.getJobId(), t);
boolean wantsReschedule = false;
- OdpJobServiceLogger.getInstance(
- OnDevicePersonalizationMaintenanceJobService.this)
+ OdpJobServiceLogger.getInstance(context)
.recordJobFinished(
MAINTENANCE_TASK_JOB_ID,
/* isSuccessful= */ false,
diff --git a/src/com/android/ondevicepersonalization/services/manifest/AppManifestConfig.java b/src/com/android/ondevicepersonalization/services/manifest/AppManifestConfig.java
index 037e9a4..c5ac5bc 100644
--- a/src/com/android/ondevicepersonalization/services/manifest/AppManifestConfig.java
+++ b/src/com/android/ondevicepersonalization/services/manifest/AppManifestConfig.java
@@ -24,7 +24,7 @@
private final String mServiceName;
private final String mFcRemoteServerUrl;
- public AppManifestConfig(String downloadUrl, String serviceName, String fcRemoteServerUrl) {
+ AppManifestConfig(String downloadUrl, String serviceName, String fcRemoteServerUrl) {
mDownloadUrl = downloadUrl;
mServiceName = serviceName;
mFcRemoteServerUrl = fcRemoteServerUrl;
diff --git a/src/com/android/ondevicepersonalization/services/manifest/AppManifestConfigHelper.java b/src/com/android/ondevicepersonalization/services/manifest/AppManifestConfigHelper.java
index b2447fe..066a871 100644
--- a/src/com/android/ondevicepersonalization/services/manifest/AppManifestConfigHelper.java
+++ b/src/com/android/ondevicepersonalization/services/manifest/AppManifestConfigHelper.java
@@ -16,17 +16,28 @@
package com.android.ondevicepersonalization.services.manifest;
+import static android.content.pm.PackageManager.GET_META_DATA;
+
+import android.content.ComponentName;
import android.content.Context;
+import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
+import com.android.ondevicepersonalization.internal.util.LoggerFactory;
+import com.android.ondevicepersonalization.services.enrollment.PartnerEnrollmentChecker;
+
+import com.google.common.collect.ImmutableList;
+
/**
* Helper class for parsing and checking app manifest configs
*/
public final class AppManifestConfigHelper {
private static final String ON_DEVICE_PERSONALIZATION_CONFIG_PROPERTY =
"android.ondevicepersonalization.ON_DEVICE_PERSONALIZATION_CONFIG";
+ private static final LoggerFactory.Logger sLogger = LoggerFactory.getLogger();
+ private static final String TAG = AppManifestConfigHelper.class.getSimpleName();
private AppManifestConfigHelper() {
}
@@ -34,12 +45,11 @@
/**
* Determines if the given package's manifest contains ODP settings
*
- * @param context the context of the API call.
+ * @param context the context of the API call.
* @param packageName the packageName of the package whose manifest config will be read
* @return true if the ODP setting exists, false otherwise
*/
- public static Boolean manifestContainsOdpSettings(Context context,
- String packageName) {
+ public static boolean manifestContainsOdpSettings(Context context, String packageName) {
PackageManager pm = context.getPackageManager();
try {
pm.getProperty(ON_DEVICE_PERSONALIZATION_CONFIG_PROPERTY, packageName);
@@ -49,9 +59,14 @@
return true;
}
+ /**
+ * Returns the ODP manifest config for a package.
+ *
+ * <p>Throws a {@link RuntimeException} if the package, its ODP settings are not found or cannot
+ * be parsed.
+ */
/** Returns the ODP manifest config for a package. */
- public static AppManifestConfig getAppManifestConfig(Context context,
- String packageName) {
+ public static AppManifestConfig getAppManifestConfig(Context context, String packageName) {
if (!manifestContainsOdpSettings(context, packageName)) {
// TODO(b/241941021) Determine correct exception to throw
throw new IllegalArgumentException(
@@ -103,4 +118,67 @@
String packageName) {
return getAppManifestConfig(context, packageName).getFcRemoteServerUrl();
}
+
+ /**
+ * Get the list of packages enrolled for ODP, by checking manifest for ODP settings.
+ *
+ * @param context The context of the calling process.
+ * @param enrolledOnly Whether to only include packages that pass the enrollment check.
+ * @return The list of packages that contain ODP manifest settings (and potentially enrolled)
+ */
+ public static ImmutableList<String> getOdpPackages(Context context, boolean enrolledOnly) {
+ ImmutableList.Builder<String> builder = new ImmutableList.Builder<>();
+ for (PackageInfo packageInfo :
+ context.getPackageManager()
+ .getInstalledPackages(PackageManager.PackageInfoFlags.of(GET_META_DATA))) {
+ String packageName = packageInfo.packageName;
+
+ if (manifestContainsOdpSettings(context, packageName)) {
+ if (enrolledOnly
+ && !PartnerEnrollmentChecker.isIsolatedServiceEnrolled(packageName)) {
+ sLogger.d(TAG + ": package %s has ODP manifest, but not enrolled", packageName);
+ continue;
+ }
+
+ String enrolledString = enrolledOnly ? "and is enrolled" : "";
+ sLogger.d(TAG + ": package %s has ODP manifest " + enrolledString, packageName);
+ builder.add(packageName);
+ }
+ }
+ return builder.build();
+ }
+
+ /**
+ * Get the list of services enrolled for ODP.
+ *
+ * @param context The context of the calling process.
+ * @param enrolledOnly Whether to only include services that pass the enrollment check.
+ * @return The list of matching Services with ODP manifest settings (and are potentially
+ * enrolled).
+ */
+ public static ImmutableList<ComponentName> getOdpServices(
+ Context context, boolean enrolledOnly) {
+ ImmutableList.Builder<ComponentName> builder = new ImmutableList.Builder<>();
+ for (PackageInfo packageInfo :
+ context.getPackageManager()
+ .getInstalledPackages(PackageManager.PackageInfoFlags.of(GET_META_DATA))) {
+ String packageName = packageInfo.packageName;
+
+ if (manifestContainsOdpSettings(context, packageName)) {
+ if (enrolledOnly
+ && !PartnerEnrollmentChecker.isIsolatedServiceEnrolled(packageName)) {
+ sLogger.d(TAG + ": service %s has ODP manifest, but not enrolled", packageName);
+ continue;
+ }
+
+ String enrolledString = enrolledOnly ? "and is enrolled" : "";
+ sLogger.d(TAG + ": service %s has ODP manifest " + enrolledString, packageName);
+
+ String serviceClass = getServiceNameFromOdpSettings(context, packageName);
+ ComponentName service = ComponentName.createRelative(packageName, serviceClass);
+ builder.add(service);
+ }
+ }
+ return builder.build();
+ }
}
diff --git a/src/com/android/ondevicepersonalization/services/manifest/AppManifestConfigParser.java b/src/com/android/ondevicepersonalization/services/manifest/AppManifestConfigParser.java
index 05c6be6..21b6ad2 100644
--- a/src/com/android/ondevicepersonalization/services/manifest/AppManifestConfigParser.java
+++ b/src/com/android/ondevicepersonalization/services/manifest/AppManifestConfigParser.java
@@ -26,7 +26,7 @@
import java.io.IOException;
/** Parser and validator for OnDevicePersonalization app manifest configs. */
-public class AppManifestConfigParser {
+class AppManifestConfigParser {
private static final LoggerFactory.Logger sLogger = LoggerFactory.getLogger();
private static final String TAG = "AppManifestConfigParser";
private static final String TAG_ON_DEVICE_PERSONALIZATION_CONFIG = "on-device-personalization";
@@ -45,8 +45,8 @@
*
* @param parser the XmlParser representing the OnDevicePersonalization app manifest config
*/
- public static AppManifestConfig getConfig(XmlPullParser parser) throws IOException,
- XmlPullParserException {
+ static AppManifestConfig getConfig(XmlPullParser parser)
+ throws IOException, XmlPullParserException {
String downloadUrl = null;
String serviceName = null;
String fcServerUrl = null;
diff --git a/src/com/android/ondevicepersonalization/services/process/IsolatedServiceInfo.java b/src/com/android/ondevicepersonalization/services/process/IsolatedServiceInfo.java
index 6cd7000..3a942ca 100644
--- a/src/com/android/ondevicepersonalization/services/process/IsolatedServiceInfo.java
+++ b/src/com/android/ondevicepersonalization/services/process/IsolatedServiceInfo.java
@@ -16,7 +16,6 @@
package com.android.ondevicepersonalization.services.process;
-import static android.adservices.ondevicepersonalization.OnDevicePersonalizationException.ERROR_ISOLATED_SERVICE_FAILED;
import android.adservices.ondevicepersonalization.aidl.IIsolatedService;
import android.annotation.NonNull;
@@ -25,7 +24,6 @@
import com.android.federatedcompute.internal.util.AbstractServiceBinder;
import com.android.ondevicepersonalization.libraries.plugin.PluginController;
-import com.android.ondevicepersonalization.services.OdpServiceException;
import java.util.Objects;
@@ -39,19 +37,26 @@
IsolatedServiceInfo(
long startTimeMillis,
@NonNull ComponentName componentName,
+ @NonNull PluginController pluginController) {
+ this(startTimeMillis, componentName, pluginController, /* isolatedServiceBinder= */ null);
+ }
+
+ IsolatedServiceInfo(
+ long startTimeMillis,
+ @NonNull ComponentName componentName,
+ @NonNull AbstractServiceBinder<IIsolatedService> isolatedServiceBinder) {
+ this(startTimeMillis, componentName, /* pluginController= */ null, isolatedServiceBinder);
+ }
+
+ private IsolatedServiceInfo(
+ long startTimeMillis,
+ @NonNull ComponentName componentName,
@Nullable PluginController pluginController,
- @Nullable AbstractServiceBinder<IIsolatedService> isolatedServiceBinder)
- throws OdpServiceException {
+ @Nullable AbstractServiceBinder<IIsolatedService> isolatedServiceBinder) {
mStartTimeMillis = startTimeMillis;
mComponentName = Objects.requireNonNull(componentName);
mPluginController = pluginController;
mIsolatedServiceBinder = isolatedServiceBinder;
-
- // TO-DO (323882182): Granular isolated servce failures.
- if ((mPluginController != null && mIsolatedServiceBinder != null)
- || (mPluginController == null && mIsolatedServiceBinder == null)) {
- throw new OdpServiceException(ERROR_ISOLATED_SERVICE_FAILED);
- }
}
PluginController getPluginController() {
diff --git a/src/com/android/ondevicepersonalization/services/process/OnDevicePersonalizationPlugin.java b/src/com/android/ondevicepersonalization/services/process/OnDevicePersonalizationPlugin.java
index 1c260b5..edddf1a 100644
--- a/src/com/android/ondevicepersonalization/services/process/OnDevicePersonalizationPlugin.java
+++ b/src/com/android/ondevicepersonalization/services/process/OnDevicePersonalizationPlugin.java
@@ -96,8 +96,10 @@
}
}
@Override public void onError(
- int errorCode, int isolatedServiceErrorCode) {
+ int errorCode, int isolatedServiceErrorCode,
+ byte[] serializedExceptionInfo) {
try {
+ // TODO(b/326455045): Support detailed error return in plugin.
mPluginCallback.onFailure(FailureType.ERROR_EXECUTING_PLUGIN);
} catch (RemoteException e) {
sLogger.e(TAG + ": Callback error.", e);
diff --git a/src/com/android/ondevicepersonalization/services/process/PluginProcessRunner.java b/src/com/android/ondevicepersonalization/services/process/PluginProcessRunner.java
index 98981e0..ab5c8c6 100644
--- a/src/com/android/ondevicepersonalization/services/process/PluginProcessRunner.java
+++ b/src/com/android/ondevicepersonalization/services/process/PluginProcessRunner.java
@@ -23,7 +23,6 @@
import android.content.Context;
import android.os.Bundle;
-
import androidx.concurrent.futures.CallbackToFutureAdapter;
import com.android.odp.module.common.Clock;
@@ -153,33 +152,36 @@
@NonNull ComponentName componentName,
@NonNull PluginController pluginController) {
return CallbackToFutureAdapter.getFuture(
- completer -> {
- try {
- sLogger.d(TAG + ": loadPlugin");
- pluginController.load(new PluginCallback() {
- @Override public void onSuccess(Bundle bundle) {
- try {
- completer.set(
- new IsolatedServiceInfo(
- startTimeMillis, componentName,
- pluginController, /* isolatedServiceBinder= */ null));
- } catch (OdpServiceException e) {
- completer.setException(e);
- }
+ completer -> {
+ try {
+ sLogger.d(TAG + ": loadPlugin");
+ pluginController.load(
+ new PluginCallback() {
+ @Override
+ public void onSuccess(Bundle bundle) {
- }
- @Override public void onFailure(FailureType failure) {
- completer.setException(new OdpServiceException(
- Constants.STATUS_INTERNAL_ERROR,
- String.format("loadPlugin failed. %s", failure.toString())));
- }
- });
- } catch (Exception e) {
- completer.setException(e);
- }
- return "loadPlugin";
- }
- );
+ completer.set(
+ new IsolatedServiceInfo(
+ startTimeMillis,
+ componentName,
+ pluginController));
+ }
+
+ @Override
+ public void onFailure(FailureType failure) {
+ completer.setException(
+ new OdpServiceException(
+ Constants.STATUS_INTERNAL_ERROR,
+ String.format(
+ "loadPlugin failed. %s",
+ failure.toString())));
+ }
+ });
+ } catch (Exception e) {
+ completer.setException(e);
+ }
+ return "loadPlugin";
+ });
}
@NonNull static ListenableFuture<Bundle> executePlugin(
diff --git a/src/com/android/ondevicepersonalization/services/process/SharedIsolatedProcessRunner.java b/src/com/android/ondevicepersonalization/services/process/SharedIsolatedProcessRunner.java
index c2ef5ac..81fe61e 100644
--- a/src/com/android/ondevicepersonalization/services/process/SharedIsolatedProcessRunner.java
+++ b/src/com/android/ondevicepersonalization/services/process/SharedIsolatedProcessRunner.java
@@ -28,6 +28,7 @@
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
+import android.os.Binder;
import android.os.Bundle;
import androidx.concurrent.futures.CallbackToFutureAdapter;
@@ -37,19 +38,23 @@
import com.android.odp.module.common.Clock;
import com.android.odp.module.common.MonotonicClock;
import com.android.odp.module.common.PackageUtils;
+import com.android.ondevicepersonalization.internal.util.ExceptionInfo;
import com.android.ondevicepersonalization.internal.util.LoggerFactory;
-import com.android.ondevicepersonalization.services.FlagsFactory;
import com.android.ondevicepersonalization.services.OdpServiceException;
import com.android.ondevicepersonalization.services.OnDevicePersonalizationApplication;
import com.android.ondevicepersonalization.services.OnDevicePersonalizationExecutors;
+import com.android.ondevicepersonalization.services.StableFlags;
+import com.android.ondevicepersonalization.services.data.errors.AggregatedErrorCodesLogger;
import com.android.ondevicepersonalization.services.util.AllowListUtils;
+import com.google.common.annotations.VisibleForTesting;
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 java.util.Objects;
+import java.util.concurrent.TimeoutException;
/** Utilities for running remote isolated services in a shared isolated process (SIP). Note that
* this runner is only selected when the shared_isolated_process_feature_enabled flag is enabled.
@@ -69,6 +74,7 @@
private final Context mApplicationContext;
private final Injector mInjector;
+ @VisibleForTesting
static class Injector {
Clock getClock() {
return MonotonicClock.getInstance();
@@ -78,9 +84,9 @@
return OnDevicePersonalizationExecutors.getBackgroundExecutor();
}
}
- SharedIsolatedProcessRunner(
- @NonNull Context applicationContext,
- @NonNull Injector injector) {
+
+ @VisibleForTesting
+ SharedIsolatedProcessRunner(@NonNull Context applicationContext, @NonNull Injector injector) {
mApplicationContext = Objects.requireNonNull(applicationContext);
mInjector = Objects.requireNonNull(injector);
}
@@ -110,20 +116,34 @@
return FluentFuture.from(isolatedServiceFuture)
.transformAsync(
(isolatedService) -> {
- try {
- return Futures.immediateFuture(new IsolatedServiceInfo(
- mInjector.getClock().elapsedRealtime(), componentName,
- /* pluginController= */ null, isolatedService));
- } catch (Exception e) {
- return Futures.immediateFailedFuture(e);
- }
- }, mInjector.getExecutor())
+ return Futures.immediateFuture(
+ new IsolatedServiceInfo(
+ mInjector.getClock().elapsedRealtime(),
+ componentName,
+ isolatedService));
+ },
+ mInjector.getExecutor())
.catchingAsync(
Exception.class,
- Futures::immediateFailedFuture,
+ e -> {
+ sLogger.d(
+ TAG
+ + ": loading of isolated service failed for "
+ + componentName,
+ e);
+ // Return OdpServiceException if the exception thrown was not
+ // already an OdpServiceException.
+ if (e instanceof OdpServiceException) {
+ return Futures.immediateFailedFuture(e);
+ }
+ return Futures.immediateFailedFuture(
+ new OdpServiceException(
+ Constants.STATUS_ISOLATED_SERVICE_LOADING_FAILED, e));
+ },
mInjector.getExecutor());
} catch (Exception e) {
- return Futures.immediateFailedFuture(e);
+ return Futures.immediateFailedFuture(
+ new OdpServiceException(Constants.STATUS_ISOLATED_SERVICE_LOADING_FAILED, e));
}
}
@@ -133,36 +153,76 @@
public ListenableFuture<Bundle> runIsolatedService(
@NonNull IsolatedServiceInfo isolatedProcessInfo, int operationCode,
@NonNull Bundle serviceParams) {
- return CallbackToFutureAdapter.getFuture(
- completer -> {
- isolatedProcessInfo.getIsolatedServiceBinder()
- .getService(Runnable::run)
- .onRequest(
- operationCode, serviceParams,
+ IIsolatedService service;
+ try {
+ service = isolatedProcessInfo.getIsolatedServiceBinder().getService(Runnable::run);
+ } catch (Exception e) {
+ // Failure in loading/connecting to the IsolatedService vs actual issue
+ // in running the IsolatedService code via the onRequest call below.
+ sLogger.d(TAG + ": unable to get the IsolatedService binder.", e);
+ return Futures.immediateFailedFuture(
+ new OdpServiceException(Constants.STATUS_ISOLATED_SERVICE_LOADING_FAILED));
+ }
+
+ ListenableFuture<Bundle> callbackFuture =
+ CallbackToFutureAdapter.getFuture(
+ completer -> {
+ service.onRequest(
+ operationCode,
+ serviceParams,
new IIsolatedServiceCallback.Stub() {
- @Override public void onSuccess(Bundle result) {
+ @Override
+ public void onSuccess(Bundle result) {
completer.set(result);
}
- // TO-DO (323882182): Granular isolated servce failures.
- @Override public void onError(
- int errorCode, int isolatedServiceErrorCode) {
- if (isolatedServiceErrorCode > 0
- && isolatedServiceErrorCode < 128) {
- completer.setException(
- new OdpServiceException(
- Constants.STATUS_SERVICE_FAILED,
- new IsolatedServiceException(
- isolatedServiceErrorCode)));
- } else {
- completer.setException(
- new OdpServiceException(
- Constants.STATUS_SERVICE_FAILED));
+ @Override
+ public void onError(
+ int errorCode,
+ int isolatedServiceErrorCode,
+ byte[] serializedExceptionInfo) {
+ Exception cause =
+ ExceptionInfo.fromByteArray(
+ serializedExceptionInfo);
+ if (isolatedServiceErrorCode > 0) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ ListenableFuture<?> unused =
+ AggregatedErrorCodesLogger
+ .logIsolatedServiceErrorCode(
+ isolatedServiceErrorCode,
+ isolatedProcessInfo
+ .getComponentName(),
+ mApplicationContext);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ cause =
+ new IsolatedServiceException(
+ isolatedServiceErrorCode, cause);
}
+ completer.setException(
+ new OdpServiceException(
+ Constants.STATUS_SERVICE_FAILED,
+ cause));
}
});
- return null;
- });
+ // used for debugging purpose only.
+ return "IsolatedService.onRequest";
+ });
+ return FluentFuture.from(callbackFuture)
+ .catchingAsync(
+ Throwable.class, // Catch FutureGarbageCollectedException
+ e -> {
+ return (e instanceof IsolatedServiceException
+ || e instanceof OdpServiceException)
+ ? Futures.immediateFailedFuture(e)
+ : Futures.immediateFailedFuture(
+ new TimeoutException(
+ "Callback to future adapter was garbage"
+ + " collected."));
+ },
+ mInjector.getExecutor());
}
/** Unbinds from the remote isolated service. */
@@ -194,9 +254,10 @@
instanceName, bindFlag, IIsolatedService.Stub::asInterface);
}
+ @VisibleForTesting
String getSipInstanceName(String packageName) {
String partnerAppsList =
- (String) FlagsFactory.getFlags().getStableFlag(KEY_TRUSTED_PARTNER_APPS_LIST);
+ (String) StableFlags.get(KEY_TRUSTED_PARTNER_APPS_LIST);
String packageCertificate = null;
try {
packageCertificate = PackageUtils.getCertDigest(mApplicationContext, packageName);
@@ -206,12 +267,11 @@
boolean isPartnerApp = AllowListUtils.isAllowListed(
packageName, packageCertificate, partnerAppsList);
String sipInstanceName = isPartnerApp ? TRUSTED_PARTNER_APPS_SIP : UNKNOWN_APPS_SIP;
- return (boolean) FlagsFactory.getFlags()
- .getStableFlag(KEY_IS_ART_IMAGE_LOADING_OPTIMIZATION_ENABLED)
+ return (boolean) StableFlags.get(KEY_IS_ART_IMAGE_LOADING_OPTIMIZATION_ENABLED)
? sipInstanceName + "_disable_art_image_" : sipInstanceName;
}
- boolean isSharedIsolatedProcessRequested(ComponentName service) throws Exception {
+ private boolean isSharedIsolatedProcessRequested(ComponentName service) throws Exception {
if (!SdkLevel.isAtLeastU()) {
return false;
}
@@ -219,8 +279,13 @@
PackageManager pm = mApplicationContext.getPackageManager();
ServiceInfo si = pm.getServiceInfo(service, PackageManager.GET_META_DATA);
+ sLogger.d(TAG + "Package manager = " + pm);
if ((si.flags & si.FLAG_ISOLATED_PROCESS) == 0) {
- throw new IllegalArgumentException("ODP client services should run in isolated processes.");
+ sLogger.e(
+ TAG, "ODP client service not configured to run in isolated process " + service);
+ throw new OdpServiceException(
+ Constants.STATUS_MANIFEST_PARSING_FAILED,
+ "ODP client services should run in isolated processes.");
}
return (si.flags & si.FLAG_ALLOW_SHARED_ISOLATED_PROCESS) != 0;
diff --git a/src/com/android/ondevicepersonalization/services/request/AppRequestFlow.java b/src/com/android/ondevicepersonalization/services/request/AppRequestFlow.java
index ec10049..64d1b00 100644
--- a/src/com/android/ondevicepersonalization/services/request/AppRequestFlow.java
+++ b/src/com/android/ondevicepersonalization/services/request/AppRequestFlow.java
@@ -18,9 +18,12 @@
import android.adservices.ondevicepersonalization.CalleeMetadata;
import android.adservices.ondevicepersonalization.Constants;
+import android.adservices.ondevicepersonalization.ExecuteInIsolatedServiceRequest;
import android.adservices.ondevicepersonalization.ExecuteInputParcel;
+import android.adservices.ondevicepersonalization.ExecuteOptionsParcel;
import android.adservices.ondevicepersonalization.ExecuteOutputParcel;
import android.adservices.ondevicepersonalization.RenderingConfig;
+import android.adservices.ondevicepersonalization.UserData;
import android.adservices.ondevicepersonalization.aidl.IExecuteCallback;
import android.adservices.ondevicepersonalization.aidl.IIsolatedModelService;
import android.annotation.NonNull;
@@ -55,6 +58,7 @@
import com.android.ondevicepersonalization.services.util.CryptUtils;
import com.android.ondevicepersonalization.services.util.DebugUtils;
import com.android.ondevicepersonalization.services.util.LogUtils;
+import com.android.ondevicepersonalization.services.util.NoiseUtil;
import com.android.ondevicepersonalization.services.util.StatsUtils;
import com.google.common.util.concurrent.FluentFuture;
@@ -66,7 +70,9 @@
import java.util.Objects;
import java.util.Set;
+import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
@@ -80,14 +86,14 @@
private final String mCallingPackageName;
@NonNull
private final ComponentName mService;
- @NonNull
- private final Bundle mWrappedParams;
+ @NonNull private final Bundle mWrappedParams;
@NonNull
private final IExecuteCallback mCallback;
@NonNull
private final Context mContext;
private final long mStartTimeMillis;
private final long mServiceEntryTimeMillis;
+ private final ExecuteOptionsParcel mOptions;
@NonNull
private AtomicReference<IsolatedModelServiceProvider> mModelServiceProvider =
@@ -97,7 +103,7 @@
private byte[] mSerializedAppParams;
@VisibleForTesting
- static class Injector {
+ public static class Injector {
ListeningExecutorService getExecutor() {
return OnDevicePersonalizationExecutors.getBackgroundExecutor();
}
@@ -106,7 +112,7 @@
return MonotonicClock.getInstance();
}
- Flags getFlags() {
+ public Flags getFlags() {
return FlagsFactory.getFlags();
}
@@ -114,12 +120,17 @@
return OnDevicePersonalizationExecutors.getScheduledExecutor();
}
- boolean shouldValidateExecuteOutput() {
+ /** Returns whether should validate rendering configuration keys. */
+ public boolean shouldValidateExecuteOutput() {
return DeviceConfig.getBoolean(
/* namespace= */ "on_device_personalization",
/* name= */ "debug.validate_rendering_config_keys",
/* defaultValue= */ true);
}
+
+ public NoiseUtil getNoiseUtil() {
+ return new NoiseUtil();
+ }
}
@NonNull
@@ -132,13 +143,22 @@
@NonNull IExecuteCallback callback,
@NonNull Context context,
long startTimeMillis,
- long serviceEntryTimeMillis) {
- this(callingPackageName, service, wrappedParams,
- callback, context, startTimeMillis, serviceEntryTimeMillis, new Injector());
+ long serviceEntryTimeMillis,
+ ExecuteOptionsParcel options) {
+ this(
+ callingPackageName,
+ service,
+ wrappedParams,
+ callback,
+ context,
+ startTimeMillis,
+ serviceEntryTimeMillis,
+ options,
+ new Injector());
}
@VisibleForTesting
- AppRequestFlow(
+ public AppRequestFlow(
@NonNull String callingPackageName,
@NonNull ComponentName service,
@NonNull Bundle wrappedParams,
@@ -146,6 +166,7 @@
@NonNull Context context,
long startTimeMillis,
long serviceEntryTimeMillis,
+ ExecuteOptionsParcel options,
@NonNull Injector injector) {
sLogger.d(TAG + ": AppRequestFlow created.");
mCallingPackageName = Objects.requireNonNull(callingPackageName);
@@ -155,6 +176,7 @@
mContext = Objects.requireNonNull(context);
mStartTimeMillis = startTimeMillis;
mServiceEntryTimeMillis = serviceEntryTimeMillis;
+ mOptions = options;
mInjector = Objects.requireNonNull(injector);
}
@@ -163,13 +185,15 @@
mStartServiceTimeMillis.set(mInjector.getClock().elapsedRealtime());
try {
- ByteArrayParceledSlice paramsBuffer = Objects.requireNonNull(
- mWrappedParams.getParcelable(
- Constants.EXTRA_APP_PARAMS_SERIALIZED, ByteArrayParceledSlice.class));
+ ByteArrayParceledSlice paramsBuffer =
+ Objects.requireNonNull(
+ mWrappedParams.getParcelable(
+ Constants.EXTRA_APP_PARAMS_SERIALIZED,
+ ByteArrayParceledSlice.class));
mSerializedAppParams = Objects.requireNonNull(paramsBuffer.getByteArray());
} catch (Exception e) {
sLogger.d(TAG + ": Failed to extract app params.", e);
- sendErrorResult(Constants.STATUS_INTERNAL_ERROR, e);
+ sendErrorResult(Constants.STATUS_INTERNAL_ERROR, 0, e);
return false;
}
@@ -180,13 +204,21 @@
mContext, mService.getPackageName()));
} catch (Exception e) {
sLogger.d(TAG + ": Failed to read manifest.", e);
- sendErrorResult(Constants.STATUS_NAME_NOT_FOUND, e);
+ sendErrorResult(
+ Constants.STATUS_MANIFEST_PARSING_FAILED, /* isolatedServiceErrorCode= */ 0, e);
return false;
}
if (!mService.getClassName().equals(config.getServiceName())) {
sLogger.d(TAG + ": service class not found");
- sendErrorResult(Constants.STATUS_CLASS_NOT_FOUND, 0);
+ sendErrorResult(
+ Constants.STATUS_MANIFEST_MISCONFIGURED,
+ /* isolatedServiceErrorCode= */ 0,
+ new ClassNotFoundException(
+ "Expected: "
+ + mService.getClassName()
+ + " Found: "
+ + config.getServiceName()));
return false;
}
@@ -200,6 +232,8 @@
@Override
public Bundle getServiceParams() {
+ sLogger.d(TAG + ": getting service params.");
+
DataAccessPermission localDataPermission = DataAccessPermission.READ_WRITE;
if (!UserPrivacyStatus.getInstance().isMeasurementEnabled()) {
localDataPermission = DataAccessPermission.READ_ONLY;
@@ -221,9 +255,11 @@
serviceParams.putBinder(
Constants.EXTRA_FEDERATED_COMPUTE_SERVICE_BINDER,
new FederatedComputeServiceImpl(mService, mContext));
- serviceParams.putParcelable(
- Constants.EXTRA_USER_DATA,
- new UserDataAccessor().getUserData());
+ UserData userData =
+ isPlatformDataProvided()
+ ? new UserDataAccessor().getUserDataWithAppInstall()
+ : new UserDataAccessor().getUserData();
+ serviceParams.putParcelable(Constants.EXTRA_USER_DATA, userData);
mModelServiceProvider.set(new IsolatedModelServiceProvider());
IIsolatedModelService modelService = mModelServiceProvider.get().getModelService(mContext);
serviceParams.putBinder(Constants.EXTRA_MODEL_SERVICE_BINDER, modelService.asBinder());
@@ -233,12 +269,14 @@
@Override
public void uploadServiceFlowMetrics(ListenableFuture<Bundle> runServiceFuture) {
+ sLogger.d(TAG + ": uploading service flow metrics.");
var unused =
FluentFuture.from(runServiceFuture)
.transform(
val -> {
StatsUtils.writeServiceRequestMetrics(
Constants.API_NAME_SERVICE_ON_EXECUTE,
+ mService.getPackageName(),
val,
mInjector.getClock(),
Constants.STATUS_SUCCESS,
@@ -251,6 +289,8 @@
e -> {
StatsUtils.writeServiceRequestMetrics(
Constants.API_NAME_SERVICE_ON_EXECUTE,
+ mService.getPackageName(),
+
/* result= */ null,
mInjector.getClock(),
Constants.STATUS_INTERNAL_ERROR,
@@ -303,12 +343,19 @@
sLogger.w(TAG + ": Request failed.", t);
if (t instanceof OdpServiceException) {
OdpServiceException e = (OdpServiceException) t;
+
sendErrorResult(
e.getErrorCode(),
DebugUtils.getIsolatedServiceExceptionCode(
- mContext, mService, e));
+ mContext, mService, e),
+ t);
} else {
- sendErrorResult(Constants.STATUS_INTERNAL_ERROR, t);
+ int errorCode =
+ t instanceof TimeoutException
+ ? Constants.STATUS_ISOLATED_SERVICE_TIMEOUT
+ : Constants.STATUS_INTERNAL_ERROR;
+ sLogger.w(TAG + ": Failing with error code: " + errorCode);
+ sendErrorResult(errorCode, /* isolatedServiceErrorCode= */ 0, t);
}
}
},
@@ -323,8 +370,11 @@
private ListenableFuture<ExecuteOutputParcel> validateExecuteOutput(
ExecuteOutputParcel result) {
sLogger.d(TAG + ": validateExecuteOutput() started.");
- if (mInjector.shouldValidateExecuteOutput()) {
- try {
+ if (!mInjector.shouldValidateExecuteOutput()) {
+ sLogger.d(TAG + ": validateExecuteOutput() skipped.");
+ return Futures.immediateFuture(result);
+ }
+ try {
OnDevicePersonalizationVendorDataDao vendorDataDao =
OnDevicePersonalizationVendorDataDao.getInstance(mContext,
mService,
@@ -332,14 +382,15 @@
if (result.getRenderingConfig() != null) {
Set<String> keyset = vendorDataDao.readAllVendorDataKeys();
if (!keyset.containsAll(result.getRenderingConfig().getKeys())) {
- return Futures.immediateFailedFuture(
- new OdpServiceException(Constants.STATUS_SERVICE_FAILED));
+ return Futures.immediateFailedFuture(
+ new OdpServiceException(Constants.STATUS_SERVICE_FAILED));
}
}
} catch (Exception e) {
- return Futures.immediateFailedFuture(e);
+ return Futures.immediateFailedFuture(
+ new OdpServiceException(Constants.STATUS_SERVICE_FAILED));
}
- }
+ sLogger.d(TAG + ": validateExecuteOutput() succeeded.");
return Futures.immediateFuture(result);
}
@@ -384,15 +435,34 @@
}
Bundle bundle = new Bundle();
bundle.putString(Constants.EXTRA_SURFACE_PACKAGE_TOKEN_STRING, token);
- if (isOutputDataAllowed()) {
- bundle.putByteArray(Constants.EXTRA_OUTPUT_DATA, result.getOutputData());
- }
+ // bundle.getInt(key) returns 0 if the key is not found. It can be confused with the
+ // real best value 0, so set it to -1 explicitly to indicate this field is unset.
+ bundle.putInt(
+ Constants.EXTRA_OUTPUT_BEST_VALUE, processBestValue(result.getBestValue()));
+
return Futures.immediateFuture(bundle);
} catch (Exception e) {
return Futures.immediateFailedFuture(e);
}
}
+ private int processBestValue(int actualResult) {
+ int bestValue = -1;
+ if (!isOutputDataAllowed()
+ || mOptions.getOutputType()
+ != ExecuteInIsolatedServiceRequest.OutputSpec.OUTPUT_TYPE_BEST_VALUE) {
+ return bestValue;
+ }
+ // Don't apply noise if partner only uses their own data.
+ if (!isPlatformDataProvided()) {
+ return actualResult;
+ }
+ return mInjector
+ .getNoiseUtil()
+ .applyNoiseToBestValue(
+ actualResult, mOptions.getMaxIntValue(), ThreadLocalRandom.current());
+ }
+
private boolean isOutputDataAllowed() {
try {
return AllowListUtils.isPairAllowListed(
@@ -407,8 +477,19 @@
}
}
+ private boolean isPlatformDataProvided() {
+ try {
+ return AllowListUtils.isAllowListed(
+ mService.getPackageName(),
+ PackageUtils.getCertDigest(mContext, mService.getPackageName()),
+ mInjector.getFlags().getDefaultPlatformDataForExecuteAllowlist());
+ } catch (Exception e) {
+ sLogger.d(TAG + ": allow list error", e);
+ return false;
+ }
+ }
+
private void sendSuccessResult(Bundle result) {
- int responseCode = Constants.STATUS_SUCCESS;
try {
mCallback.onSuccess(
result,
@@ -417,31 +498,16 @@
.setCallbackInvokeTimeMillis(
SystemClock.elapsedRealtime()).build());
} catch (RemoteException e) {
- responseCode = Constants.STATUS_INTERNAL_ERROR;
sLogger.w(TAG + ": Callback error", e);
}
}
- private void sendErrorResult(int errorCode, int isolatedServiceErrorCode) {
+ private void sendErrorResult(int errorCode, int isolatedServiceErrorCode, Throwable t) {
try {
mCallback.onError(
errorCode,
isolatedServiceErrorCode,
- null,
- new CalleeMetadata.Builder()
- .setServiceEntryTimeMillis(mServiceEntryTimeMillis)
- .setCallbackInvokeTimeMillis(SystemClock.elapsedRealtime()).build());
- } catch (RemoteException e) {
- sLogger.w(TAG + ": Callback error", e);
- }
- }
-
- private void sendErrorResult(int errorCode, Throwable t) {
- try {
- mCallback.onError(
- errorCode,
- 0,
- DebugUtils.getErrorMessage(mContext, t),
+ DebugUtils.serializeExceptionInfo(mService, t),
new CalleeMetadata.Builder()
.setServiceEntryTimeMillis(mServiceEntryTimeMillis)
.setCallbackInvokeTimeMillis(SystemClock.elapsedRealtime()).build());
diff --git a/src/com/android/ondevicepersonalization/services/request/RenderFlow.java b/src/com/android/ondevicepersonalization/services/request/RenderFlow.java
index 3cd6675..bd2dd9a 100644
--- a/src/com/android/ondevicepersonalization/services/request/RenderFlow.java
+++ b/src/com/android/ondevicepersonalization/services/request/RenderFlow.java
@@ -162,7 +162,7 @@
try {
if (!UserPrivacyStatus.getInstance().isProtectedAudienceEnabled()) {
sLogger.d(TAG + ": User control is not given for targeting.");
- sendErrorResult(Constants.STATUS_PERSONALIZATION_DISABLED, 0);
+ sendErrorResult(Constants.STATUS_PERSONALIZATION_DISABLED, 0, null);
return false;
}
@@ -175,7 +175,7 @@
mContext, servicePackageName));
mService = ComponentName.createRelative(servicePackageName, serviceClassName);
} catch (Exception e) {
- sendErrorResult(Constants.STATUS_INTERNAL_ERROR, 0);
+ sendErrorResult(Constants.STATUS_INTERNAL_ERROR, 0, e);
return false;
}
return true;
@@ -209,28 +209,33 @@
@Override
public void uploadServiceFlowMetrics(ListenableFuture<Bundle> runServiceFuture) {
- var unused = FluentFuture.from(runServiceFuture)
- .transform(
- val -> {
- StatsUtils.writeServiceRequestMetrics(
- Constants.API_NAME_SERVICE_ON_RENDER,
- val, mInjector.getClock(),
- Constants.STATUS_SUCCESS, mStartServiceTimeMillis);
- return val;
- },
- mInjector.getExecutor()
- )
- .catchingAsync(
- Exception.class,
- e -> {
- StatsUtils.writeServiceRequestMetrics(
- Constants.API_NAME_SERVICE_ON_RENDER, /* result= */ null,
- mInjector.getClock(),
- Constants.STATUS_INTERNAL_ERROR, mStartServiceTimeMillis);
- return Futures.immediateFailedFuture(e);
- },
- mInjector.getExecutor()
- );
+ var unused =
+ FluentFuture.from(runServiceFuture)
+ .transform(
+ val -> {
+ StatsUtils.writeServiceRequestMetrics(
+ Constants.API_NAME_SERVICE_ON_RENDER,
+ mService.getPackageName(),
+ val,
+ mInjector.getClock(),
+ Constants.STATUS_SUCCESS,
+ mStartServiceTimeMillis);
+ return val;
+ },
+ mInjector.getExecutor())
+ .catchingAsync(
+ Exception.class,
+ e -> {
+ StatsUtils.writeServiceRequestMetrics(
+ Constants.API_NAME_SERVICE_ON_RENDER,
+ mService.getPackageName(),
+ /* result= */ null,
+ mInjector.getClock(),
+ Constants.STATUS_INTERNAL_ERROR,
+ mStartServiceTimeMillis);
+ return Futures.immediateFailedFuture(e);
+ },
+ mInjector.getExecutor());
}
@Override
@@ -286,9 +291,10 @@
sendErrorResult(
e.getErrorCode(),
DebugUtils.getIsolatedServiceExceptionCode(
- mContext, mService, e));
+ mContext, mService, e),
+ t);
} else {
- sendErrorResult(Constants.STATUS_INTERNAL_ERROR, t);
+ sendErrorResult(Constants.STATUS_INTERNAL_ERROR, 0, t);
}
}
},
@@ -303,7 +309,10 @@
sendSuccessResult(surfacePackage);
} else {
sLogger.w(TAG + ": surfacePackages is null or empty");
- sendErrorResult(Constants.STATUS_INTERNAL_ERROR, 0);
+ sendErrorResult(
+ Constants.STATUS_INTERNAL_ERROR,
+ 0,
+ new IllegalStateException("missing surfacePackage"));
}
}
@@ -321,26 +330,12 @@
}
}
- private void sendErrorResult(int errorCode, int isolatedServiceErrorCode) {
+ private void sendErrorResult(int errorCode, int isolatedServiceErrorCode, Throwable t) {
try {
mCallback.onError(
errorCode,
isolatedServiceErrorCode,
- null,
- new CalleeMetadata.Builder()
- .setServiceEntryTimeMillis(mServiceEntryTimeMillis)
- .setCallbackInvokeTimeMillis(SystemClock.elapsedRealtime()).build());
- } catch (RemoteException e) {
- sLogger.w(TAG + ": Callback error", e);
- }
- }
-
- private void sendErrorResult(int errorCode, Throwable t) {
- try {
- mCallback.onError(
- errorCode,
- 0,
- DebugUtils.getErrorMessage(mContext, t),
+ DebugUtils.serializeExceptionInfo(mService, t),
new CalleeMetadata.Builder()
.setServiceEntryTimeMillis(mServiceEntryTimeMillis)
.setCallbackInvokeTimeMillis(SystemClock.elapsedRealtime()).build());
diff --git a/src/com/android/ondevicepersonalization/services/serviceflow/ServiceFlowFactory.java b/src/com/android/ondevicepersonalization/services/serviceflow/ServiceFlowFactory.java
index 50ea958..34f0ca9 100644
--- a/src/com/android/ondevicepersonalization/services/serviceflow/ServiceFlowFactory.java
+++ b/src/com/android/ondevicepersonalization/services/serviceflow/ServiceFlowFactory.java
@@ -18,6 +18,7 @@
import android.adservices.ondevicepersonalization.DownloadCompletedOutputParcel;
import android.adservices.ondevicepersonalization.EventOutputParcel;
+import android.adservices.ondevicepersonalization.ExecuteOptionsParcel;
import android.adservices.ondevicepersonalization.RequestLogRecord;
import android.adservices.ondevicepersonalization.aidl.IExecuteCallback;
import android.adservices.ondevicepersonalization.aidl.IRegisterMeasurementEventCallback;
@@ -27,6 +28,7 @@
import android.os.Bundle;
import android.os.IBinder;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.ondevicepersonalization.services.data.events.EventUrlPayload;
import com.android.ondevicepersonalization.services.display.WebViewFlow;
import com.android.ondevicepersonalization.services.download.DownloadFlow;
@@ -43,26 +45,104 @@
public static ServiceFlow createInstance(ServiceFlowType serviceFlowType, Object... args) {
return switch (serviceFlowType) {
case APP_REQUEST_FLOW ->
- new AppRequestFlow((String) args[0], (ComponentName) args[1], (Bundle) args[2],
- (IExecuteCallback) args[3], (Context) args[4], (long) args[5],
- (long) args[6]);
+ new AppRequestFlow(
+ (String) args[0],
+ (ComponentName) args[1],
+ (Bundle) args[2],
+ (IExecuteCallback) args[3],
+ (Context) args[4],
+ (long) args[5],
+ (long) args[6],
+ (ExecuteOptionsParcel) args[7]);
case RENDER_FLOW ->
- new RenderFlow((String) args[0], (IBinder) args[1], (int) args[2],
- (int) args[3], (int) args[4], (IRequestSurfacePackageCallback) args[5],
- (Context) args[6], (long) args[7], (long) args[8]);
+ new RenderFlow(
+ (String) args[0],
+ (IBinder) args[1],
+ (int) args[2],
+ (int) args[3],
+ (int) args[4],
+ (IRequestSurfacePackageCallback) args[5],
+ (Context) args[6],
+ (long) args[7],
+ (long) args[8]);
case WEB_TRIGGER_FLOW ->
- new WebTriggerFlow((Bundle) args[0], (Context) args[1],
- (IRegisterMeasurementEventCallback) args[2], (long) args[3],
+ new WebTriggerFlow(
+ (Bundle) args[0],
+ (Context) args[1],
+ (IRegisterMeasurementEventCallback) args[2],
+ (long) args[3],
(long) args[4]);
case WEB_VIEW_FLOW ->
- new WebViewFlow((Context) args[0], (ComponentName) args[1], (long) args[2],
- (RequestLogRecord) args[3], (FutureCallback<EventOutputParcel>) args[4],
+ new WebViewFlow(
+ (Context) args[0],
+ (ComponentName) args[1],
+ (long) args[2],
+ (RequestLogRecord) args[3],
+ (FutureCallback<EventOutputParcel>) args[4],
(EventUrlPayload) args[5]);
case DOWNLOAD_FLOW ->
- new DownloadFlow((String) args[0], (Context) args[1],
+ new DownloadFlow(
+ (String) args[0],
+ (Context) args[1],
(FutureCallback<DownloadCompletedOutputParcel>) args[2]);
- default -> throw new IllegalArgumentException(
- "Invalid service flow type: " + serviceFlowType);
+ default ->
+ throw new IllegalArgumentException(
+ "Invalid service flow type: " + serviceFlowType);
+ };
+ }
+
+ /** Create a service flow instance give the type for testing only. */
+ @VisibleForTesting
+ public static ServiceFlow createInstanceForTest(
+ ServiceFlowType serviceFlowType, Object... args) {
+ // TODO(b/354265327): only support injector in app request flow. Need update constructor for
+ // testing in other flows.
+ return switch (serviceFlowType) {
+ case APP_REQUEST_FLOW ->
+ new AppRequestFlow(
+ (String) args[0],
+ (ComponentName) args[1],
+ (Bundle) args[2],
+ (IExecuteCallback) args[3],
+ (Context) args[4],
+ (long) args[5],
+ (long) args[6],
+ (ExecuteOptionsParcel) args[7],
+ (AppRequestFlow.Injector) args[8]);
+ case RENDER_FLOW ->
+ new RenderFlow(
+ (String) args[0],
+ (IBinder) args[1],
+ (int) args[2],
+ (int) args[3],
+ (int) args[4],
+ (IRequestSurfacePackageCallback) args[5],
+ (Context) args[6],
+ (long) args[7],
+ (long) args[8]);
+ case WEB_TRIGGER_FLOW ->
+ new WebTriggerFlow(
+ (Bundle) args[0],
+ (Context) args[1],
+ (IRegisterMeasurementEventCallback) args[2],
+ (long) args[3],
+ (long) args[4]);
+ case WEB_VIEW_FLOW ->
+ new WebViewFlow(
+ (Context) args[0],
+ (ComponentName) args[1],
+ (long) args[2],
+ (RequestLogRecord) args[3],
+ (FutureCallback<EventOutputParcel>) args[4],
+ (EventUrlPayload) args[5]);
+ case DOWNLOAD_FLOW ->
+ new DownloadFlow(
+ (String) args[0],
+ (Context) args[1],
+ (FutureCallback<DownloadCompletedOutputParcel>) args[2]);
+ default ->
+ throw new IllegalArgumentException(
+ "Invalid service flow type: " + serviceFlowType);
};
}
}
diff --git a/src/com/android/ondevicepersonalization/services/serviceflow/ServiceFlowOrchestrator.java b/src/com/android/ondevicepersonalization/services/serviceflow/ServiceFlowOrchestrator.java
index 42878c1..d5b215e 100644
--- a/src/com/android/ondevicepersonalization/services/serviceflow/ServiceFlowOrchestrator.java
+++ b/src/com/android/ondevicepersonalization/services/serviceflow/ServiceFlowOrchestrator.java
@@ -16,8 +16,11 @@
package com.android.ondevicepersonalization.services.serviceflow;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.ondevicepersonalization.services.OnDevicePersonalizationExecutors;
+import java.util.concurrent.Executors;
+
/** Orchestrator that handles the scheduling of all service flows. */
public class ServiceFlowOrchestrator {
@@ -49,4 +52,13 @@
.submit(serviceFlowTask::run);
};
}
+
+ /** Schedules a given service flow task with the orchestrator for testing only. */
+ @VisibleForTesting
+ public void scheduleForTest(ServiceFlowType serviceFlowType, Object... args) {
+ ServiceFlow serviceFlow = ServiceFlowFactory.createInstanceForTest(serviceFlowType, args);
+
+ ServiceFlowTask serviceFlowTask = new ServiceFlowTask(serviceFlowType, serviceFlow);
+ Executors.newSingleThreadExecutor().submit(serviceFlowTask::run);
+ }
}
diff --git a/src/com/android/ondevicepersonalization/services/serviceflow/ServiceFlowTask.java b/src/com/android/ondevicepersonalization/services/serviceflow/ServiceFlowTask.java
index 3455b3a..2c95c19 100644
--- a/src/com/android/ondevicepersonalization/services/serviceflow/ServiceFlowTask.java
+++ b/src/com/android/ondevicepersonalization/services/serviceflow/ServiceFlowTask.java
@@ -21,8 +21,8 @@
import android.os.Bundle;
import com.android.ondevicepersonalization.internal.util.LoggerFactory;
-import com.android.ondevicepersonalization.services.FlagsFactory;
import com.android.ondevicepersonalization.services.OnDevicePersonalizationExecutors;
+import com.android.ondevicepersonalization.services.StableFlags;
import com.android.ondevicepersonalization.services.process.IsolatedServiceInfo;
import com.android.ondevicepersonalization.services.process.PluginProcessRunner;
import com.android.ondevicepersonalization.services.process.ProcessRunner;
@@ -55,8 +55,7 @@
mServiceFlowType = serviceFlowType;
mServiceFlow = serviceFlow;
mProcessRunner =
- (boolean) FlagsFactory.getFlags()
- .getStableFlag(KEY_SHARED_ISOLATED_PROCESS_FEATURE_ENABLED)
+ (boolean) StableFlags.get(KEY_SHARED_ISOLATED_PROCESS_FEATURE_ENABLED)
? SharedIsolatedProcessRunner.getInstance()
: PluginProcessRunner.getInstance();
}
@@ -80,8 +79,10 @@
/** Executes the given service flow. */
public void run() {
try {
- if (mIsCompleted || !mServiceFlow.isServiceFlowReady()) {
- sLogger.d(TAG + ": Unexpected service flow state for " + mServiceFlowType);
+ boolean isServiceFlowReady = mServiceFlow.isServiceFlowReady();
+ if (mIsCompleted || !isServiceFlowReady) {
+ sLogger.d(TAG + " skipped running %s, isCompleted: %s, isServiceFlowReady: %s",
+ mServiceFlowType, mIsCompleted, isServiceFlowReady);
return;
}
diff --git a/src/com/android/ondevicepersonalization/services/serviceflow/ServiceFlowType.java b/src/com/android/ondevicepersonalization/services/serviceflow/ServiceFlowType.java
index 52cf2b5..7af72ad 100644
--- a/src/com/android/ondevicepersonalization/services/serviceflow/ServiceFlowType.java
+++ b/src/com/android/ondevicepersonalization/services/serviceflow/ServiceFlowType.java
@@ -23,52 +23,35 @@
import static android.adservices.ondevicepersonalization.Constants.OP_WEB_TRIGGER;
import static android.adservices.ondevicepersonalization.Constants.OP_WEB_VIEW_EVENT;
-import static com.android.ondevicepersonalization.services.PhFlags.KEY_APP_REQUEST_FLOW_DEADLINE_SECONDS;
-import static com.android.ondevicepersonalization.services.PhFlags.KEY_DOWNLOAD_FLOW_DEADLINE_SECONDS;
-import static com.android.ondevicepersonalization.services.PhFlags.KEY_EXAMPLE_STORE_FLOW_DEADLINE_SECONDS;
-import static com.android.ondevicepersonalization.services.PhFlags.KEY_RENDER_FLOW_DEADLINE_SECONDS;
-import static com.android.ondevicepersonalization.services.PhFlags.KEY_WEB_TRIGGER_FLOW_DEADLINE_SECONDS;
-import static com.android.ondevicepersonalization.services.PhFlags.KEY_WEB_VIEW_FLOW_DEADLINE_SECONDS;
-
-import com.android.ondevicepersonalization.services.FlagsFactory;
-
/** Collection of on-device personalization service flows. */
public enum ServiceFlowType {
APP_REQUEST_FLOW(
- "AppRequest", OP_EXECUTE, Priority.HIGH,
- (int) FlagsFactory.getFlags().getStableFlag(KEY_APP_REQUEST_FLOW_DEADLINE_SECONDS)),
+ "AppRequest", OP_EXECUTE, Priority.HIGH),
RENDER_FLOW(
- "Render", OP_RENDER, Priority.HIGH,
- (int) FlagsFactory.getFlags().getStableFlag(KEY_RENDER_FLOW_DEADLINE_SECONDS)),
+ "Render", OP_RENDER, Priority.HIGH),
WEB_TRIGGER_FLOW(
- "WebTrigger", OP_WEB_TRIGGER, Priority.NORMAL,
- (int) FlagsFactory.getFlags().getStableFlag(KEY_WEB_TRIGGER_FLOW_DEADLINE_SECONDS)),
+ "WebTrigger", OP_WEB_TRIGGER, Priority.NORMAL),
WEB_VIEW_FLOW(
- "ComputeEventMetrics", OP_WEB_VIEW_EVENT, Priority.NORMAL,
- (int) FlagsFactory.getFlags().getStableFlag(KEY_WEB_VIEW_FLOW_DEADLINE_SECONDS)),
+ "WebView", OP_WEB_VIEW_EVENT, Priority.NORMAL),
EXAMPLE_STORE_FLOW(
- "ExampleStore", OP_TRAINING_EXAMPLE, Priority.NORMAL,
- (int) FlagsFactory.getFlags().getStableFlag(KEY_EXAMPLE_STORE_FLOW_DEADLINE_SECONDS)),
+ "ExampleStore", OP_TRAINING_EXAMPLE, Priority.NORMAL),
DOWNLOAD_FLOW(
- "DownloadJob", OP_DOWNLOAD, Priority.LOW,
- (int) FlagsFactory.getFlags().getStableFlag(KEY_DOWNLOAD_FLOW_DEADLINE_SECONDS));
+ "DownloadJob", OP_DOWNLOAD, Priority.LOW);
final String mTaskName;
final int mOperationCode;
final Priority mPriority;
- final int mExecutionTimeout;
- ServiceFlowType(String taskName, int operationCode, Priority priority, int executionTimeout) {
+ ServiceFlowType(String taskName, int operationCode, Priority priority) {
mTaskName = taskName;
mOperationCode = operationCode;
mPriority = priority;
- mExecutionTimeout = executionTimeout;
}
public String getTaskName() {
@@ -83,10 +66,6 @@
return mPriority;
}
- public int getExecutionTimeout() {
- return mExecutionTimeout;
- }
-
public enum Priority {
HIGH,
NORMAL,
diff --git a/src/com/android/ondevicepersonalization/services/sharedlibrary/spe/OdpJobServiceFactory.java b/src/com/android/ondevicepersonalization/services/sharedlibrary/spe/OdpJobServiceFactory.java
index 8d94612..7540877 100644
--- a/src/com/android/ondevicepersonalization/services/sharedlibrary/spe/OdpJobServiceFactory.java
+++ b/src/com/android/ondevicepersonalization/services/sharedlibrary/spe/OdpJobServiceFactory.java
@@ -22,11 +22,11 @@
import android.content.Context;
import com.android.adservices.shared.proto.ModuleJobPolicy;
-import com.android.adservices.shared.proto.ProtoParser;
import com.android.adservices.shared.spe.framework.JobServiceFactory;
import com.android.adservices.shared.spe.framework.JobWorker;
import com.android.adservices.shared.spe.logging.JobSchedulingLogger;
import com.android.adservices.shared.spe.logging.JobServiceLogger;
+import com.android.adservices.shared.util.ProtoParser;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.ondevicepersonalization.internal.util.LoggerFactory;
@@ -86,6 +86,7 @@
ModuleJobPolicy policy =
ProtoParser.parseBase64EncodedStringToProto(
ModuleJobPolicy.parser(),
+ ClientErrorLogger.getInstance(),
PROTO_PROPERTY_FOR_LOGCAT,
flags.getOdpModuleJobPolicy());
sSingleton =
diff --git a/src/com/android/ondevicepersonalization/services/statsd/OdpStatsdLogger.java b/src/com/android/ondevicepersonalization/services/statsd/OdpStatsdLogger.java
index 3b7c39b..d958a33 100644
--- a/src/com/android/ondevicepersonalization/services/statsd/OdpStatsdLogger.java
+++ b/src/com/android/ondevicepersonalization/services/statsd/OdpStatsdLogger.java
@@ -17,9 +17,11 @@
package com.android.ondevicepersonalization.services.statsd;
import static com.android.ondevicepersonalization.OnDevicePersonalizationStatsLog.ONDEVICEPERSONALIZATION_API_CALLED;
+import static com.android.ondevicepersonalization.OnDevicePersonalizationStatsLog.ON_DEVICE_PERSONALIZATION_API_CALLED__API_NAME__ADSERVICES_GET_COMMON_STATES;
import static com.android.ondevicepersonalization.OnDevicePersonalizationStatsLog.ON_DEVICE_PERSONALIZATION_API_CALLED__API_NAME__EVENT_URL_CREATE_WITH_REDIRECT;
import static com.android.ondevicepersonalization.OnDevicePersonalizationStatsLog.ON_DEVICE_PERSONALIZATION_API_CALLED__API_NAME__EVENT_URL_CREATE_WITH_RESPONSE;
import static com.android.ondevicepersonalization.OnDevicePersonalizationStatsLog.ON_DEVICE_PERSONALIZATION_API_CALLED__API_NAME__EXECUTE;
+import static com.android.ondevicepersonalization.OnDevicePersonalizationStatsLog.ON_DEVICE_PERSONALIZATION_API_CALLED__API_NAME__FEDERATED_COMPUTE_CANCEL;
import static com.android.ondevicepersonalization.OnDevicePersonalizationStatsLog.ON_DEVICE_PERSONALIZATION_API_CALLED__API_NAME__FEDERATED_COMPUTE_SCHEDULE;
import static com.android.ondevicepersonalization.OnDevicePersonalizationStatsLog.ON_DEVICE_PERSONALIZATION_API_CALLED__API_NAME__LOCAL_DATA_GET;
import static com.android.ondevicepersonalization.OnDevicePersonalizationStatsLog.ON_DEVICE_PERSONALIZATION_API_CALLED__API_NAME__LOCAL_DATA_KEYSET;
@@ -28,6 +30,7 @@
import static com.android.ondevicepersonalization.OnDevicePersonalizationStatsLog.ON_DEVICE_PERSONALIZATION_API_CALLED__API_NAME__LOG_READER_GET_JOINED_EVENTS;
import static com.android.ondevicepersonalization.OnDevicePersonalizationStatsLog.ON_DEVICE_PERSONALIZATION_API_CALLED__API_NAME__LOG_READER_GET_REQUESTS;
import static com.android.ondevicepersonalization.OnDevicePersonalizationStatsLog.ON_DEVICE_PERSONALIZATION_API_CALLED__API_NAME__MODEL_MANAGER_RUN;
+import static com.android.ondevicepersonalization.OnDevicePersonalizationStatsLog.ON_DEVICE_PERSONALIZATION_API_CALLED__API_NAME__NOTIFY_MEASUREMENT_EVENT;
import static com.android.ondevicepersonalization.OnDevicePersonalizationStatsLog.ON_DEVICE_PERSONALIZATION_API_CALLED__API_NAME__REMOTE_DATA_GET;
import static com.android.ondevicepersonalization.OnDevicePersonalizationStatsLog.ON_DEVICE_PERSONALIZATION_API_CALLED__API_NAME__REMOTE_DATA_KEYSET;
import static com.android.ondevicepersonalization.OnDevicePersonalizationStatsLog.ON_DEVICE_PERSONALIZATION_API_CALLED__API_NAME__REQUEST_SURFACE_PACKAGE;
@@ -46,9 +49,11 @@
public class OdpStatsdLogger {
private static volatile OdpStatsdLogger sStatsdLogger = null;
private static final Set<Integer> sApiNames = Set.of(
+ ON_DEVICE_PERSONALIZATION_API_CALLED__API_NAME__ADSERVICES_GET_COMMON_STATES,
ON_DEVICE_PERSONALIZATION_API_CALLED__API_NAME__EVENT_URL_CREATE_WITH_REDIRECT,
ON_DEVICE_PERSONALIZATION_API_CALLED__API_NAME__EVENT_URL_CREATE_WITH_RESPONSE,
ON_DEVICE_PERSONALIZATION_API_CALLED__API_NAME__EXECUTE,
+ ON_DEVICE_PERSONALIZATION_API_CALLED__API_NAME__FEDERATED_COMPUTE_CANCEL,
ON_DEVICE_PERSONALIZATION_API_CALLED__API_NAME__FEDERATED_COMPUTE_SCHEDULE,
ON_DEVICE_PERSONALIZATION_API_CALLED__API_NAME__LOCAL_DATA_GET,
ON_DEVICE_PERSONALIZATION_API_CALLED__API_NAME__LOCAL_DATA_KEYSET,
@@ -57,6 +62,7 @@
ON_DEVICE_PERSONALIZATION_API_CALLED__API_NAME__LOG_READER_GET_JOINED_EVENTS,
ON_DEVICE_PERSONALIZATION_API_CALLED__API_NAME__LOG_READER_GET_REQUESTS,
ON_DEVICE_PERSONALIZATION_API_CALLED__API_NAME__MODEL_MANAGER_RUN,
+ ON_DEVICE_PERSONALIZATION_API_CALLED__API_NAME__NOTIFY_MEASUREMENT_EVENT,
ON_DEVICE_PERSONALIZATION_API_CALLED__API_NAME__REMOTE_DATA_GET,
ON_DEVICE_PERSONALIZATION_API_CALLED__API_NAME__REMOTE_DATA_KEYSET,
ON_DEVICE_PERSONALIZATION_API_CALLED__API_NAME__REQUEST_SURFACE_PACKAGE,
diff --git a/src/com/android/ondevicepersonalization/services/statsd/joblogging/OdpJobServiceLogger.java b/src/com/android/ondevicepersonalization/services/statsd/joblogging/OdpJobServiceLogger.java
index b82bea3..063229a 100644
--- a/src/com/android/ondevicepersonalization/services/statsd/joblogging/OdpJobServiceLogger.java
+++ b/src/com/android/ondevicepersonalization/services/statsd/joblogging/OdpJobServiceLogger.java
@@ -36,12 +36,12 @@
/** A background job logger to log ODP background job stats. */
public final class OdpJobServiceLogger extends JobServiceLogger {
@GuardedBy("SINGLETON_LOCK")
- private static volatile OdpJobServiceLogger sSingleton;
+ private static OdpJobServiceLogger sSingleton;
private static final Object SINGLETON_LOCK = new Object();
/** Create an instance of {@link JobServiceLogger}. */
- public OdpJobServiceLogger(
+ private OdpJobServiceLogger(
Context context,
Clock clock,
StatsdJobServiceLogger statsdLogger,
diff --git a/src/com/android/ondevicepersonalization/services/util/AllowListUtils.java b/src/com/android/ondevicepersonalization/services/util/AllowListUtils.java
index b1fa932..a9ccd56 100644
--- a/src/com/android/ondevicepersonalization/services/util/AllowListUtils.java
+++ b/src/com/android/ondevicepersonalization/services/util/AllowListUtils.java
@@ -31,10 +31,12 @@
public static boolean isAllowListed(final String entityName,
final String packageCertificate,
@NonNull final String allowList) {
+ if (allowList == null) {
+ return false;
+ }
if (ALLOW_ALL.equals(allowList)) {
return true;
}
-
if (entityName == null || entityName.trim().isEmpty()) {
return false;
}
@@ -82,6 +84,8 @@
String[] entityAndCert = entityInAllowList.split(CERT_SPLITTER);
if (entityAndCert == null) {
return false;
+ } else if (ALLOW_ALL.equals(entityInAllowList)) {
+ return true;
} else if (entityAndCert.length == 1 && entityAndCert[0] != null
&& !entityAndCert[0].isBlank()) {
return entityAndCert[0].equals(entityName);
diff --git a/src/com/android/ondevicepersonalization/services/util/DebugUtils.java b/src/com/android/ondevicepersonalization/services/util/DebugUtils.java
index dbf3acc..1f5e095 100644
--- a/src/com/android/ondevicepersonalization/services/util/DebugUtils.java
+++ b/src/com/android/ondevicepersonalization/services/util/DebugUtils.java
@@ -21,13 +21,17 @@
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.os.Build;
+import android.os.SystemProperties;
import android.provider.Settings;
import com.android.odp.module.common.PackageUtils;
+import com.android.ondevicepersonalization.internal.util.ExceptionInfo;
import com.android.ondevicepersonalization.internal.util.LoggerFactory;
import com.android.ondevicepersonalization.services.FlagsFactory;
import com.android.ondevicepersonalization.services.OdpServiceException;
+import com.android.ondevicepersonalization.services.OnDevicePersonalizationApplication;
import java.util.Objects;
@@ -35,6 +39,12 @@
public class DebugUtils {
private static final LoggerFactory.Logger sLogger = LoggerFactory.getLogger();
private static final String TAG = DebugUtils.class.getSimpleName();
+ private static final int MAX_EXCEPTION_CHAIN_DEPTH = 3;
+
+ private static final String OVERRIDE_FC_SERVER_URL_PACKAGE =
+ "debug.ondevicepersonalization.override_fc_server_url_package";
+ private static final String OVERRIDE_FC_SERVER_URL =
+ "debug.ondevicepersonalization.override_fc_server_url";
/** Returns true if the device is debuggable. */
public static boolean isDeveloperModeEnabled(@NonNull Context context) {
@@ -65,17 +75,59 @@
return 0;
}
- /** Returns the exception message if debugging is allowed. */
- public static String getErrorMessage(@NonNull Context context, Throwable t) {
+ /** Serializes an exception chain to a byte[] */
+ public static byte[] serializeExceptionInfo(
+ ComponentName service, Throwable t) {
try {
- if (t != null && isDeveloperModeEnabled(context)
- && FlagsFactory.getFlags().isIsolatedServiceDebuggingEnabled()) {
- return t.getClass().getSimpleName() + ": " + t.getMessage();
+ Context context = OnDevicePersonalizationApplication.getAppContext();
+ if (t == null || !isDeveloperModeEnabled(context)
+ || !FlagsFactory.getFlags().isIsolatedServiceDebuggingEnabled()
+ || !PackageUtils.isPackageDebuggable(context, service.getPackageName())) {
+ return null;
}
+
+ return ExceptionInfo.toByteArray(t, MAX_EXCEPTION_CHAIN_DEPTH);
} catch (Exception e) {
- sLogger.e(e, TAG + ": failed to get message");
+ sLogger.e(e, TAG + ": failed to serialize exception info");
+ return null;
}
- return null;
+ }
+
+ /**
+ * Returns an override URL for federated compute for the provided package if one exists, else
+ * returns empty if a matching override is not found.
+ *
+ * @param applicationContext the application context.
+ * @param packageName the package for which to check for override.
+ * @return override URL or empty string if an override is not found.
+ */
+ public static String getFcServerOverrideUrl(Context applicationContext, String packageName) {
+ String url = "";
+ // Check for override manifest url property, if package is debuggable
+ try {
+ if (!PackageUtils.isPackageDebuggable(applicationContext, packageName)) {
+ return url;
+ }
+ } catch (PackageManager.NameNotFoundException nne) {
+ sLogger.e(TAG + ": failed to get override URL for package." + nne);
+ return url;
+ }
+
+ // Check system properties first
+ if (SystemProperties.get(OVERRIDE_FC_SERVER_URL_PACKAGE, "").equals(packageName)) {
+ String overrideManifestUrl = SystemProperties.get(OVERRIDE_FC_SERVER_URL, "");
+ if (!overrideManifestUrl.isEmpty()) {
+ sLogger.d(
+ TAG
+ + ": Overriding FC server URL from system properties for package"
+ + packageName
+ + " to "
+ + overrideManifestUrl);
+ url = overrideManifestUrl;
+ }
+ }
+
+ return url;
}
private DebugUtils() {}
diff --git a/src/com/android/ondevicepersonalization/services/util/NoiseUtil.java b/src/com/android/ondevicepersonalization/services/util/NoiseUtil.java
new file mode 100644
index 0000000..b3cd783
--- /dev/null
+++ b/src/com/android/ondevicepersonalization/services/util/NoiseUtil.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ondevicepersonalization.services.util;
+
+import com.android.ondevicepersonalization.internal.util.LoggerFactory;
+import com.android.ondevicepersonalization.services.FlagsFactory;
+
+import java.util.concurrent.ThreadLocalRandom;
+
+/** Util class for adding noise to returned result. */
+public class NoiseUtil {
+ private static final LoggerFactory.Logger sLogger = LoggerFactory.getLogger();
+ private static final String TAG = NoiseUtil.class.getSimpleName();
+
+ /**
+ * Add noise to {@link OnDevicePersonalizationManager#executeInIsolatedService} with best value
+ * option.
+ */
+ public int applyNoiseToBestValue(int actualValue, int maxValue, ThreadLocalRandom random) {
+ if (actualValue < 0 || actualValue > maxValue) {
+ sLogger.e(
+ TAG + ": returned int value %d is not in the range [0, %d].",
+ actualValue,
+ maxValue);
+ return -1;
+ }
+ int noisedValue = actualValue;
+ boolean shouldSelectRandomValue =
+ random.nextDouble() < FlagsFactory.getFlags().getNoiseForExecuteBestValue();
+ if (shouldSelectRandomValue) {
+ while (noisedValue == actualValue) {
+ noisedValue = random.nextInt(maxValue);
+ }
+ }
+ return noisedValue;
+ }
+}
diff --git a/src/com/android/ondevicepersonalization/services/util/StatsUtils.java b/src/com/android/ondevicepersonalization/services/util/StatsUtils.java
index ae0dd1f..9a41fbc 100644
--- a/src/com/android/ondevicepersonalization/services/util/StatsUtils.java
+++ b/src/com/android/ondevicepersonalization/services/util/StatsUtils.java
@@ -42,28 +42,31 @@
return callerLatencyMillis - calleeLatencyMillis;
}
- /** Writes app request usage to statsd. */
- public static void writeAppRequestMetrics(
- int apiName, Clock clock, int responseCode, long startTimeMillis) {
- int latencyMillis = (int) (clock.elapsedRealtime() - startTimeMillis);
- ApiCallStats callStats = new ApiCallStats.Builder(apiName)
- .setLatencyMillis(latencyMillis)
- .setResponseCode(responseCode)
- .build();
+ /** Writes service request usage to statsd. Mainly for failure case. */
+ public static void writeServiceRequestMetrics(int apiName, int responseCode) {
+ ApiCallStats callStats =
+ new ApiCallStats.Builder(apiName).setResponseCode(responseCode).build();
OdpStatsdLogger.getInstance().logApiCallStats(callStats);
}
/** Writes service request usage to statsd. */
public static void writeServiceRequestMetrics(
- int apiName, Bundle result, Clock clock, int responseCode, long startTimeMillis) {
+ int apiName,
+ String sdkPackageName,
+ Bundle result,
+ Clock clock,
+ int responseCode,
+ long startTimeMillis) {
int latencyMillis = (int) (clock.elapsedRealtime() - startTimeMillis);
int overheadLatencyMillis =
(int) StatsUtils.getOverheadLatencyMillis(latencyMillis, result);
- ApiCallStats callStats = new ApiCallStats.Builder(apiName)
- .setLatencyMillis(latencyMillis)
- .setOverheadLatencyMillis(overheadLatencyMillis)
- .setResponseCode(responseCode)
- .build();
+ ApiCallStats callStats =
+ new ApiCallStats.Builder(apiName)
+ .setLatencyMillis(latencyMillis)
+ .setOverheadLatencyMillis(overheadLatencyMillis)
+ .setResponseCode(responseCode)
+ .setSdkPackageName(sdkPackageName == null ? "" : sdkPackageName)
+ .build();
OdpStatsdLogger.getInstance().logApiCallStats(callStats);
}
private StatsUtils() {}
diff --git a/src/com/android/ondevicepersonalization/services/webtrigger/WebTriggerFlow.java b/src/com/android/ondevicepersonalization/services/webtrigger/WebTriggerFlow.java
index 00cec81..669b2f1 100644
--- a/src/com/android/ondevicepersonalization/services/webtrigger/WebTriggerFlow.java
+++ b/src/com/android/ondevicepersonalization/services/webtrigger/WebTriggerFlow.java
@@ -218,28 +218,33 @@
@Override
public void uploadServiceFlowMetrics(ListenableFuture<Bundle> runServiceFuture) {
- var unused = FluentFuture.from(runServiceFuture)
- .transform(
- val -> {
- StatsUtils.writeServiceRequestMetrics(
- Constants.API_NAME_SERVICE_ON_WEB_TRIGGER,
- val, mInjector.getClock(),
- Constants.STATUS_SUCCESS, mStartServiceTimeMillis);
- return val;
- },
- mInjector.getExecutor()
- )
- .catchingAsync(
- Exception.class,
- e -> {
- StatsUtils.writeServiceRequestMetrics(
- Constants.API_NAME_SERVICE_ON_WEB_TRIGGER, /* result= */ null,
- mInjector.getClock(),
- Constants.STATUS_INTERNAL_ERROR, mStartServiceTimeMillis);
- return Futures.immediateFailedFuture(e);
- },
- mInjector.getExecutor()
- );
+ var unused =
+ FluentFuture.from(runServiceFuture)
+ .transform(
+ val -> {
+ StatsUtils.writeServiceRequestMetrics(
+ Constants.API_NAME_SERVICE_ON_WEB_TRIGGER,
+ mServiceParcel.getIsolatedService().getPackageName(),
+ val,
+ mInjector.getClock(),
+ Constants.STATUS_SUCCESS,
+ mStartServiceTimeMillis);
+ return val;
+ },
+ mInjector.getExecutor())
+ .catchingAsync(
+ Exception.class,
+ e -> {
+ StatsUtils.writeServiceRequestMetrics(
+ Constants.API_NAME_SERVICE_ON_WEB_TRIGGER,
+ mServiceParcel.getIsolatedService().getPackageName(),
+ /* result= */ null,
+ mInjector.getClock(),
+ Constants.STATUS_INTERNAL_ERROR,
+ mStartServiceTimeMillis);
+ return Futures.immediateFailedFuture(e);
+ },
+ mInjector.getExecutor());
}
@Override
diff --git a/tests/commontests/Android.bp b/tests/commontests/Android.bp
new file mode 100644
index 0000000..ab9ff95
--- /dev/null
+++ b/tests/commontests/Android.bp
@@ -0,0 +1,67 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+ default_team: "trendy_team_android_rubidium",
+}
+
+android_test {
+ name: "CommonUtilsTests",
+ srcs: [
+ "src/**/*.java",
+ ":common-ondevicepersonalization-sources",
+ ],
+ defaults: [
+ // For ExtendedMockito dependencies.
+ "modules-utils-testable-device-config-defaults",
+ ],
+ libs: [
+ "android.test.base.stubs.system",
+ "android.test.runner.stubs.system",
+ "auto_value_annotations",
+ "framework-annotations-lib",
+ "framework-ondevicepersonalization.impl",
+ "truth",
+ ],
+ static_libs: [
+ "androidx.test.ext.junit",
+ "androidx.test.ext.truth",
+ "androidx.test.rules",
+ "federated-compute-java-proto-lite",
+ "flatbuffers-java",
+ "mockito-target-extended-minus-junit4",
+ "modules-utils-build",
+ "modules-utils-preconditions",
+ "libprotobuf-java-lite",
+ ],
+ manifest: "AndroidManifest.xml",
+ plugins: ["auto_value_plugin"],
+ sdk_version: "module_current",
+ target_sdk_version: "current",
+ min_sdk_version: "Tiramisu",
+ certificate: "platform",
+ compile_multilib: "both",
+ test_config: "AndroidTest.xml",
+ test_suites: [
+ "general-tests",
+ "mts-ondevicepersonalization",
+ ],
+ jni_libs: [
+ "libdexmakerjvmtiagent",
+ "libstaticjvmtiagent",
+ "libfcp_cpp_dep_jni",
+ "libfcp_hpke_jni",
+ ],
+}
diff --git a/tests/commontests/AndroidManifest.xml b/tests/commontests/AndroidManifest.xml
new file mode 100644
index 0000000..696aa97
--- /dev/null
+++ b/tests/commontests/AndroidManifest.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.odp.module.commontests" >
+
+ <!-- Used for scheduling connectivity jobs -->
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+ <uses-permission android:name="android.permission.INTERNET" />
+ <!-- Used for persisting scheduled jobs -->
+ <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
+ <uses-permission android:name="android.permission.BIND_EXAMPLE_STORE_SERVICE" />
+
+ <application android:label="CommonUtilsTests"
+ android:debuggable="true">
+ <uses-library android:name="android.test.runner"/>
+
+ </application>
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.odp.module.commontests"
+ android:label="Tests of common shared utilities for odp module"/>
+</manifest>
\ No newline at end of file
diff --git a/tests/commontests/AndroidTest.xml b/tests/commontests/AndroidTest.xml
new file mode 100644
index 0000000..617277c
--- /dev/null
+++ b/tests/commontests/AndroidTest.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<configuration description="Configuration for Common Utils unit tests">
+ <option name="config-descriptor:metadata" key="component" value="framework" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true"/>
+ <option name="test-file-name" value="CommonUtilsTests.apk"/>
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+ <option name="hidden-api-checks" value="false" />
+ <option name="package" value="com.android.odp.module.commontests"/>
+ </test>
+
+ <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+ <option name="mainline-module-package-name" value="com.google.android.ondevicepersonalization" />
+ </object>
+ <option name="config-descriptor:metadata" key="mainline-param" value="com.google.android.ondevicepersonalization.apex" />
+</configuration>
diff --git a/tests/commontests/src/com/android/odp/module/common/HttpClientTest.java b/tests/commontests/src/com/android/odp/module/common/HttpClientTest.java
new file mode 100644
index 0000000..fc30684
--- /dev/null
+++ b/tests/commontests/src/com/android/odp/module/common/HttpClientTest.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.odp.module.common;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.android.modules.utils.testing.ExtendedMockitoRule;
+
+import com.google.common.util.concurrent.MoreExecutors;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Spy;
+import org.mockito.quality.Strictness;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+
+@RunWith(JUnit4.class)
+public final class HttpClientTest {
+ @Rule
+ public final ExtendedMockitoRule extendedMockitoRule =
+ new ExtendedMockitoRule.Builder(this).setStrictness(Strictness.LENIENT).build();
+
+ private static final int DEFAULT_RETRY_LIMIT = 3;
+ private static final int HTTP_UNAVAILABLE = 503;
+ private static final int HTTP_OK = 200;
+
+ @Spy
+ private HttpClient mHttpClient =
+ new HttpClient(DEFAULT_RETRY_LIMIT, MoreExecutors.newDirectExecutorService());
+
+ @Test
+ public void testPerformGetRequestFailsWithRetry() throws Exception {
+ String failureMessage = "FAIL!";
+ OdpHttpResponse testFailedResponse =
+ new OdpHttpResponse.Builder()
+ .setHeaders(new HashMap<>())
+ .setPayload(failureMessage.getBytes(UTF_8))
+ .setStatusCode(HTTP_UNAVAILABLE)
+ .build();
+ TestHttpIOSupplier testSupplier = new TestHttpIOSupplier(testFailedResponse);
+
+ OdpHttpResponse returnedResponse = mHttpClient.performRequestWithRetry(testSupplier);
+
+ assertEquals(DEFAULT_RETRY_LIMIT, testSupplier.mCallCount.get());
+ assertThat(returnedResponse.getStatusCode()).isEqualTo(HTTP_UNAVAILABLE);
+ assertTrue(returnedResponse.getHeaders().isEmpty());
+ assertThat(returnedResponse.getPayload()).isEqualTo(failureMessage.getBytes(UTF_8));
+ }
+
+ @Test
+ public void testPerformGetRequestSuccessWithRetry() throws Exception {
+ Map<String, List<String>> mockHeaders = new HashMap<>();
+ mockHeaders.put("Header1", Arrays.asList("Value1"));
+ String failureMessage = "FAIL!";
+ String successMessage = "Success!";
+ OdpHttpResponse testFailedResponse =
+ new OdpHttpResponse.Builder()
+ .setHeaders(new HashMap<>())
+ .setPayload(failureMessage.getBytes(UTF_8))
+ .setStatusCode(HTTP_UNAVAILABLE)
+ .build();
+ OdpHttpResponse testSuccessfulResponse =
+ new OdpHttpResponse.Builder()
+ .setPayload(successMessage.getBytes(UTF_8))
+ .setStatusCode(HTTP_OK)
+ .setHeaders(mockHeaders)
+ .build();
+ TestHttpIOSupplier testSupplier =
+ new TestHttpIOSupplier(
+ testSuccessfulResponse, testFailedResponse, DEFAULT_RETRY_LIMIT - 1);
+
+ OdpHttpResponse response = mHttpClient.performRequestWithRetry(testSupplier);
+
+ assertEquals(DEFAULT_RETRY_LIMIT, testSupplier.mCallCount.get());
+ assertThat(response.getStatusCode()).isEqualTo(HTTP_OK);
+ assertThat(response.getHeaders()).isEqualTo(mockHeaders);
+ }
+
+ private static final class TestHttpIOSupplier
+ implements HttpClient.HttpIOSupplier<OdpHttpResponse> {
+ private final AtomicInteger mCallCount = new AtomicInteger(0);
+
+ private final OdpHttpResponse mSuccessfulResponse;
+ private final OdpHttpResponse mFailedResponse;
+ private final int mNumFailedCalls;
+
+ private TestHttpIOSupplier(OdpHttpResponse failedResponse) {
+ this(null, failedResponse, 0);
+ }
+
+ private TestHttpIOSupplier(
+ OdpHttpResponse successfulResponse,
+ OdpHttpResponse failedResponse,
+ int numFailedCalls) {
+ this.mSuccessfulResponse = successfulResponse;
+ this.mFailedResponse = failedResponse;
+ this.mNumFailedCalls = numFailedCalls;
+ }
+
+ @Override
+ public OdpHttpResponse get() throws IOException {
+ int callCount = mCallCount.incrementAndGet();
+ if (mSuccessfulResponse == null) {
+ return mFailedResponse;
+ }
+
+ return callCount > mNumFailedCalls ? mSuccessfulResponse : mFailedResponse;
+ }
+ }
+}
diff --git a/tests/commontests/src/com/android/odp/module/common/HttpClientUtilsTest.java b/tests/commontests/src/com/android/odp/module/common/HttpClientUtilsTest.java
new file mode 100644
index 0000000..d3e8d17
--- /dev/null
+++ b/tests/commontests/src/com/android/odp/module/common/HttpClientUtilsTest.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.odp.module.common;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.when;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.android.odp.module.common.HttpClientUtils.HttpMethod;
+import com.android.odp.module.common.HttpClientUtils.HttpURLConnectionSupplier;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.ByteArrayInputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class HttpClientUtilsTest {
+ private static final String TEST_URI = "https://valid.com";
+ private static final Map<String, String> TEST_REQUEST_HEADERS = Map.of("Foo", "Bar");
+
+ private static final OdpHttpRequest DEFAULT_GET_REQUEST =
+ OdpHttpRequest.create(
+ "https://google.com",
+ HttpClientUtils.HttpMethod.GET,
+ new HashMap<>(),
+ HttpClientUtils.EMPTY_BODY);
+
+ private static final Map<String, List<String>> TEST_RESPONSE_HEADERS =
+ ImmutableMap.of(
+ "x-content", ImmutableList.of("1", "2"), "api-key", ImmutableList.of("xyz"));
+
+ private static final OdpHttpResponse TEST_RESPONSE =
+ new OdpHttpResponse.Builder()
+ .setStatusCode(200)
+ .setPayload("payload".getBytes(UTF_8))
+ .setHeaders(TEST_RESPONSE_HEADERS)
+ .build();
+
+ private static final OdpHttpRequest TEST_EMPTY_REQUEST =
+ OdpHttpRequest.create(
+ TEST_URI, HttpMethod.GET, TEST_REQUEST_HEADERS, HttpClientUtils.EMPTY_BODY);
+
+ private static final String TEST_FAILURE_MESSAGE = "FAIL!";
+ private static final String TEST_SUCCESS_MESSAGE = "Success!";
+ @Mock private HttpURLConnectionSupplier mHttpURLConnectionSupplier;
+ @Mock private HttpURLConnection mMockHttpURLConnection;
+
+ @Before
+ public void setup() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ doReturn(mMockHttpURLConnection).when(mHttpURLConnectionSupplier).get();
+ }
+
+ @Test
+ public void testGetTotalSentBytes_emptyBody() {
+ assertThat(HttpClientUtils.getTotalSentBytes(TEST_EMPTY_REQUEST)).isEqualTo(42);
+ }
+
+ @Test
+ public void testGetTotalReceivedBytes() {
+ assertThat(HttpClientUtils.getTotalReceivedBytes(TEST_RESPONSE)).isEqualTo(43);
+ }
+
+ @Test
+ public void testUnableToOpenconnection_returnFailure() throws Exception {
+ OdpHttpRequest request =
+ OdpHttpRequest.create(
+ "https://google.com",
+ HttpClientUtils.HttpMethod.POST,
+ new HashMap<>(),
+ HttpClientUtils.EMPTY_BODY);
+ doThrow(new IOException()).when(mHttpURLConnectionSupplier).get();
+
+ assertThrows(
+ IOException.class,
+ () -> HttpClientUtils.performRequest(request, mHttpURLConnectionSupplier, false));
+ }
+
+ @Test
+ public void testPerformGetRequestSuccess() throws Exception {
+ InputStream mockStream = new ByteArrayInputStream(TEST_SUCCESS_MESSAGE.getBytes(UTF_8));
+ Map<String, List<String>> mockHeaders = new HashMap<>();
+ mockHeaders.put("Header1", Arrays.asList("Value1"));
+ when(mMockHttpURLConnection.getInputStream()).thenReturn(mockStream);
+ when(mMockHttpURLConnection.getResponseCode()).thenReturn(200);
+ when(mMockHttpURLConnection.getHeaderFields()).thenReturn(mockHeaders);
+ when(mMockHttpURLConnection.getContentLengthLong())
+ .thenReturn((long) TEST_SUCCESS_MESSAGE.length());
+
+ OdpHttpResponse response =
+ HttpClientUtils.performRequest(
+ DEFAULT_GET_REQUEST, mHttpURLConnectionSupplier, false);
+
+ assertThat(response.getStatusCode()).isEqualTo(200);
+ assertThat(response.getHeaders()).isEqualTo(mockHeaders);
+ assertThat(response.getPayload()).isEqualTo(TEST_SUCCESS_MESSAGE.getBytes(UTF_8));
+ }
+
+ @Test
+ public void testPerformGetRequestPayloadIntoFileSuccess() throws Exception {
+ InputStream mockStream = new ByteArrayInputStream(TEST_SUCCESS_MESSAGE.getBytes(UTF_8));
+ Map<String, List<String>> mockHeaders = new HashMap<>();
+ mockHeaders.put("Header1", Arrays.asList("Value1"));
+ when(mMockHttpURLConnection.getInputStream()).thenReturn(mockStream);
+ when(mMockHttpURLConnection.getResponseCode()).thenReturn(200);
+ when(mMockHttpURLConnection.getHeaderFields()).thenReturn(mockHeaders);
+ when(mMockHttpURLConnection.getContentLengthLong())
+ .thenReturn((long) TEST_SUCCESS_MESSAGE.length());
+
+ OdpHttpResponse response =
+ HttpClientUtils.performRequest(
+ DEFAULT_GET_REQUEST, mHttpURLConnectionSupplier, true);
+
+ assertThat(response.getStatusCode()).isEqualTo(200);
+ assertThat(response.getHeaders()).isEqualTo(mockHeaders);
+ assertThat(response.getPayload()).isEqualTo(null);
+ assertThat(response.getPayloadFileName()).isNotEmpty();
+ assertThat(new FileInputStream(response.getPayloadFileName()).readAllBytes())
+ .isEqualTo(TEST_SUCCESS_MESSAGE.getBytes(UTF_8));
+ }
+
+ @Test
+ public void testPerformGetRequestFails() throws Exception {
+ InputStream mockStream = new ByteArrayInputStream(TEST_FAILURE_MESSAGE.getBytes(UTF_8));
+ when(mMockHttpURLConnection.getErrorStream()).thenReturn(mockStream);
+ when(mMockHttpURLConnection.getResponseCode()).thenReturn(503);
+ when(mMockHttpURLConnection.getHeaderFields()).thenReturn(new HashMap<>());
+ when(mMockHttpURLConnection.getContentLengthLong())
+ .thenReturn((long) TEST_FAILURE_MESSAGE.length());
+
+ OdpHttpResponse response =
+ HttpClientUtils.performRequest(
+ DEFAULT_GET_REQUEST, mHttpURLConnectionSupplier, false);
+
+ assertThat(response.getStatusCode()).isEqualTo(503);
+ assertTrue(response.getHeaders().isEmpty());
+ assertThat(response.getPayload()).isEqualTo(TEST_FAILURE_MESSAGE.getBytes(UTF_8));
+ }
+
+ @Test
+ public void testSetup() throws Exception {
+ URL testURL = new URL(TEST_URI);
+
+ URLConnection urlConnection = HttpClientUtils.setup(testURL);
+
+ assertEquals(HttpClientUtils.NETWORK_CONNECT_TIMEOUT_MS, urlConnection.getConnectTimeout());
+ assertEquals(HttpClientUtils.NETWORK_READ_TIMEOUT_MS, urlConnection.getReadTimeout());
+ }
+}
diff --git a/tests/commontests/src/com/android/odp/module/common/OdpHttpRequestTest.java b/tests/commontests/src/com/android/odp/module/common/OdpHttpRequestTest.java
new file mode 100644
index 0000000..bbb71c9
--- /dev/null
+++ b/tests/commontests/src/com/android/odp/module/common/OdpHttpRequestTest.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.odp.module.common;
+
+import static com.android.odp.module.common.HttpClientUtils.ACCEPT_ENCODING_HDR;
+import static com.android.odp.module.common.HttpClientUtils.GZIP_ENCODING_HDR;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.HashMap;
+
+@RunWith(JUnit4.class)
+public final class OdpHttpRequestTest {
+ private static final byte[] PAYLOAD = "non_empty_request_body".getBytes();
+
+ @Test
+ public void testCreateRequestInvalidUri_fails() throws Exception {
+ assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ OdpHttpRequest.create(
+ "http://invalid.com",
+ HttpClientUtils.HttpMethod.GET,
+ new HashMap<>(),
+ PAYLOAD));
+ }
+
+ @Test
+ public void testCreateWithInvalidRequestBody_fails() throws Exception {
+ assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ OdpHttpRequest.create(
+ "https://valid.com",
+ HttpClientUtils.HttpMethod.GET,
+ new HashMap<>(),
+ PAYLOAD));
+ }
+
+ @Test
+ public void testCreateWithContentLengthHeader_fails() throws Exception {
+ HashMap<String, String> headers = new HashMap<>();
+ headers.put("Content-Length", "1234");
+ assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ OdpHttpRequest.create(
+ "https://valid.com",
+ HttpClientUtils.HttpMethod.POST,
+ headers,
+ PAYLOAD));
+ }
+
+ @Test
+ public void createGetRequest_valid() throws Exception {
+ String expectedUri = "https://valid.com";
+ OdpHttpRequest request =
+ OdpHttpRequest.create(
+ expectedUri,
+ HttpClientUtils.HttpMethod.GET,
+ new HashMap<>(),
+ HttpClientUtils.EMPTY_BODY);
+
+ assertThat(request.getUri()).isEqualTo(expectedUri);
+ assertThat(request.getHttpMethod()).isEqualTo(HttpClientUtils.HttpMethod.GET);
+ assertThat(request.getBody()).isEqualTo(HttpClientUtils.EMPTY_BODY);
+ assertTrue(request.getExtraHeaders().isEmpty());
+ }
+
+ @Test
+ public void createGetRequestWithHeader_valid() throws Exception {
+ String expectedUri = "https://valid.com";
+ HashMap<String, String> expectedHeaders = new HashMap<>();
+ expectedHeaders.put("Foo", "Bar");
+
+ OdpHttpRequest request =
+ OdpHttpRequest.create(
+ expectedUri,
+ HttpClientUtils.HttpMethod.GET,
+ expectedHeaders,
+ HttpClientUtils.EMPTY_BODY);
+
+ assertThat(request.getUri()).isEqualTo(expectedUri);
+ assertThat(request.getExtraHeaders()).isEqualTo(expectedHeaders);
+ }
+
+ @Test
+ public void createPostRequestWithoutBody_valid() {
+ String expectedUri = "https://valid.com";
+
+ OdpHttpRequest request =
+ OdpHttpRequest.create(
+ expectedUri,
+ HttpClientUtils.HttpMethod.POST,
+ new HashMap<>(),
+ HttpClientUtils.EMPTY_BODY);
+
+ assertThat(request.getUri()).isEqualTo(expectedUri);
+ assertTrue(request.getExtraHeaders().isEmpty());
+ assertThat(request.getHttpMethod()).isEqualTo(HttpClientUtils.HttpMethod.POST);
+ assertThat(request.getBody()).isEqualTo(HttpClientUtils.EMPTY_BODY);
+ }
+
+ @Test
+ public void createPostRequestWithBody_valid() {
+ String expectedUri = "https://valid.com";
+
+ OdpHttpRequest request =
+ OdpHttpRequest.create(
+ expectedUri, HttpClientUtils.HttpMethod.POST, new HashMap<>(), PAYLOAD);
+
+ assertThat(request.getUri()).isEqualTo(expectedUri);
+ assertThat(request.getHttpMethod()).isEqualTo(HttpClientUtils.HttpMethod.POST);
+ assertThat(request.getBody()).isEqualTo(PAYLOAD);
+ }
+
+ @Test
+ public void createPostRequestWithBodyHeader_valid() {
+ String expectedUri = "https://valid.com";
+ HashMap<String, String> expectedHeaders = new HashMap<>();
+ expectedHeaders.put("Foo", "Bar");
+
+ OdpHttpRequest request =
+ OdpHttpRequest.create(
+ expectedUri, HttpClientUtils.HttpMethod.POST, expectedHeaders, PAYLOAD);
+
+ assertThat(request.getUri()).isEqualTo(expectedUri);
+ assertThat(request.getHttpMethod()).isEqualTo(HttpClientUtils.HttpMethod.POST);
+ assertThat(request.getBody()).isEqualTo(PAYLOAD);
+ assertThat(request.getExtraHeaders()).isEqualTo(expectedHeaders);
+ }
+
+ @Test
+ public void createGetRequestWithAcceptCompression_valid() {
+ String expectedUri = "https://valid.com";
+ HashMap<String, String> headerList = new HashMap<>();
+ headerList.put(ACCEPT_ENCODING_HDR, GZIP_ENCODING_HDR);
+ OdpHttpRequest request =
+ OdpHttpRequest.create(
+ expectedUri, HttpClientUtils.HttpMethod.POST, headerList, PAYLOAD);
+
+ assertThat(request.getUri()).isEqualTo(expectedUri);
+ assertThat(request.getHttpMethod()).isEqualTo(HttpClientUtils.HttpMethod.POST);
+ HashMap<String, String> expectedHeaders = new HashMap<>();
+ expectedHeaders.put(HttpClientUtils.CONTENT_LENGTH_HDR, String.valueOf(22));
+ expectedHeaders.put(ACCEPT_ENCODING_HDR, GZIP_ENCODING_HDR);
+ assertThat(request.getExtraHeaders()).isEqualTo(expectedHeaders);
+ }
+}
diff --git a/tests/federatedcomputetests/src/com/android/federatedcompute/services/http/FederatedComputeHttpResponseTest.java b/tests/commontests/src/com/android/odp/module/common/OdpHttpResponseTest.java
similarity index 75%
rename from tests/federatedcomputetests/src/com/android/federatedcompute/services/http/FederatedComputeHttpResponseTest.java
rename to tests/commontests/src/com/android/odp/module/common/OdpHttpResponseTest.java
index d993663..cdccbd5 100644
--- a/tests/federatedcomputetests/src/com/android/federatedcompute/services/http/FederatedComputeHttpResponseTest.java
+++ b/tests/commontests/src/com/android/odp/module/common/OdpHttpResponseTest.java
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-package com.android.federatedcompute.services.http;
+package com.android.odp.module.common;
-import static com.android.federatedcompute.services.http.HttpClientUtil.OCTET_STREAM;
+import static com.android.odp.module.common.HttpClientUtils.OCTET_STREAM;
import static com.google.common.truth.Truth.assertThat;
@@ -36,7 +36,7 @@
import java.util.Map;
@RunWith(JUnit4.class)
-public final class FederatedComputeHttpResponseTest {
+public final class OdpHttpResponseTest {
@Test
public void testBuildWithAllValues() {
final int responseCode = 200;
@@ -48,8 +48,8 @@
"api-key",
ImmutableList.of("xyz"));
- FederatedComputeHttpResponse response =
- new FederatedComputeHttpResponse.Builder()
+ OdpHttpResponse response =
+ new OdpHttpResponse.Builder()
.setStatusCode(responseCode)
.setPayload(payload)
.setHeaders(headers)
@@ -63,8 +63,8 @@
@Test
public void testBuildWithMinimalRequiredValues() {
final int responseCode = 200;
- FederatedComputeHttpResponse response =
- new FederatedComputeHttpResponse.Builder().setStatusCode(responseCode).build();
+ OdpHttpResponse response =
+ new OdpHttpResponse.Builder().setStatusCode(responseCode).build();
assertThat(response.getStatusCode()).isEqualTo(responseCode);
}
@@ -73,20 +73,17 @@
public void testBuildStatusCodeNull_invalid() {
assertThrows(
IllegalArgumentException.class,
- () ->
- new FederatedComputeHttpResponse.Builder()
- .setPayload("payload".getBytes(UTF_8))
- .build());
+ () -> new OdpHttpResponse.Builder().setPayload("payload".getBytes(UTF_8)).build());
}
@Test
public void testGetBody_success() {
final byte[] uncompressedBody = "payload".getBytes(UTF_8);
Map<String, List<String>> expectedHeaders = new HashMap<>();
- expectedHeaders.put(HttpClientUtil.CONTENT_TYPE_HDR, ImmutableList.of(OCTET_STREAM));
+ expectedHeaders.put(HttpClientUtils.CONTENT_TYPE_HDR, ImmutableList.of(OCTET_STREAM));
- FederatedComputeHttpResponse response =
- new FederatedComputeHttpResponse.Builder()
+ OdpHttpResponse response =
+ new OdpHttpResponse.Builder()
.setStatusCode(200)
.setPayload(uncompressedBody)
.setHeaders(expectedHeaders)
diff --git a/tests/cts/configtest/AndroidManifest.xml b/tests/cts/configtest/AndroidManifest.xml
index 0b1e262..4b4b731 100644
--- a/tests/cts/configtest/AndroidManifest.xml
+++ b/tests/cts/configtest/AndroidManifest.xml
@@ -16,7 +16,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.ondevicepersonalization.cts.configtest">
- <application android:label="CtsOnDevicePersonalizationConfigTests">
+ <application android:debuggable="true" android:label="CtsOnDevicePersonalizationConfigTests">
<uses-library android:name="android.test.runner" />
<activity
android:name=".TestActivity"
diff --git a/tests/cts/endtoend/Android.bp b/tests/cts/endtoend/Android.bp
index ab2e36c..38b9ae0 100644
--- a/tests/cts/endtoend/Android.bp
+++ b/tests/cts/endtoend/Android.bp
@@ -32,10 +32,13 @@
"androidx.test.ext.truth",
"androidx.test.rules",
"compatibility-device-util-axt",
+ "flag-junit",
"hamcrest-library",
+ "ondevicepersonalization_flags_lib",
"ondevicepersonalization-testing-sample-service-api",
"ondevicepersonalization-testing-utils",
"platform-test-rules",
+ "platform-compat-test-rules",
],
libs: [
"sdk_public_33_android.test.base",
diff --git a/tests/cts/endtoend/AndroidManifest.xml b/tests/cts/endtoend/AndroidManifest.xml
index 286223b..832a202 100644
--- a/tests/cts/endtoend/AndroidManifest.xml
+++ b/tests/cts/endtoend/AndroidManifest.xml
@@ -16,8 +16,10 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.ondevicepersonalization.cts.e2e">
-
- <application android:label="CtsOnDevicePersonalizationE2ETests">
+ <uses-sdk android:minSdkVersion="33"
+ android:targetSdkVersion="33" />
+ <application android:debuggable="true"
+ android:label="CtsOnDevicePersonalizationE2ETests">
<uses-library android:name="android.test.runner" />
<activity
android:name=".TestActivity"
diff --git a/tests/cts/endtoend/AndroidTest.xml b/tests/cts/endtoend/AndroidTest.xml
index 78bc830..5d31241 100644
--- a/tests/cts/endtoend/AndroidTest.xml
+++ b/tests/cts/endtoend/AndroidTest.xml
@@ -19,6 +19,7 @@
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
<option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user_on_secondary_display" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true"/>
diff --git a/tests/cts/endtoend/src/com/android/ondevicepersonalization/cts/e2e/CtsOdpManagerTests.java b/tests/cts/endtoend/src/com/android/ondevicepersonalization/cts/e2e/CtsOdpManagerTests.java
index d7192bc..03707d4 100644
--- a/tests/cts/endtoend/src/com/android/ondevicepersonalization/cts/e2e/CtsOdpManagerTests.java
+++ b/tests/cts/endtoend/src/com/android/ondevicepersonalization/cts/e2e/CtsOdpManagerTests.java
@@ -15,7 +15,8 @@
*/
package com.android.ondevicepersonalization.cts.e2e;
-import static org.junit.Assert.assertArrayEquals;
+
+import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@@ -23,6 +24,8 @@
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
+import android.adservices.ondevicepersonalization.ExecuteInIsolatedServiceRequest;
+import android.adservices.ondevicepersonalization.ExecuteInIsolatedServiceResponse;
import android.adservices.ondevicepersonalization.OnDevicePersonalizationException;
import android.adservices.ondevicepersonalization.OnDevicePersonalizationManager;
import android.adservices.ondevicepersonalization.OnDevicePersonalizationManager.ExecuteResult;
@@ -32,10 +35,14 @@
import android.content.pm.PackageManager.NameNotFoundException;
import android.net.Uri;
import android.os.PersistableBundle;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.util.Base64;
import androidx.test.core.app.ApplicationProvider;
+import com.android.adservices.ondevicepersonalization.flags.Flags;
import com.android.compatibility.common.util.ShellUtils;
import com.android.ondevicepersonalization.testing.sampleserviceapi.SampleServiceApi;
import com.android.ondevicepersonalization.testing.utils.DeviceSupportHelper;
@@ -44,6 +51,7 @@
import org.junit.After;
import org.junit.Assume;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -65,11 +73,16 @@
private static final int LARGE_BLOB_SIZE = 10485760;
private static final int DELAY_MILLIS = 2000;
+ private static final String TEST_POPULATION_NAME = "criteo_app_test_task";
+
private final Context mContext = ApplicationProvider.getApplicationContext();
@Parameterized.Parameter(0)
public boolean mIsSipFeatureEnabled;
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
@Parameterized.Parameters
public static Collection<Object[]> data() {
return Arrays.asList(new Object[][] {{true}, {false}});
@@ -225,7 +238,7 @@
Executors.newSingleThreadExecutor(),
receiver);
assertNull(receiver.getResult());
- assertTrue(receiver.getException() instanceof IllegalStateException);
+ assertThat(receiver.getException()).isInstanceOf(IllegalStateException.class);
}
@Test
@@ -234,15 +247,18 @@
mContext.getSystemService(OnDevicePersonalizationManager.class);
assertNotNull(manager);
var receiver = new ResultReceiver<ExecuteResult>();
+
manager.execute(
new ComponentName("com.example.odptargetingapp2", "someclass"),
PersistableBundle.EMPTY,
Executors.newSingleThreadExecutor(),
receiver);
+
assertNull(receiver.getResult());
- assertTrue(receiver.getException() instanceof NameNotFoundException);
+ assertThat(receiver.getException()).isInstanceOf(NameNotFoundException.class);
}
+
@Test
public void testExecuteReturnsClassNotFoundIfServiceClassNotFound()
throws InterruptedException {
@@ -250,13 +266,15 @@
mContext.getSystemService(OnDevicePersonalizationManager.class);
assertNotNull(manager);
var receiver = new ResultReceiver<ExecuteResult>();
+
manager.execute(
new ComponentName(SERVICE_PACKAGE, "someclass"),
PersistableBundle.EMPTY,
Executors.newSingleThreadExecutor(),
receiver);
+
assertNull(receiver.getResult());
- assertTrue(receiver.getException() instanceof ClassNotFoundException);
+ assertThat(receiver.getException()).isInstanceOf(ClassNotFoundException.class);
}
@Test
@@ -318,7 +336,7 @@
}
@Test
- public void testExecuteWithOutputData() throws InterruptedException {
+ public void testExecuteWithOutputDataDisabled() throws InterruptedException {
OnDevicePersonalizationManager manager =
mContext.getSystemService(OnDevicePersonalizationManager.class);
assertNotNull(manager);
@@ -334,7 +352,7 @@
Executors.newSingleThreadExecutor(),
receiver);
assertTrue(receiver.getErrorMessage(), receiver.isSuccess());
- assertArrayEquals(new byte[] {'A'}, receiver.getResult().getOutputData());
+ assertThat(receiver.getResult().getOutputData()).isNull();
}
@Test
@@ -446,7 +464,7 @@
receiver);
assertTrue(receiver.isError());
assertNull(receiver.getResult());
- assertTrue(receiver.getException() instanceof OnDevicePersonalizationException);
+ assertThat(receiver.getException()).isInstanceOf(OnDevicePersonalizationException.class);
assertEquals(
((OnDevicePersonalizationException) receiver.getException()).getErrorCode(),
OnDevicePersonalizationException.ERROR_ISOLATED_SERVICE_FAILED);
@@ -469,7 +487,7 @@
receiver);
assertTrue(receiver.isError());
assertNull(receiver.getResult());
- assertTrue(receiver.getException() instanceof OnDevicePersonalizationException);
+ assertThat(receiver.getException()).isInstanceOf(OnDevicePersonalizationException.class);
assertEquals(
((OnDevicePersonalizationException) receiver.getException()).getErrorCode(),
OnDevicePersonalizationException.ERROR_ISOLATED_SERVICE_FAILED);
@@ -720,15 +738,32 @@
mContext.getSystemService(OnDevicePersonalizationManager.class);
assertNotNull(manager);
var receiver = new ResultReceiver<ExecuteResult>();
- PersistableBundle appParams = new PersistableBundle();
- appParams.putString(
- SampleServiceApi.KEY_OPCODE, SampleServiceApi.OPCODE_SCHEDULE_FEDERATED_JOB);
- appParams.putString(SampleServiceApi.KEY_POPULATION_NAME, "criteo_app_test_task");
+ PersistableBundle appParams = getScheduleFCJobParams(/* useLegacyApi= */ true);
+
manager.execute(
new ComponentName(SERVICE_PACKAGE, SERVICE_CLASS),
appParams,
Executors.newSingleThreadExecutor(),
receiver);
+
+ assertTrue(receiver.getErrorMessage(), receiver.isSuccess());
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_FCP_SCHEDULE_WITH_OUTCOME_RECEIVER_ENABLED)
+ public void testExecuteWithScheduleFederatedJobWithOutcomeReceiver() throws Exception {
+ OnDevicePersonalizationManager manager =
+ mContext.getSystemService(OnDevicePersonalizationManager.class);
+ assertNotNull(manager);
+ var receiver = new ResultReceiver<ExecuteResult>();
+ PersistableBundle appParams = getScheduleFCJobParams(/* useLegacyApi= */ false);
+
+ manager.execute(
+ new ComponentName(SERVICE_PACKAGE, SERVICE_CLASS),
+ appParams,
+ Executors.newSingleThreadExecutor(),
+ receiver);
+
assertTrue(receiver.getErrorMessage(), receiver.isSuccess());
}
@@ -749,4 +784,727 @@
receiver);
assertTrue(receiver.getErrorMessage(), receiver.isSuccess());
}
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_EXECUTE_IN_ISOLATED_SERVICE_API_ENABLED)
+ public void testExecuteInIsolatedServiceThrowsNPEIfExecutorMissing() {
+ OnDevicePersonalizationManager manager =
+ mContext.getSystemService(OnDevicePersonalizationManager.class);
+ assertNotNull(manager);
+ ExecuteInIsolatedServiceRequest request =
+ new ExecuteInIsolatedServiceRequest.Builder(
+ new ComponentName(SERVICE_PACKAGE, SERVICE_CLASS))
+ .setAppParams(PersistableBundle.EMPTY)
+ .build();
+
+ assertThrows(
+ NullPointerException.class,
+ () -> manager.executeInIsolatedService(request, null, new ResultReceiver<>()));
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_EXECUTE_IN_ISOLATED_SERVICE_API_ENABLED)
+ public void testExecuteInIsolatedServiceThrowsNPEIfReceiverMissing() {
+ OnDevicePersonalizationManager manager =
+ mContext.getSystemService(OnDevicePersonalizationManager.class);
+ assertNotNull(manager);
+ ExecuteInIsolatedServiceRequest request =
+ new ExecuteInIsolatedServiceRequest.Builder(
+ new ComponentName(SERVICE_PACKAGE, SERVICE_CLASS))
+ .setAppParams(PersistableBundle.EMPTY)
+ .build();
+
+ assertThrows(
+ NullPointerException.class,
+ () ->
+ manager.executeInIsolatedService(
+ request, Executors.newSingleThreadExecutor(), null));
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_EXECUTE_IN_ISOLATED_SERVICE_API_ENABLED)
+ public void testExecuteInIsolatedServiceThrowsIAEIfPackageNameMissing() {
+ OnDevicePersonalizationManager manager =
+ mContext.getSystemService(OnDevicePersonalizationManager.class);
+ assertNotNull(manager);
+ ExecuteInIsolatedServiceRequest request =
+ new ExecuteInIsolatedServiceRequest.Builder(new ComponentName("", SERVICE_CLASS))
+ .setAppParams(PersistableBundle.EMPTY)
+ .build();
+ assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ manager.executeInIsolatedService(
+ request,
+ Executors.newSingleThreadExecutor(),
+ new ResultReceiver<>()));
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_EXECUTE_IN_ISOLATED_SERVICE_API_ENABLED)
+ public void testExecuteInIsolatedServiceThrowsIAEIfClassNameMissing()
+ throws InterruptedException {
+ OnDevicePersonalizationManager manager =
+ mContext.getSystemService(OnDevicePersonalizationManager.class);
+ assertNotNull(manager);
+ ExecuteInIsolatedServiceRequest request =
+ new ExecuteInIsolatedServiceRequest.Builder(new ComponentName(SERVICE_PACKAGE, ""))
+ .setAppParams(PersistableBundle.EMPTY)
+ .build();
+ assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ manager.executeInIsolatedService(
+ request,
+ Executors.newSingleThreadExecutor(),
+ new ResultReceiver<>()));
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_EXECUTE_IN_ISOLATED_SERVICE_API_ENABLED)
+ public void testExecuteInIsolatedServiceReturnsIllegalStateIfServiceNotEnrolled()
+ throws InterruptedException {
+ OnDevicePersonalizationManager manager =
+ mContext.getSystemService(OnDevicePersonalizationManager.class);
+ assertNotNull(manager);
+ ExecuteInIsolatedServiceRequest request =
+ new ExecuteInIsolatedServiceRequest.Builder(
+ new ComponentName("somepackage", "someclass"))
+ .setAppParams(PersistableBundle.EMPTY)
+ .build();
+ var receiver = new ResultReceiver<ExecuteInIsolatedServiceResponse>();
+
+ manager.executeInIsolatedService(request, Executors.newSingleThreadExecutor(), receiver);
+
+ assertNull(receiver.getResult());
+ assertTrue(receiver.getException() instanceof IllegalStateException);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_EXECUTE_IN_ISOLATED_SERVICE_API_ENABLED)
+ public void testExecuteInIsolatedServiceReturnsNameNotFoundIfServiceNotInstalled()
+ throws InterruptedException {
+ OnDevicePersonalizationManager manager =
+ mContext.getSystemService(OnDevicePersonalizationManager.class);
+ assertNotNull(manager);
+ var receiver = new ResultReceiver<ExecuteInIsolatedServiceResponse>();
+ ExecuteInIsolatedServiceRequest request =
+ new ExecuteInIsolatedServiceRequest.Builder(
+ new ComponentName("com.example.odptargetingapp2", "someclass"))
+ .setAppParams(PersistableBundle.EMPTY)
+ .build();
+
+ manager.executeInIsolatedService(request, Executors.newSingleThreadExecutor(), receiver);
+
+ assertNull(receiver.getResult());
+ assertThat(receiver.getException()).isInstanceOf(OnDevicePersonalizationException.class);
+ OnDevicePersonalizationException exception =
+ (OnDevicePersonalizationException) receiver.getException();
+ assertThat(exception.getErrorCode())
+ .isEqualTo(
+ OnDevicePersonalizationException
+ .ERROR_ISOLATED_SERVICE_MANIFEST_PARSING_FAILED);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_EXECUTE_IN_ISOLATED_SERVICE_API_ENABLED)
+ public void testExecuteInIsolatedServiceReturnsManifestParsingErrorIfServiceClassNotFound()
+ throws InterruptedException {
+ OnDevicePersonalizationManager manager =
+ mContext.getSystemService(OnDevicePersonalizationManager.class);
+ assertNotNull(manager);
+ var receiver = new ResultReceiver<ExecuteInIsolatedServiceResponse>();
+ ExecuteInIsolatedServiceRequest request =
+ new ExecuteInIsolatedServiceRequest.Builder(
+ new ComponentName(SERVICE_PACKAGE, "someclass"))
+ .setAppParams(PersistableBundle.EMPTY)
+ .build();
+
+ manager.executeInIsolatedService(request, Executors.newSingleThreadExecutor(), receiver);
+
+ assertNull(receiver.getResult());
+ assertThat(receiver.getException()).isInstanceOf(OnDevicePersonalizationException.class);
+ OnDevicePersonalizationException exception =
+ (OnDevicePersonalizationException) receiver.getException();
+ assertThat(exception.getErrorCode())
+ .isEqualTo(
+ OnDevicePersonalizationException
+ .ERROR_ISOLATED_SERVICE_MANIFEST_PARSING_FAILED);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_EXECUTE_IN_ISOLATED_SERVICE_API_ENABLED)
+ public void testExecuteInIsolatedServiceNoOp() throws InterruptedException {
+ OnDevicePersonalizationManager manager =
+ mContext.getSystemService(OnDevicePersonalizationManager.class);
+ assertNotNull(manager);
+ var receiver = new ResultReceiver<ExecuteInIsolatedServiceResponse>();
+ ExecuteInIsolatedServiceRequest request =
+ new ExecuteInIsolatedServiceRequest.Builder(
+ new ComponentName(SERVICE_PACKAGE, SERVICE_CLASS))
+ .setAppParams(PersistableBundle.EMPTY)
+ .build();
+
+ manager.executeInIsolatedService(request, Executors.newSingleThreadExecutor(), receiver);
+
+ assertTrue(receiver.getErrorMessage(), receiver.isSuccess());
+ SurfacePackageToken token = receiver.getResult().getSurfacePackageToken();
+ assertNull(token);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_EXECUTE_IN_ISOLATED_SERVICE_API_ENABLED)
+ public void testExecuteInIsolatedServiceWithRenderAndLogging() throws InterruptedException {
+ OnDevicePersonalizationManager manager =
+ mContext.getSystemService(OnDevicePersonalizationManager.class);
+ assertNotNull(manager);
+ var receiver = new ResultReceiver<ExecuteInIsolatedServiceResponse>();
+ PersistableBundle appParams = new PersistableBundle();
+ appParams.putString(SampleServiceApi.KEY_OPCODE, SampleServiceApi.OPCODE_RENDER_AND_LOG);
+ appParams.putString(SampleServiceApi.KEY_RENDERING_CONFIG_IDS, "id1");
+ PersistableBundle logData = new PersistableBundle();
+ logData.putString("id", "a1");
+ logData.putDouble("val", 5.0);
+ appParams.putPersistableBundle(SampleServiceApi.KEY_LOG_DATA, logData);
+ ExecuteInIsolatedServiceRequest request =
+ new ExecuteInIsolatedServiceRequest.Builder(
+ new ComponentName(SERVICE_PACKAGE, SERVICE_CLASS))
+ .setAppParams(appParams)
+ .build();
+
+ manager.executeInIsolatedService(request, Executors.newSingleThreadExecutor(), receiver);
+
+ assertTrue(receiver.getErrorMessage(), receiver.isSuccess());
+ SurfacePackageToken token = receiver.getResult().getSurfacePackageToken();
+ assertNotNull(token);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_EXECUTE_IN_ISOLATED_SERVICE_API_ENABLED)
+ public void testExecuteInIsolatedServiceWithRender() throws InterruptedException {
+ OnDevicePersonalizationManager manager =
+ mContext.getSystemService(OnDevicePersonalizationManager.class);
+ assertNotNull(manager);
+ var receiver = new ResultReceiver<ExecuteInIsolatedServiceResponse>();
+ PersistableBundle appParams = new PersistableBundle();
+ appParams.putString(SampleServiceApi.KEY_OPCODE, SampleServiceApi.OPCODE_RENDER_AND_LOG);
+ appParams.putString(SampleServiceApi.KEY_RENDERING_CONFIG_IDS, "id1");
+ ExecuteInIsolatedServiceRequest request =
+ new ExecuteInIsolatedServiceRequest.Builder(
+ new ComponentName(SERVICE_PACKAGE, SERVICE_CLASS))
+ .setAppParams(appParams)
+ .build();
+
+ manager.executeInIsolatedService(request, Executors.newSingleThreadExecutor(), receiver);
+
+ assertTrue(receiver.getErrorMessage(), receiver.isSuccess());
+ SurfacePackageToken token = receiver.getResult().getSurfacePackageToken();
+ assertNotNull(token);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_EXECUTE_IN_ISOLATED_SERVICE_API_ENABLED)
+ public void testExecuteInIsolatedServiceReadRemoteData() throws InterruptedException {
+ OnDevicePersonalizationManager manager =
+ mContext.getSystemService(OnDevicePersonalizationManager.class);
+ assertNotNull(manager);
+ var receiver = new ResultReceiver<ExecuteInIsolatedServiceResponse>();
+ PersistableBundle appParams = new PersistableBundle();
+ appParams.putString(SampleServiceApi.KEY_OPCODE, SampleServiceApi.OPCODE_READ_REMOTE_DATA);
+ ExecuteInIsolatedServiceRequest request =
+ new ExecuteInIsolatedServiceRequest.Builder(
+ new ComponentName(SERVICE_PACKAGE, SERVICE_CLASS))
+ .setAppParams(appParams)
+ .build();
+
+ manager.executeInIsolatedService(request, Executors.newSingleThreadExecutor(), receiver);
+
+ assertTrue(receiver.getErrorMessage(), receiver.isSuccess());
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_EXECUTE_IN_ISOLATED_SERVICE_API_ENABLED)
+ public void testExecuteInIsolatedServiceReadUserData() throws InterruptedException {
+ OnDevicePersonalizationManager manager =
+ mContext.getSystemService(OnDevicePersonalizationManager.class);
+ assertNotNull(manager);
+ var receiver = new ResultReceiver<ExecuteInIsolatedServiceResponse>();
+ PersistableBundle appParams = new PersistableBundle();
+ appParams.putString(SampleServiceApi.KEY_OPCODE, SampleServiceApi.OPCODE_READ_USER_DATA);
+ ExecuteInIsolatedServiceRequest request =
+ new ExecuteInIsolatedServiceRequest.Builder(
+ new ComponentName(SERVICE_PACKAGE, SERVICE_CLASS))
+ .setAppParams(appParams)
+ .build();
+
+ manager.executeInIsolatedService(request, Executors.newSingleThreadExecutor(), receiver);
+
+ assertTrue(receiver.getErrorMessage(), receiver.isSuccess());
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_EXECUTE_IN_ISOLATED_SERVICE_API_ENABLED)
+ public void testExecuteInIsolatedServiceWithLogging() throws InterruptedException {
+ OnDevicePersonalizationManager manager =
+ mContext.getSystemService(OnDevicePersonalizationManager.class);
+ assertNotNull(manager);
+ var receiver = new ResultReceiver<ExecuteInIsolatedServiceResponse>();
+ PersistableBundle appParams = new PersistableBundle();
+ appParams.putString(SampleServiceApi.KEY_OPCODE, SampleServiceApi.OPCODE_RENDER_AND_LOG);
+ PersistableBundle logData = new PersistableBundle();
+ logData.putString("id", "a1");
+ logData.putDouble("val", 5.0);
+ appParams.putPersistableBundle(SampleServiceApi.KEY_LOG_DATA, logData);
+ ExecuteInIsolatedServiceRequest request =
+ new ExecuteInIsolatedServiceRequest.Builder(
+ new ComponentName(SERVICE_PACKAGE, SERVICE_CLASS))
+ .setAppParams(appParams)
+ .build();
+
+ manager.executeInIsolatedService(request, Executors.newSingleThreadExecutor(), receiver);
+ assertTrue(receiver.getErrorMessage(), receiver.isSuccess());
+ SurfacePackageToken token = receiver.getResult().getSurfacePackageToken();
+ assertNull(token);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_EXECUTE_IN_ISOLATED_SERVICE_API_ENABLED)
+ public void testExecuteInIsolatedServiceReadLog() throws InterruptedException {
+ OnDevicePersonalizationManager manager =
+ mContext.getSystemService(OnDevicePersonalizationManager.class);
+ assertNotNull(manager);
+ final long now = System.currentTimeMillis();
+
+ {
+ var receiver = new ResultReceiver<ExecuteInIsolatedServiceResponse>();
+ PersistableBundle appParams = new PersistableBundle();
+ appParams.putString(
+ SampleServiceApi.KEY_OPCODE, SampleServiceApi.OPCODE_RENDER_AND_LOG);
+ PersistableBundle logData = new PersistableBundle();
+ logData.putLong(SampleServiceApi.KEY_EXPECTED_LOG_DATA_KEY, now);
+ appParams.putPersistableBundle(SampleServiceApi.KEY_LOG_DATA, logData);
+ ExecuteInIsolatedServiceRequest request =
+ new ExecuteInIsolatedServiceRequest.Builder(
+ new ComponentName(SERVICE_PACKAGE, SERVICE_CLASS))
+ .setAppParams(appParams)
+ .build();
+
+ manager.executeInIsolatedService(
+ request, Executors.newSingleThreadExecutor(), receiver);
+ assertTrue(receiver.getErrorMessage(), receiver.isSuccess());
+ }
+
+ // Add delay between writing and read from db to reduce flakiness.
+ Thread.sleep(DELAY_MILLIS);
+
+ {
+ var receiver = new ResultReceiver<ExecuteInIsolatedServiceResponse>();
+ PersistableBundle appParams = new PersistableBundle();
+ appParams.putString(SampleServiceApi.KEY_OPCODE, SampleServiceApi.OPCODE_READ_LOG);
+ appParams.putLong(SampleServiceApi.KEY_EXPECTED_LOG_DATA_VALUE, now);
+ ExecuteInIsolatedServiceRequest request =
+ new ExecuteInIsolatedServiceRequest.Builder(
+ new ComponentName(SERVICE_PACKAGE, SERVICE_CLASS))
+ .setAppParams(appParams)
+ .build();
+
+ manager.executeInIsolatedService(
+ request, Executors.newSingleThreadExecutor(), receiver);
+ assertTrue(receiver.getErrorMessage(), receiver.isSuccess());
+ }
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_EXECUTE_IN_ISOLATED_SERVICE_API_ENABLED)
+ public void testExecuteInIsolatedServiceReturnsErrorIfServiceThrows()
+ throws InterruptedException {
+ OnDevicePersonalizationManager manager =
+ mContext.getSystemService(OnDevicePersonalizationManager.class);
+ assertNotNull(manager);
+ var receiver = new ResultReceiver<ExecuteInIsolatedServiceResponse>();
+ PersistableBundle appParams = new PersistableBundle();
+ appParams.putString(SampleServiceApi.KEY_OPCODE, SampleServiceApi.OPCODE_THROW_EXCEPTION);
+ appParams.putString(SampleServiceApi.KEY_EXCEPTION_CLASS, "java.lang.NullPointerException");
+ ExecuteInIsolatedServiceRequest request =
+ new ExecuteInIsolatedServiceRequest.Builder(
+ new ComponentName(SERVICE_PACKAGE, SERVICE_CLASS))
+ .setAppParams(appParams)
+ .build();
+
+ manager.executeInIsolatedService(request, Executors.newSingleThreadExecutor(), receiver);
+ assertTrue(receiver.isError());
+ assertNull(receiver.getResult());
+ assertTrue(receiver.getException() instanceof OnDevicePersonalizationException);
+ assertEquals(
+ ((OnDevicePersonalizationException) receiver.getException()).getErrorCode(),
+ OnDevicePersonalizationException.ERROR_ISOLATED_SERVICE_FAILED);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_EXECUTE_IN_ISOLATED_SERVICE_API_ENABLED)
+ public void testExecuteInIsolatedServiceReturnsErrorIfServiceReturnsError()
+ throws InterruptedException {
+ OnDevicePersonalizationManager manager =
+ mContext.getSystemService(OnDevicePersonalizationManager.class);
+ assertNotNull(manager);
+ var receiver = new ResultReceiver<ExecuteInIsolatedServiceResponse>();
+ PersistableBundle appParams = new PersistableBundle();
+ appParams.putString(
+ SampleServiceApi.KEY_OPCODE, SampleServiceApi.OPCODE_FAIL_WITH_ERROR_CODE);
+ appParams.putInt(SampleServiceApi.KEY_ERROR_CODE, 10);
+ ExecuteInIsolatedServiceRequest request =
+ new ExecuteInIsolatedServiceRequest.Builder(
+ new ComponentName(SERVICE_PACKAGE, SERVICE_CLASS))
+ .setAppParams(appParams)
+ .build();
+
+ manager.executeInIsolatedService(request, Executors.newSingleThreadExecutor(), receiver);
+ assertTrue(receiver.isError());
+ assertNull(receiver.getResult());
+ assertTrue(receiver.getException() instanceof OnDevicePersonalizationException);
+ assertEquals(
+ ((OnDevicePersonalizationException) receiver.getException()).getErrorCode(),
+ OnDevicePersonalizationException.ERROR_ISOLATED_SERVICE_FAILED);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_EXECUTE_IN_ISOLATED_SERVICE_API_ENABLED)
+ public void testExecuteInIsolatedServiceWriteAndReadLocalData() throws InterruptedException {
+ final String tableKey = "testKey_" + System.currentTimeMillis();
+ OnDevicePersonalizationManager manager =
+ mContext.getSystemService(OnDevicePersonalizationManager.class);
+ assertNotNull(manager);
+
+ // Write 1 byte.
+ {
+ var receiver = new ResultReceiver<ExecuteInIsolatedServiceResponse>();
+ PersistableBundle appParams = new PersistableBundle();
+ appParams.putString(
+ SampleServiceApi.KEY_OPCODE, SampleServiceApi.OPCODE_WRITE_LOCAL_DATA);
+ appParams.putString(SampleServiceApi.KEY_TABLE_KEY, tableKey);
+ appParams.putString(
+ SampleServiceApi.KEY_BASE64_VALUE, Base64.encodeToString(new byte[] {'A'}, 0));
+ ExecuteInIsolatedServiceRequest request =
+ new ExecuteInIsolatedServiceRequest.Builder(
+ new ComponentName(SERVICE_PACKAGE, SERVICE_CLASS))
+ .setAppParams(appParams)
+ .build();
+
+ manager.executeInIsolatedService(
+ request, Executors.newSingleThreadExecutor(), receiver);
+
+ assertTrue(receiver.getErrorMessage(), receiver.isSuccess());
+ }
+
+ // Add delay between writing and read from db to reduce flakiness.
+ Thread.sleep(DELAY_MILLIS);
+
+ // Read and check whether value matches written value.
+ {
+ var receiver = new ResultReceiver<ExecuteInIsolatedServiceResponse>();
+ PersistableBundle appParams = new PersistableBundle();
+ appParams.putString(
+ SampleServiceApi.KEY_OPCODE, SampleServiceApi.OPCODE_READ_LOCAL_DATA);
+ appParams.putString(SampleServiceApi.KEY_TABLE_KEY, tableKey);
+ appParams.putString(
+ SampleServiceApi.KEY_BASE64_VALUE, Base64.encodeToString(new byte[] {'A'}, 0));
+ ExecuteInIsolatedServiceRequest request =
+ new ExecuteInIsolatedServiceRequest.Builder(
+ new ComponentName(SERVICE_PACKAGE, SERVICE_CLASS))
+ .setAppParams(appParams)
+ .build();
+
+ manager.executeInIsolatedService(
+ request, Executors.newSingleThreadExecutor(), receiver);
+
+ assertTrue(receiver.getErrorMessage(), receiver.isSuccess());
+ }
+
+ // Add delay between writing and read from db to reduce flakiness.
+ Thread.sleep(DELAY_MILLIS);
+
+ // Remove.
+ {
+ var receiver = new ResultReceiver<ExecuteInIsolatedServiceResponse>();
+ PersistableBundle appParams = new PersistableBundle();
+ appParams.putString(
+ SampleServiceApi.KEY_OPCODE, SampleServiceApi.OPCODE_WRITE_LOCAL_DATA);
+ appParams.putString(SampleServiceApi.KEY_TABLE_KEY, tableKey);
+ ExecuteInIsolatedServiceRequest request =
+ new ExecuteInIsolatedServiceRequest.Builder(
+ new ComponentName(SERVICE_PACKAGE, SERVICE_CLASS))
+ .setAppParams(appParams)
+ .build();
+
+ manager.executeInIsolatedService(
+ request, Executors.newSingleThreadExecutor(), receiver);
+
+ assertTrue(receiver.getErrorMessage(), receiver.isSuccess());
+ }
+
+ // Add delay between writing and read from db to reduce flakiness.
+ Thread.sleep(DELAY_MILLIS);
+
+ // Read and check whether value was removed.
+ {
+ var receiver = new ResultReceiver<ExecuteInIsolatedServiceResponse>();
+ PersistableBundle appParams = new PersistableBundle();
+ appParams.putString(
+ SampleServiceApi.KEY_OPCODE, SampleServiceApi.OPCODE_READ_LOCAL_DATA);
+ appParams.putString(SampleServiceApi.KEY_TABLE_KEY, tableKey);
+ ExecuteInIsolatedServiceRequest request =
+ new ExecuteInIsolatedServiceRequest.Builder(
+ new ComponentName(SERVICE_PACKAGE, SERVICE_CLASS))
+ .setAppParams(appParams)
+ .build();
+
+ manager.executeInIsolatedService(
+ request, Executors.newSingleThreadExecutor(), receiver);
+
+ assertTrue(receiver.getErrorMessage(), receiver.isSuccess());
+ }
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_EXECUTE_IN_ISOLATED_SERVICE_API_ENABLED)
+ public void testExecuteInIsolatedServiceWriteAndReadLargeLocalData()
+ throws InterruptedException {
+ final String tableKey = "testKey_" + System.currentTimeMillis();
+ OnDevicePersonalizationManager manager =
+ mContext.getSystemService(OnDevicePersonalizationManager.class);
+ assertNotNull(manager);
+
+ // Write 10MB.
+ {
+ var receiver = new ResultReceiver<ExecuteInIsolatedServiceResponse>();
+ PersistableBundle appParams = new PersistableBundle();
+ appParams.putString(
+ SampleServiceApi.KEY_OPCODE, SampleServiceApi.OPCODE_WRITE_LOCAL_DATA);
+ appParams.putString(SampleServiceApi.KEY_TABLE_KEY, tableKey);
+ appParams.putString(
+ SampleServiceApi.KEY_BASE64_VALUE, Base64.encodeToString(new byte[] {'A'}, 0));
+ appParams.putInt(SampleServiceApi.KEY_TABLE_VALUE_REPEAT_COUNT, LARGE_BLOB_SIZE);
+ ExecuteInIsolatedServiceRequest request =
+ new ExecuteInIsolatedServiceRequest.Builder(
+ new ComponentName(SERVICE_PACKAGE, SERVICE_CLASS))
+ .setAppParams(appParams)
+ .build();
+
+ manager.executeInIsolatedService(
+ request, Executors.newSingleThreadExecutor(), receiver);
+
+ assertTrue(receiver.getErrorMessage(), receiver.isSuccess());
+ }
+
+ // Add delay between writing and read from db to reduce flakiness.
+ Thread.sleep(DELAY_MILLIS);
+
+ // Read and check whether value matches written value.
+ {
+ var receiver = new ResultReceiver<ExecuteInIsolatedServiceResponse>();
+ PersistableBundle appParams = new PersistableBundle();
+ appParams.putString(
+ SampleServiceApi.KEY_OPCODE, SampleServiceApi.OPCODE_READ_LOCAL_DATA);
+ appParams.putString(SampleServiceApi.KEY_TABLE_KEY, tableKey);
+ appParams.putString(
+ SampleServiceApi.KEY_BASE64_VALUE, Base64.encodeToString(new byte[] {'A'}, 0));
+ appParams.putInt(SampleServiceApi.KEY_TABLE_VALUE_REPEAT_COUNT, LARGE_BLOB_SIZE);
+ ExecuteInIsolatedServiceRequest request =
+ new ExecuteInIsolatedServiceRequest.Builder(
+ new ComponentName(SERVICE_PACKAGE, SERVICE_CLASS))
+ .setAppParams(appParams)
+ .build();
+
+ manager.executeInIsolatedService(
+ request, Executors.newSingleThreadExecutor(), receiver);
+
+ assertTrue(receiver.getErrorMessage(), receiver.isSuccess());
+ }
+
+ // Add delay between writing and read from db to reduce flakiness.
+ Thread.sleep(DELAY_MILLIS);
+
+ // Remove.
+ {
+ var receiver = new ResultReceiver<ExecuteInIsolatedServiceResponse>();
+ PersistableBundle appParams = new PersistableBundle();
+ appParams.putString(
+ SampleServiceApi.KEY_OPCODE, SampleServiceApi.OPCODE_WRITE_LOCAL_DATA);
+ appParams.putString(SampleServiceApi.KEY_TABLE_KEY, tableKey);
+ ExecuteInIsolatedServiceRequest request =
+ new ExecuteInIsolatedServiceRequest.Builder(
+ new ComponentName(SERVICE_PACKAGE, SERVICE_CLASS))
+ .setAppParams(appParams)
+ .build();
+
+ manager.executeInIsolatedService(
+ request, Executors.newSingleThreadExecutor(), receiver);
+
+ assertTrue(receiver.getErrorMessage(), receiver.isSuccess());
+ }
+
+ // Add delay between writing and read from db to reduce flakiness.
+ Thread.sleep(DELAY_MILLIS);
+
+ // Read and check whether value was removed.
+ {
+ var receiver = new ResultReceiver<ExecuteInIsolatedServiceResponse>();
+ PersistableBundle appParams = new PersistableBundle();
+ appParams.putString(
+ SampleServiceApi.KEY_OPCODE, SampleServiceApi.OPCODE_READ_LOCAL_DATA);
+ appParams.putString(SampleServiceApi.KEY_TABLE_KEY, tableKey);
+ ExecuteInIsolatedServiceRequest request =
+ new ExecuteInIsolatedServiceRequest.Builder(
+ new ComponentName(SERVICE_PACKAGE, SERVICE_CLASS))
+ .setAppParams(appParams)
+ .build();
+
+ manager.executeInIsolatedService(
+ request, Executors.newSingleThreadExecutor(), receiver);
+
+ assertTrue(receiver.getErrorMessage(), receiver.isSuccess());
+ }
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_EXECUTE_IN_ISOLATED_SERVICE_API_ENABLED)
+ public void testExecuteInIsolatedServiceSendLargeBlob() throws InterruptedException {
+ OnDevicePersonalizationManager manager =
+ mContext.getSystemService(OnDevicePersonalizationManager.class);
+ assertNotNull(manager);
+ var receiver = new ResultReceiver<ExecuteInIsolatedServiceResponse>();
+ PersistableBundle appParams = new PersistableBundle();
+ appParams.putString(
+ SampleServiceApi.KEY_OPCODE, SampleServiceApi.OPCODE_CHECK_VALUE_LENGTH);
+ byte[] buffer = new byte[LARGE_BLOB_SIZE];
+ for (int i = 0; i < LARGE_BLOB_SIZE; ++i) {
+ buffer[i] = 'A';
+ }
+ appParams.putString(SampleServiceApi.KEY_BASE64_VALUE, Base64.encodeToString(buffer, 0));
+ appParams.putInt(SampleServiceApi.KEY_VALUE_LENGTH, LARGE_BLOB_SIZE);
+ ExecuteInIsolatedServiceRequest request =
+ new ExecuteInIsolatedServiceRequest.Builder(
+ new ComponentName(SERVICE_PACKAGE, SERVICE_CLASS))
+ .setAppParams(appParams)
+ .build();
+
+ manager.executeInIsolatedService(request, Executors.newSingleThreadExecutor(), receiver);
+ assertTrue(receiver.getErrorMessage(), receiver.isSuccess());
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_EXECUTE_IN_ISOLATED_SERVICE_API_ENABLED)
+ public void testExecuteInIsolatedServiceWithModelInference() throws Exception {
+ final String tableKey = "model_" + System.currentTimeMillis();
+ OnDevicePersonalizationManager manager =
+ mContext.getSystemService(OnDevicePersonalizationManager.class);
+ assertNotNull(manager);
+ Uri modelUri =
+ Uri.parse(
+ "android.resource://"
+ + ApplicationProvider.getApplicationContext().getPackageName()
+ + "/raw/model");
+ Context context = ApplicationProvider.getApplicationContext();
+ InputStream in = context.getContentResolver().openInputStream(modelUri);
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ byte[] buf = new byte[4096];
+ int bytesRead;
+ while ((bytesRead = in.read(buf)) != -1) {
+ outputStream.write(buf, 0, bytesRead);
+ }
+ byte[] buffer = outputStream.toByteArray();
+ outputStream.close();
+ // Write model to local data.
+ {
+ var receiver = new ResultReceiver<ExecuteInIsolatedServiceResponse>();
+ PersistableBundle appParams = new PersistableBundle();
+ appParams.putString(
+ SampleServiceApi.KEY_OPCODE, SampleServiceApi.OPCODE_WRITE_LOCAL_DATA);
+ appParams.putString(SampleServiceApi.KEY_TABLE_KEY, tableKey);
+ appParams.putString(
+ SampleServiceApi.KEY_BASE64_VALUE, Base64.encodeToString(buffer, 0));
+ ExecuteInIsolatedServiceRequest request =
+ new ExecuteInIsolatedServiceRequest.Builder(
+ new ComponentName(SERVICE_PACKAGE, SERVICE_CLASS))
+ .setAppParams(appParams)
+ .build();
+
+ manager.executeInIsolatedService(
+ request, Executors.newSingleThreadExecutor(), receiver);
+
+ assertTrue(receiver.getErrorMessage(), receiver.isSuccess());
+ }
+
+ // Add delay between writing and read from db to reduce flakiness.
+ Thread.sleep(DELAY_MILLIS);
+
+ // Run model inference
+ {
+ var receiver = new ResultReceiver<ExecuteInIsolatedServiceResponse>();
+ PersistableBundle appParams = new PersistableBundle();
+ appParams.putString(
+ SampleServiceApi.KEY_OPCODE, SampleServiceApi.OPCODE_RUN_MODEL_INFERENCE);
+ appParams.putString(SampleServiceApi.KEY_TABLE_KEY, tableKey);
+ appParams.putDouble(SampleServiceApi.KEY_INFERENCE_RESULT, 0.5922908);
+ ExecuteInIsolatedServiceRequest request =
+ new ExecuteInIsolatedServiceRequest.Builder(
+ new ComponentName(SERVICE_PACKAGE, SERVICE_CLASS))
+ .setAppParams(appParams)
+ .build();
+
+ manager.executeInIsolatedService(
+ request, Executors.newSingleThreadExecutor(), receiver);
+
+ assertTrue(receiver.getErrorMessage(), receiver.isSuccess());
+ }
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_EXECUTE_IN_ISOLATED_SERVICE_API_ENABLED)
+ public void testExecuteInIsolatedServiceWithScheduleFederatedJob() throws Exception {
+ OnDevicePersonalizationManager manager =
+ mContext.getSystemService(OnDevicePersonalizationManager.class);
+ assertNotNull(manager);
+ var receiver = new ResultReceiver<ExecuteInIsolatedServiceResponse>();
+ ExecuteInIsolatedServiceRequest request =
+ new ExecuteInIsolatedServiceRequest.Builder(
+ new ComponentName(SERVICE_PACKAGE, SERVICE_CLASS))
+ .setAppParams(getScheduleFCJobParams(/* useLegacyApi= */ true))
+ .build();
+
+ manager.executeInIsolatedService(request, Executors.newSingleThreadExecutor(), receiver);
+ assertTrue(receiver.getErrorMessage(), receiver.isSuccess());
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_EXECUTE_IN_ISOLATED_SERVICE_API_ENABLED)
+ public void testExecuteInIsolatedServiceWithCancelFederatedJob() throws Exception {
+ OnDevicePersonalizationManager manager =
+ mContext.getSystemService(OnDevicePersonalizationManager.class);
+ assertNotNull(manager);
+ var receiver = new ResultReceiver<ExecuteInIsolatedServiceResponse>();
+ PersistableBundle appParams = new PersistableBundle();
+ appParams.putString(
+ SampleServiceApi.KEY_OPCODE, SampleServiceApi.OPCODE_CANCEL_FEDERATED_JOB);
+ appParams.putString(SampleServiceApi.KEY_POPULATION_NAME, "criteo_app_test_task");
+ ExecuteInIsolatedServiceRequest request =
+ new ExecuteInIsolatedServiceRequest.Builder(
+ new ComponentName(SERVICE_PACKAGE, SERVICE_CLASS))
+ .setAppParams(appParams)
+ .build();
+
+ manager.executeInIsolatedService(request, Executors.newSingleThreadExecutor(), receiver);
+ assertTrue(receiver.getErrorMessage(), receiver.isSuccess());
+ }
+
+ private static PersistableBundle getScheduleFCJobParams(boolean useLegacyApi) {
+ PersistableBundle appParams = new PersistableBundle();
+ appParams.putString(
+ SampleServiceApi.KEY_OPCODE,
+ useLegacyApi
+ ? SampleServiceApi.OPCODE_SCHEDULE_FEDERATED_JOB
+ : SampleServiceApi.OPCODE_SCHEDULE_FEDERATED_JOB_V2);
+ appParams.putString(SampleServiceApi.KEY_POPULATION_NAME, TEST_POPULATION_NAME);
+ return appParams;
+ }
}
diff --git a/tests/cts/endtoend/src/com/android/ondevicepersonalization/cts/e2e/DataClassesTest.java b/tests/cts/endtoend/src/com/android/ondevicepersonalization/cts/e2e/DataClassesTest.java
index 323f478..89a8ae6 100644
--- a/tests/cts/endtoend/src/com/android/ondevicepersonalization/cts/e2e/DataClassesTest.java
+++ b/tests/cts/endtoend/src/com/android/ondevicepersonalization/cts/e2e/DataClassesTest.java
@@ -16,21 +16,28 @@
package com.android.ondevicepersonalization.cts.e2e;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
+import android.adservices.ondevicepersonalization.AppInfo;
import android.adservices.ondevicepersonalization.DownloadCompletedOutput;
import android.adservices.ondevicepersonalization.EventLogRecord;
import android.adservices.ondevicepersonalization.EventOutput;
+import android.adservices.ondevicepersonalization.ExecuteInIsolatedServiceRequest;
+import android.adservices.ondevicepersonalization.ExecuteInIsolatedServiceResponse;
import android.adservices.ondevicepersonalization.ExecuteOutput;
import android.adservices.ondevicepersonalization.FederatedComputeInput;
+import android.adservices.ondevicepersonalization.FederatedComputeScheduleRequest;
import android.adservices.ondevicepersonalization.FederatedComputeScheduler;
import android.adservices.ondevicepersonalization.IsolatedServiceException;
import android.adservices.ondevicepersonalization.MeasurementWebTriggerEventParams;
import android.adservices.ondevicepersonalization.RenderOutput;
import android.adservices.ondevicepersonalization.RenderingConfig;
import android.adservices.ondevicepersonalization.RequestLogRecord;
+import android.adservices.ondevicepersonalization.SurfacePackageToken;
import android.adservices.ondevicepersonalization.TrainingExampleRecord;
import android.adservices.ondevicepersonalization.TrainingExamplesOutput;
import android.adservices.ondevicepersonalization.TrainingInterval;
@@ -39,10 +46,17 @@
import android.content.ContentValues;
import android.net.Uri;
import android.os.PersistableBundle;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import com.android.adservices.ondevicepersonalization.flags.Flags;
+import com.android.ondevicepersonalization.testing.sampleserviceapi.SampleServiceApi;
+
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -56,6 +70,14 @@
@SmallTest
@RunWith(AndroidJUnit4.class)
public class DataClassesTest {
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
+ private static final String SERVICE_PACKAGE =
+ "com.android.ondevicepersonalization.testing.sampleservice";
+ private static final String SERVICE_CLASS =
+ "com.android.ondevicepersonalization.testing.sampleservice.SampleService";
+
/**
* Test builder and getters for ExecuteOutput.
*/
@@ -65,17 +87,16 @@
row.put("a", 5);
ExecuteOutput data =
new ExecuteOutput.Builder()
- .setRequestLogRecord(new RequestLogRecord.Builder().addRow(row).build())
- .setRenderingConfig(new RenderingConfig.Builder().addKey("abc").build())
- .addEventLogRecord(new EventLogRecord.Builder().setType(1).build())
- .setOutputData(new byte[]{1})
- .build();
+ .setRequestLogRecord(new RequestLogRecord.Builder().addRow(row).build())
+ .setRenderingConfig(new RenderingConfig.Builder().addKey("abc").build())
+ .addEventLogRecord(new EventLogRecord.Builder().setType(1).build())
+ .build();
assertEquals(
5, data.getRequestLogRecord().getRows().get(0).getAsInteger("a").intValue());
assertEquals("abc", data.getRenderingConfig().getKeys().get(0));
assertEquals(1, data.getEventLogRecords().get(0).getType());
- assertArrayEquals(new byte[]{1}, data.getOutputData());
+ assertThat(data.getOutputData()).isNull();
}
/**
@@ -179,6 +200,29 @@
params.getTrainingInterval().getSchedulingMode());
}
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_FCP_SCHEDULE_WITH_OUTCOME_RECEIVER_ENABLED)
+ public void testFederatedComputeSchedulerRequest() {
+ // Test for Data classes associated with FederatedComputeScheduler's schedule API.
+ String testPopulation = "testPopulation";
+ Duration testInterval = Duration.ofSeconds(5);
+ int testSchedulingMode = TrainingInterval.SCHEDULING_MODE_RECURRENT;
+ TrainingInterval testData =
+ new TrainingInterval.Builder()
+ .setSchedulingMode(testSchedulingMode)
+ .setMinimumInterval(testInterval)
+ .build();
+
+ FederatedComputeScheduler.Params params = new FederatedComputeScheduler.Params(testData);
+ FederatedComputeScheduleRequest request =
+ new FederatedComputeScheduleRequest(params, testPopulation);
+
+ assertEquals(testPopulation, request.getPopulationName());
+ assertEquals(testInterval, request.getParams().getTrainingInterval().getMinimumInterval());
+ assertEquals(
+ testSchedulingMode, request.getParams().getTrainingInterval().getSchedulingMode());
+ }
+
/** Test for RequestLogRecord class. */
@Test
public void testRequestLogRecord() {
@@ -297,7 +341,106 @@
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_DATA_CLASS_MISSING_CTORS_AND_GETTERS_ENABLED)
public void testIsolatedServiceException() {
- assertEquals(42, new IsolatedServiceException(42).getErrorCode());
+ IsolatedServiceException e = new IsolatedServiceException(42);
+ assertEquals(42, e.getErrorCode());
+
+ e = new IsolatedServiceException(42, new NullPointerException());
+ assertEquals(42, e.getErrorCode());
+ assertTrue(e.getCause() instanceof NullPointerException);
+
+ e = new IsolatedServiceException(42, "errr", new NullPointerException());
+ assertEquals(42, e.getErrorCode());
+ assertEquals("errr", e.getMessage());
+ assertTrue(e.getCause() instanceof NullPointerException);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DATA_CLASS_MISSING_CTORS_AND_GETTERS_ENABLED)
+ public void testAppInfo() {
+ AppInfo appInfo = new AppInfo(true);
+ assertThat(appInfo.isInstalled()).isTrue();
+
+ appInfo = new AppInfo(false);
+ assertThat(appInfo.isInstalled()).isFalse();
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_EXECUTE_IN_ISOLATED_SERVICE_API_ENABLED)
+ public void testExecuteInIsolatedServiceRequest() {
+ ComponentName service = new ComponentName(SERVICE_PACKAGE, SERVICE_CLASS);
+ PersistableBundle appParams = new PersistableBundle();
+ appParams.putString(SampleServiceApi.KEY_OPCODE, SampleServiceApi.OPCODE_RENDER_AND_LOG);
+ ExecuteInIsolatedServiceRequest request =
+ new ExecuteInIsolatedServiceRequest.Builder(service)
+ .setAppParams(appParams)
+ .setOutputSpec(
+ ExecuteInIsolatedServiceRequest.OutputSpec.buildBestValueSpec(100))
+ .build();
+
+ assertThat(request.getService()).isEqualTo(service);
+ assertThat(request.getAppParams()).isEqualTo(appParams);
+ assertThat(request.getOutputSpec().getMaxIntValue()).isEqualTo(100);
+ assertThat(request.getOutputSpec().getOutputType())
+ .isEqualTo(ExecuteInIsolatedServiceRequest.OutputSpec.OUTPUT_TYPE_BEST_VALUE);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_EXECUTE_IN_ISOLATED_SERVICE_API_ENABLED)
+ public void testExecuteInIsolatedServiceRequest_nullOutputSpec() {
+ ComponentName service = new ComponentName(SERVICE_PACKAGE, SERVICE_CLASS);
+ PersistableBundle appParams = new PersistableBundle();
+ appParams.putString(SampleServiceApi.KEY_OPCODE, SampleServiceApi.OPCODE_RENDER_AND_LOG);
+ ExecuteInIsolatedServiceRequest request =
+ new ExecuteInIsolatedServiceRequest.Builder(service)
+ .setAppParams(appParams)
+ .build();
+
+ assertThat(request.getService()).isEqualTo(service);
+ assertThat(request.getAppParams()).isEqualTo(appParams);
+ assertThat(request.getOutputSpec().getMaxIntValue()).isEqualTo(-1);
+ assertThat(request.getOutputSpec().getOutputType())
+ .isEqualTo(ExecuteInIsolatedServiceRequest.OutputSpec.OUTPUT_TYPE_NULL);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_EXECUTE_IN_ISOLATED_SERVICE_API_ENABLED)
+ public void testExecuteInIsolatedServiceResponse() {
+ SurfacePackageToken token = new SurfacePackageToken("token");
+ ExecuteInIsolatedServiceResponse response = new ExecuteInIsolatedServiceResponse(token, 10);
+
+ assertThat(response.getBestValue()).isEqualTo(10);
+ assertThat(response.getSurfacePackageToken()).isEqualTo(token);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_EXECUTE_IN_ISOLATED_SERVICE_API_ENABLED)
+ public void testExecuteInIsolatedServiceResponse_nullBestValue() {
+ SurfacePackageToken token = new SurfacePackageToken("token");
+ ExecuteInIsolatedServiceResponse response = new ExecuteInIsolatedServiceResponse(token);
+
+ assertThat(response.getBestValue()).isEqualTo(-1);
+ assertThat(response.getSurfacePackageToken()).isEqualTo(token);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_EXECUTE_IN_ISOLATED_SERVICE_API_ENABLED)
+ public void testExecuteOutputWithBestValue() {
+ ContentValues row = new ContentValues();
+ row.put("a", 5);
+ ExecuteOutput data =
+ new ExecuteOutput.Builder()
+ .setRequestLogRecord(new RequestLogRecord.Builder().addRow(row).build())
+ .setRenderingConfig(new RenderingConfig.Builder().addKey("abc").build())
+ .addEventLogRecord(new EventLogRecord.Builder().setType(1).build())
+ .setBestValue(100)
+ .build();
+
+ assertEquals(5, data.getRequestLogRecord().getRows().get(0).getAsInteger("a").intValue());
+ assertEquals("abc", data.getRenderingConfig().getKeys().get(0));
+ assertEquals(1, data.getEventLogRecords().get(0).getType());
+ assertThat(data.getOutputData()).isNull();
+ assertThat(data.getBestValue()).isEqualTo(100);
}
}
diff --git a/tests/cts/endtoend/src/com/android/ondevicepersonalization/cts/e2e/IsolatedWorkerTest.java b/tests/cts/endtoend/src/com/android/ondevicepersonalization/cts/e2e/IsolatedWorkerTest.java
index 265e788..560716f 100644
--- a/tests/cts/endtoend/src/com/android/ondevicepersonalization/cts/e2e/IsolatedWorkerTest.java
+++ b/tests/cts/endtoend/src/com/android/ondevicepersonalization/cts/e2e/IsolatedWorkerTest.java
@@ -29,7 +29,6 @@
import android.adservices.ondevicepersonalization.EventLogRecord;
import android.adservices.ondevicepersonalization.EventOutput;
import android.adservices.ondevicepersonalization.ExecuteInput;
-import android.adservices.ondevicepersonalization.ExecuteInputParcel;
import android.adservices.ondevicepersonalization.ExecuteOutput;
import android.adservices.ondevicepersonalization.IsolatedServiceException;
import android.adservices.ondevicepersonalization.IsolatedWorker;
@@ -46,13 +45,16 @@
import android.net.Uri;
import android.os.OutcomeReceiver;
import android.os.PersistableBundle;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import com.android.ondevicepersonalization.internal.util.ByteArrayParceledSlice;
-import com.android.ondevicepersonalization.internal.util.PersistableBundleUtils;
+import com.android.adservices.ondevicepersonalization.flags.Flags;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -65,20 +67,18 @@
*/
@SmallTest
@RunWith(AndroidJUnit4.class)
+@RequiresFlagsEnabled(Flags.FLAG_DATA_CLASS_MISSING_CTORS_AND_GETTERS_ENABLED)
public class IsolatedWorkerTest {
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
@Test
public void testOnExecute() throws Exception {
IsolatedWorker worker = new TestWorker();
WorkerResultReceiver<ExecuteOutput> receiver = new WorkerResultReceiver<>();
PersistableBundle bundle = new PersistableBundle();
bundle.putString("x", "y");
- ByteArrayParceledSlice slice = new ByteArrayParceledSlice(
- PersistableBundleUtils.toByteArray(bundle));
- ExecuteInputParcel inputParcel = new ExecuteInputParcel.Builder()
- .setAppPackageName("com.example.app")
- .setSerializedAppParams(slice)
- .build();
- worker.onExecute(new ExecuteInput(inputParcel), receiver);
+ worker.onExecute(new ExecuteInput("com.example.app", bundle), receiver);
}
@Test
@@ -97,8 +97,7 @@
WorkerResultReceiver<DownloadCompletedOutput> receiver = new WorkerResultReceiver<>();
TestKeyValueStore store = new TestKeyValueStore(
Map.of("a", new byte[]{'A'}, "b", new byte[]{'B'}));
- worker.onDownloadCompleted(
- new DownloadCompletedInput.Builder(store).build(), receiver);
+ worker.onDownloadCompleted(new DownloadCompletedInput(store), receiver);
assertThat(receiver.mResult.getRetainedKeys(), containsInAnyOrder("a", "b"));
}
diff --git a/tests/cts/endtoend/src/com/android/ondevicepersonalization/cts/e2e/RequestSurfacePackageTests.java b/tests/cts/endtoend/src/com/android/ondevicepersonalization/cts/e2e/RequestSurfacePackageTests.java
index 67fee65..e010c62 100644
--- a/tests/cts/endtoend/src/com/android/ondevicepersonalization/cts/e2e/RequestSurfacePackageTests.java
+++ b/tests/cts/endtoend/src/com/android/ondevicepersonalization/cts/e2e/RequestSurfacePackageTests.java
@@ -171,7 +171,7 @@
clickableLink.click();
// Retry if unable to click on the link.
- Thread.sleep(5 * 1000);
+ Thread.sleep(2500);
surfacePackage.release();
mDevice.pressHome();
diff --git a/tests/cts/service/src/com/android/ondevicepersonalization/testing/sampleservice/SampleWorker.java b/tests/cts/service/src/com/android/ondevicepersonalization/testing/sampleservice/SampleWorker.java
index 01b5d16..6971661 100644
--- a/tests/cts/service/src/com/android/ondevicepersonalization/testing/sampleservice/SampleWorker.java
+++ b/tests/cts/service/src/com/android/ondevicepersonalization/testing/sampleservice/SampleWorker.java
@@ -21,6 +21,8 @@
import android.adservices.ondevicepersonalization.ExecuteInput;
import android.adservices.ondevicepersonalization.ExecuteOutput;
import android.adservices.ondevicepersonalization.FederatedComputeInput;
+import android.adservices.ondevicepersonalization.FederatedComputeScheduleRequest;
+import android.adservices.ondevicepersonalization.FederatedComputeScheduleResponse;
import android.adservices.ondevicepersonalization.FederatedComputeScheduler;
import android.adservices.ondevicepersonalization.InferenceInput;
import android.adservices.ondevicepersonalization.InferenceOutput;
@@ -52,15 +54,19 @@
import java.util.HashMap;
import java.util.List;
import java.util.Objects;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
class SampleWorker implements IsolatedWorker {
private static final String TAG = "OdpTestingSampleService";
private static final int ERROR_SAMPLE_SERVICE_FAILED = 1;
+ private static final int SCHEDULE_CALLBACK_TIMEOUT_SECONDS = 5;
private static final String TRANSPARENT_PNG_BASE64 =
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAA"
@@ -145,7 +151,9 @@
} else if (op.equals(SampleServiceApi.OPCODE_READ_LOG)) {
result = handleReadLog(appParams);
} else if (op.equals(SampleServiceApi.OPCODE_SCHEDULE_FEDERATED_JOB)) {
- result = handleScheduleFederatedJob(appParams);
+ result = handleScheduleFederatedJob(appParams, /* useLegacyScheduleApi= */ true);
+ } else if (op.equals(SampleServiceApi.OPCODE_SCHEDULE_FEDERATED_JOB_V2)) {
+ result = handleScheduleFederatedJob(appParams, /* useLegacyScheduleApi= */ false);
} else if (op.equals(SampleServiceApi.OPCODE_CANCEL_FEDERATED_JOB)) {
result = handleCancelFederatedJob(appParams);
}
@@ -460,7 +468,8 @@
receiver.onResult(new RenderOutput.Builder().setContent(html).build());
}
- private ExecuteOutput handleScheduleFederatedJob(PersistableBundle appParams) {
+ private ExecuteOutput handleScheduleFederatedJob(
+ PersistableBundle appParams, boolean useLegacyScheduleApi) {
Log.i(TAG, "handleScheduleFederatedJob()");
String populationName =
Objects.requireNonNull(appParams.getString(SampleServiceApi.KEY_POPULATION_NAME));
@@ -472,8 +481,43 @@
.setSchedulingMode(TrainingInterval.SCHEDULING_MODE_ONE_TIME)
.build();
FederatedComputeScheduler.Params params = new FederatedComputeScheduler.Params(interval);
- mFcpScheduler.schedule(params, input);
- return new ExecuteOutput.Builder().build();
+
+ if (useLegacyScheduleApi) {
+ mFcpScheduler.schedule(params, input);
+ return new ExecuteOutput.Builder().build();
+ }
+
+ // Use new schedule API with outcome-receiver
+ BlockingQueue<Object> asyncResult = new ArrayBlockingQueue<>(1);
+ final Object emptyValue = new Object();
+ FederatedComputeScheduleRequest request =
+ new FederatedComputeScheduleRequest(params, populationName);
+ mFcpScheduler.schedule(
+ request,
+ new OutcomeReceiver<FederatedComputeScheduleResponse, Exception>() {
+ @Override
+ public void onResult(FederatedComputeScheduleResponse result) {
+ Log.e(TAG, "FCP schedule request successful!");
+ asyncResult.add(result);
+ }
+
+ @Override
+ public void onError(Exception e) {
+ Log.e(TAG, "FCP schedule request failed: " + e.getMessage());
+ asyncResult.add(emptyValue);
+ }
+ });
+
+ // Wait for outcome receiver callback.
+ Object response = null;
+ try {
+ response = asyncResult.poll(SCHEDULE_CALLBACK_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ Log.e(TAG, "Timed out waiting for schedule request to succeed!");
+ }
+ return (response == null || response == emptyValue)
+ ? null
+ : new ExecuteOutput.Builder().build();
}
private ExecuteOutput handleCancelFederatedJob(PersistableBundle appParams) {
diff --git a/tests/cts/serviceapi/src/com/android/ondevicepersonalization/testing/sampleserviceapi/SampleServiceApi.java b/tests/cts/serviceapi/src/com/android/ondevicepersonalization/testing/sampleserviceapi/SampleServiceApi.java
index 72004ba..4f5d83a 100644
--- a/tests/cts/serviceapi/src/com/android/ondevicepersonalization/testing/sampleserviceapi/SampleServiceApi.java
+++ b/tests/cts/serviceapi/src/com/android/ondevicepersonalization/testing/sampleserviceapi/SampleServiceApi.java
@@ -45,7 +45,14 @@
public static final String OPCODE_READ_REMOTE_DATA = "read_remote_data";
public static final String OPCODE_READ_USER_DATA = "read_user_data";
public static final String OPCODE_READ_LOG = "read_log";
+
+ // Code for the legacy FCP schedule API.
public static final String OPCODE_SCHEDULE_FEDERATED_JOB = "schedule_federated_job";
+
+ // Code for the new FCP schedule API.
+ public static final String OPCODE_SCHEDULE_FEDERATED_JOB_V2 =
+ "schedule_federated_job_outcome_receiver";
+
public static final String OPCODE_CANCEL_FEDERATED_JOB = "cancel_federated_job";
// Event types in logs.
diff --git a/tests/federatedcomputetests/src/com/android/federatedcompute/services/common/PhFlagsTest.java b/tests/federatedcomputetests/src/com/android/federatedcompute/services/common/PhFlagsTest.java
index 6a5e421..58665b0 100644
--- a/tests/federatedcomputetests/src/com/android/federatedcompute/services/common/PhFlagsTest.java
+++ b/tests/federatedcomputetests/src/com/android/federatedcompute/services/common/PhFlagsTest.java
@@ -29,6 +29,7 @@
import static com.android.federatedcompute.services.common.Flags.DEFAULT_TRAINING_MIN_BATTERY_LEVEL;
import static com.android.federatedcompute.services.common.Flags.ENABLE_CLIENT_ERROR_LOGGING;
import static com.android.federatedcompute.services.common.Flags.ENCRYPTION_ENABLED;
+import static com.android.federatedcompute.services.common.Flags.FCP_DEFAULT_CHECKPOINT_FILE_SIZE_LIMIT;
import static com.android.federatedcompute.services.common.Flags.FCP_DEFAULT_MEMORY_SIZE_LIMIT;
import static com.android.federatedcompute.services.common.Flags.FCP_RECURRENT_RESCHEDULE_LIMIT;
import static com.android.federatedcompute.services.common.Flags.FCP_RESCHEDULE_LIMIT;
@@ -45,6 +46,7 @@
import static com.android.federatedcompute.services.common.PhFlags.ENABLE_ELIGIBILITY_TASK;
import static com.android.federatedcompute.services.common.PhFlags.FCP_BACKGROUND_JOB_LOGGING_SAMPLING_RATE;
import static com.android.federatedcompute.services.common.PhFlags.FCP_BACKGROUND_JOB_SAMPLING_LOGGING_RATE;
+import static com.android.federatedcompute.services.common.PhFlags.FCP_CHECKPOINT_FILE_SIZE_LIMIT_CONFIG_NAME;
import static com.android.federatedcompute.services.common.PhFlags.FCP_ENABLE_BACKGROUND_JOBS_LOGGING;
import static com.android.federatedcompute.services.common.PhFlags.FCP_ENABLE_CLIENT_ERROR_LOGGING;
import static com.android.federatedcompute.services.common.PhFlags.FCP_ENABLE_ENCRYPTION;
@@ -206,6 +208,11 @@
FCP_TASK_LIMIT_PER_PACKAGE_CONFIG_NAME,
Integer.toString(DEFAULT_FCP_TASK_LIMIT_PER_PACKAGE),
/* makeDefault= */ false);
+ DeviceConfig.setProperty(
+ DeviceConfig.NAMESPACE_ON_DEVICE_PERSONALIZATION,
+ FCP_CHECKPOINT_FILE_SIZE_LIMIT_CONFIG_NAME,
+ Integer.toString(FCP_DEFAULT_CHECKPOINT_FILE_SIZE_LIMIT),
+ /* makeDefault= */ false);
}
@Test
@@ -620,28 +627,13 @@
@Test
public void testGetBackgroundJobsLoggingEnabled() {
- // read a stable flag value and verify it's equal to the default value.
- boolean stableValue = FlagsFactory.getFlags().getBackgroundJobsLoggingEnabled();
- assertThat(stableValue).isEqualTo(BACKGROUND_JOB_LOGGING_ENABLED);
-
- // Now overriding the value from PH.
- boolean overrideEnabled = !stableValue;
- DeviceConfig.setProperty(
- DeviceConfig.NAMESPACE_ON_DEVICE_PERSONALIZATION,
- FCP_ENABLE_BACKGROUND_JOBS_LOGGING,
- Boolean.toString(overrideEnabled),
- /* makeDefault= */ false);
-
- // the flag value remains stable
assertThat(FlagsFactory.getFlags().getBackgroundJobsLoggingEnabled())
- .isEqualTo(stableValue);
+ .isEqualTo(true);
}
@Test
public void testGetBackgroundJobSamplingLoggingRate() {
int defaultValue = FCP_BACKGROUND_JOB_SAMPLING_LOGGING_RATE;
- assertThat(FlagsFactory.getFlags().getBackgroundJobSamplingLoggingRate())
- .isEqualTo(defaultValue);
// Now overriding the value from PH.
int overrideRate = defaultValue + 1;
@@ -701,10 +693,33 @@
}
@Test
+ public void testGetFcpCheckinFileSizeLimit() {
+ // Without Overriding
+ DeviceConfig.setProperty(
+ DeviceConfig.NAMESPACE_ON_DEVICE_PERSONALIZATION,
+ FCP_CHECKPOINT_FILE_SIZE_LIMIT_CONFIG_NAME,
+ Integer.toString(FCP_DEFAULT_CHECKPOINT_FILE_SIZE_LIMIT),
+ /* makeDefault= */ false);
+ assertThat(FlagsFactory.getFlags().getFcpCheckpointFileSizeLimit())
+ .isEqualTo(FCP_DEFAULT_CHECKPOINT_FILE_SIZE_LIMIT);
+
+ // Now overriding the value from PH.
+ int overrideFcpCheckinFileSizeLimit = 1000;
+ DeviceConfig.setProperty(
+ DeviceConfig.NAMESPACE_ON_DEVICE_PERSONALIZATION,
+ FCP_CHECKPOINT_FILE_SIZE_LIMIT_CONFIG_NAME,
+ Integer.toString(overrideFcpCheckinFileSizeLimit),
+ /* makeDefault= */ false);
+
+ Flags phFlags = FlagsFactory.getFlags();
+ assertThat(phFlags.getFcpCheckpointFileSizeLimit())
+ .isEqualTo(overrideFcpCheckinFileSizeLimit);
+ }
+
+ @Test
public void testGetJobSchedulingLoggingEnabled() {
// read a stable flag value and verify it's equal to the default value.
boolean stableValue = FlagsFactory.getFlags().getJobSchedulingLoggingEnabled();
- assertThat(stableValue).isEqualTo(DEFAULT_JOB_SCHEDULING_LOGGING_ENABLED);
// override the value in device config.
boolean overrideEnabled = !stableValue;
@@ -722,8 +737,6 @@
@Test
public void testGetJobSchedulingLoggingSamplingRate() {
int defaultValue = DEFAULT_JOB_SCHEDULING_LOGGING_SAMPLING_RATE;
- assertThat(FlagsFactory.getFlags().getJobSchedulingLoggingSamplingRate())
- .isEqualTo(defaultValue);
// Override the value in device config.
int overrideRate = defaultValue + 1;
diff --git a/tests/federatedcomputetests/src/com/android/federatedcompute/services/encryption/BackgroundKeyFetchJobServiceTest.java b/tests/federatedcomputetests/src/com/android/federatedcompute/services/encryption/BackgroundKeyFetchJobServiceTest.java
index b7dcb65..7af1169 100644
--- a/tests/federatedcomputetests/src/com/android/federatedcompute/services/encryption/BackgroundKeyFetchJobServiceTest.java
+++ b/tests/federatedcomputetests/src/com/android/federatedcompute/services/encryption/BackgroundKeyFetchJobServiceTest.java
@@ -49,7 +49,7 @@
import com.android.federatedcompute.services.data.FederatedComputeDbHelper;
import com.android.federatedcompute.services.data.FederatedComputeEncryptionKey;
import com.android.federatedcompute.services.data.FederatedComputeEncryptionKeyDao;
-import com.android.federatedcompute.services.http.HttpClient;
+import com.android.odp.module.common.HttpClient;
import com.android.odp.module.common.MonotonicClock;
import com.google.common.util.concurrent.FluentFuture;
@@ -98,7 +98,7 @@
mContext = ApplicationProvider.getApplicationContext();
mInjector = new TestInjector();
mEncryptionDao = FederatedComputeEncryptionKeyDao.getInstanceForTest(mContext);
- mHttpClient = new HttpClient();
+ mHttpClient = new HttpClient(/* retryLimit= */ 3, MoreExecutors.newDirectExecutorService());
mSpyService = spy(new BackgroundKeyFetchJobService(new TestInjector()));
doReturn(mSpyService).when(mSpyService).getApplicationContext();
doNothing().when(mSpyService).jobFinished(any(), anyBoolean());
diff --git a/tests/federatedcomputetests/src/com/android/federatedcompute/services/encryption/FederatedComputeKeyFetchManagerTest.java b/tests/federatedcomputetests/src/com/android/federatedcompute/services/encryption/FederatedComputeKeyFetchManagerTest.java
index 5c1b3c4..8a1ca01 100644
--- a/tests/federatedcomputetests/src/com/android/federatedcompute/services/encryption/FederatedComputeKeyFetchManagerTest.java
+++ b/tests/federatedcomputetests/src/com/android/federatedcompute/services/encryption/FederatedComputeKeyFetchManagerTest.java
@@ -37,10 +37,10 @@
import com.android.federatedcompute.services.data.FederatedComputeDbHelper;
import com.android.federatedcompute.services.data.FederatedComputeEncryptionKey;
import com.android.federatedcompute.services.data.FederatedComputeEncryptionKeyDao;
-import com.android.federatedcompute.services.http.FederatedComputeHttpResponse;
-import com.android.federatedcompute.services.http.HttpClient;
import com.android.odp.module.common.Clock;
+import com.android.odp.module.common.HttpClient;
import com.android.odp.module.common.MonotonicClock;
+import com.android.odp.module.common.OdpHttpResponse;
import com.google.common.util.concurrent.Futures;
@@ -69,7 +69,7 @@
"Content-Type", List.of("json"));
private static final String SAMPLE_RESPONSE_PAYLOAD =
- """
+ """
{ "keys": [{ "id": "0cc9b4c9-08bd", "key": "BQo+c1Tw6TaQ+VH/b+9PegZOjHuKAFkl8QdmS0IjRj8" """
+ "} ] }";
@@ -166,7 +166,7 @@
public void testFetchAndPersistActiveKeys_scheduled_success() throws Exception {
doReturn(
Futures.immediateFuture(
- new FederatedComputeHttpResponse.Builder()
+ new OdpHttpResponse.Builder()
.setHeaders(SAMPLE_RESPONSE_HEADER)
.setPayload(SAMPLE_RESPONSE_PAYLOAD.getBytes())
.setStatusCode(200)
@@ -186,7 +186,7 @@
public void testFetchAndPersistActiveKeys_nonScheduled_success() throws Exception {
doReturn(
Futures.immediateFuture(
- new FederatedComputeHttpResponse.Builder()
+ new OdpHttpResponse.Builder()
.setHeaders(SAMPLE_RESPONSE_HEADER)
.setPayload(SAMPLE_RESPONSE_PAYLOAD.getBytes())
.setStatusCode(200)
@@ -280,7 +280,7 @@
public void testFetchAndPersistActiveKeys_scheduledNoDeletion() throws Exception {
doReturn(
Futures.immediateFuture(
- new FederatedComputeHttpResponse.Builder()
+ new OdpHttpResponse.Builder()
.setHeaders(SAMPLE_RESPONSE_HEADER)
.setPayload(SAMPLE_RESPONSE_PAYLOAD.getBytes())
.setStatusCode(200)
@@ -314,7 +314,7 @@
public void testFetchAndPersistActiveKeys_nonScheduledNoDeletion() throws Exception {
doReturn(
Futures.immediateFuture(
- new FederatedComputeHttpResponse.Builder()
+ new OdpHttpResponse.Builder()
.setHeaders(SAMPLE_RESPONSE_HEADER)
.setPayload(SAMPLE_RESPONSE_PAYLOAD.getBytes())
.setStatusCode(200)
@@ -348,7 +348,7 @@
public void testFetchAndPersistActiveKeys_scheduledWithDeletion() throws Exception {
doReturn(
Futures.immediateFuture(
- new FederatedComputeHttpResponse.Builder()
+ new OdpHttpResponse.Builder()
.setHeaders(SAMPLE_RESPONSE_HEADER)
.setPayload(SAMPLE_RESPONSE_PAYLOAD.getBytes())
.setStatusCode(200)
@@ -387,7 +387,7 @@
public void testFetchAndPersistActiveKeys_nonScheduledWithDeletion() throws Exception {
doReturn(
Futures.immediateFuture(
- new FederatedComputeHttpResponse.Builder()
+ new OdpHttpResponse.Builder()
.setHeaders(SAMPLE_RESPONSE_HEADER)
.setPayload(SAMPLE_RESPONSE_PAYLOAD.getBytes())
.setStatusCode(200)
@@ -429,7 +429,7 @@
public void testGetOrFetchActiveKeys_fetch() {
doReturn(
Futures.immediateFuture(
- new FederatedComputeHttpResponse.Builder()
+ new OdpHttpResponse.Builder()
.setHeaders(SAMPLE_RESPONSE_HEADER)
.setPayload(SAMPLE_RESPONSE_PAYLOAD.getBytes())
.setStatusCode(200)
@@ -458,7 +458,7 @@
.build());
doReturn(
Futures.immediateFuture(
- new FederatedComputeHttpResponse.Builder()
+ new OdpHttpResponse.Builder()
.setHeaders(SAMPLE_RESPONSE_HEADER)
.setPayload(SAMPLE_RESPONSE_PAYLOAD.getBytes())
.setStatusCode(200)
diff --git a/tests/federatedcomputetests/src/com/android/federatedcompute/services/http/FederatedComputeHttpRequestTest.java b/tests/federatedcomputetests/src/com/android/federatedcompute/services/http/FederatedComputeHttpRequestTest.java
deleted file mode 100644
index fb5a339..0000000
--- a/tests/federatedcomputetests/src/com/android/federatedcompute/services/http/FederatedComputeHttpRequestTest.java
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.federatedcompute.services.http;
-
-import static com.android.federatedcompute.services.http.HttpClientUtil.ACCEPT_ENCODING_HDR;
-import static com.android.federatedcompute.services.http.HttpClientUtil.GZIP_ENCODING_HDR;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assert.assertThrows;
-import static org.junit.Assert.assertTrue;
-
-import com.android.federatedcompute.services.http.HttpClientUtil.HttpMethod;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.util.HashMap;
-
-@RunWith(JUnit4.class)
-public final class FederatedComputeHttpRequestTest {
- private static final byte[] PAYLOAD = "non_empty_request_body".getBytes();
-
- @Test
- public void testCreateRequestInvalidUri_fails() throws Exception {
- assertThrows(
- IllegalArgumentException.class,
- () ->
- FederatedComputeHttpRequest.create(
- "http://invalid.com", HttpMethod.GET, new HashMap<>(), PAYLOAD));
- }
-
- @Test
- public void testCreateWithInvalidRequestBody_fails() throws Exception {
- assertThrows(
- IllegalArgumentException.class,
- () ->
- FederatedComputeHttpRequest.create(
- "https://valid.com", HttpMethod.GET, new HashMap<>(), PAYLOAD));
- }
-
- @Test
- public void testCreateWithContentLengthHeader_fails() throws Exception {
- HashMap<String, String> headers = new HashMap<>();
- headers.put("Content-Length", "1234");
- assertThrows(
- IllegalArgumentException.class,
- () ->
- FederatedComputeHttpRequest.create(
- "https://valid.com", HttpMethod.POST, headers, PAYLOAD));
- }
-
- @Test
- public void createGetRequest_valid() throws Exception {
- String expectedUri = "https://valid.com";
- FederatedComputeHttpRequest request =
- FederatedComputeHttpRequest.create(
- expectedUri, HttpMethod.GET, new HashMap<>(), HttpClientUtil.EMPTY_BODY);
-
- assertThat(request.getUri()).isEqualTo(expectedUri);
- assertThat(request.getHttpMethod()).isEqualTo(HttpMethod.GET);
- assertThat(request.getBody()).isEqualTo(HttpClientUtil.EMPTY_BODY);
- assertTrue(request.getExtraHeaders().isEmpty());
- }
-
- @Test
- public void createGetRequestWithHeader_valid() throws Exception {
- String expectedUri = "https://valid.com";
- HashMap<String, String> expectedHeaders = new HashMap<>();
- expectedHeaders.put("Foo", "Bar");
-
- FederatedComputeHttpRequest request =
- FederatedComputeHttpRequest.create(
- expectedUri, HttpMethod.GET, expectedHeaders, HttpClientUtil.EMPTY_BODY);
-
- assertThat(request.getUri()).isEqualTo(expectedUri);
- assertThat(request.getExtraHeaders()).isEqualTo(expectedHeaders);
- }
-
- @Test
- public void createPostRequestWithoutBody_valid() {
- String expectedUri = "https://valid.com";
-
- FederatedComputeHttpRequest request =
- FederatedComputeHttpRequest.create(
- expectedUri, HttpMethod.POST, new HashMap<>(), HttpClientUtil.EMPTY_BODY);
-
- assertThat(request.getUri()).isEqualTo(expectedUri);
- assertTrue(request.getExtraHeaders().isEmpty());
- assertThat(request.getHttpMethod()).isEqualTo(HttpMethod.POST);
- assertThat(request.getBody()).isEqualTo(HttpClientUtil.EMPTY_BODY);
- }
-
- @Test
- public void createPostRequestWithBody_valid() {
- String expectedUri = "https://valid.com";
-
- FederatedComputeHttpRequest request =
- FederatedComputeHttpRequest.create(
- expectedUri, HttpMethod.POST, new HashMap<>(), PAYLOAD);
-
- assertThat(request.getUri()).isEqualTo(expectedUri);
- assertThat(request.getHttpMethod()).isEqualTo(HttpMethod.POST);
- assertThat(request.getBody()).isEqualTo(PAYLOAD);
- }
-
- @Test
- public void createPostRequestWithBodyHeader_valid() {
- String expectedUri = "https://valid.com";
- HashMap<String, String> expectedHeaders = new HashMap<>();
- expectedHeaders.put("Foo", "Bar");
-
- FederatedComputeHttpRequest request =
- FederatedComputeHttpRequest.create(
- expectedUri, HttpMethod.POST, expectedHeaders, PAYLOAD);
-
- assertThat(request.getUri()).isEqualTo(expectedUri);
- assertThat(request.getHttpMethod()).isEqualTo(HttpMethod.POST);
- assertThat(request.getBody()).isEqualTo(PAYLOAD);
- assertThat(request.getExtraHeaders()).isEqualTo(expectedHeaders);
- }
-
- @Test
- public void createGetRequestWithAcceptCompression_valid() {
- String expectedUri = "https://valid.com";
- HashMap<String, String> headerList = new HashMap<>();
- headerList.put(ACCEPT_ENCODING_HDR, GZIP_ENCODING_HDR);
- FederatedComputeHttpRequest request =
- FederatedComputeHttpRequest.create(
- expectedUri, HttpMethod.POST, headerList, PAYLOAD);
-
- assertThat(request.getUri()).isEqualTo(expectedUri);
- assertThat(request.getHttpMethod()).isEqualTo(HttpMethod.POST);
- HashMap<String, String> expectedHeaders = new HashMap<>();
- expectedHeaders.put(HttpClientUtil.CONTENT_LENGTH_HDR, String.valueOf(22));
- expectedHeaders.put(ACCEPT_ENCODING_HDR, GZIP_ENCODING_HDR);
- assertThat(request.getExtraHeaders()).isEqualTo(expectedHeaders);
- }
-}
diff --git a/tests/federatedcomputetests/src/com/android/federatedcompute/services/http/HttpClientTest.java b/tests/federatedcomputetests/src/com/android/federatedcompute/services/http/HttpClientTest.java
deleted file mode 100644
index c616ec2..0000000
--- a/tests/federatedcomputetests/src/com/android/federatedcompute/services/http/HttpClientTest.java
+++ /dev/null
@@ -1,196 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.federatedcompute.services.http;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assert.assertThrows;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import static java.nio.charset.StandardCharsets.UTF_8;
-
-import com.android.federatedcompute.services.http.HttpClientUtil.HttpMethod;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.mockito.ArgumentMatchers;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.Spy;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.HttpURLConnection;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.atomic.AtomicInteger;
-
-@RunWith(JUnit4.class)
-public final class HttpClientTest {
- public static final FederatedComputeHttpRequest DEFAULT_GET_REQUEST =
- FederatedComputeHttpRequest.create(
- "https://google.com",
- HttpMethod.GET,
- new HashMap<>(),
- HttpClientUtil.EMPTY_BODY);
- @Spy private HttpClient mHttpClient = new HttpClient();
- @Rule public MockitoRule rule = MockitoJUnit.rule();
- @Mock private HttpURLConnection mMockHttpURLConnection;
-
- @Test
- public void testUnableToOpenconnection_returnFailure() throws Exception {
- FederatedComputeHttpRequest request =
- FederatedComputeHttpRequest.create(
- "https://google.com",
- HttpMethod.POST,
- new HashMap<>(),
- HttpClientUtil.EMPTY_BODY);
- doThrow(new IOException()).when(mHttpClient).setup(ArgumentMatchers.any());
-
- assertThrows(IOException.class, () -> mHttpClient.performRequest(request));
- }
-
- @Test
- public void testPerformGetRequestSuccess() throws Exception {
- String successMessage = "Success!";
- InputStream mockStream = new ByteArrayInputStream(successMessage.getBytes(UTF_8));
- Map<String, List<String>> mockHeaders = new HashMap<>();
- mockHeaders.put("Header1", Arrays.asList("Value1"));
- when(mMockHttpURLConnection.getInputStream()).thenReturn(mockStream);
- when(mMockHttpURLConnection.getResponseCode()).thenReturn(200);
- when(mMockHttpURLConnection.getHeaderFields()).thenReturn(mockHeaders);
- doReturn(mMockHttpURLConnection).when(mHttpClient).setup(ArgumentMatchers.any());
- when(mMockHttpURLConnection.getContentLengthLong())
- .thenReturn((long) successMessage.length());
-
- FederatedComputeHttpResponse response = mHttpClient.performRequest(DEFAULT_GET_REQUEST);
-
- assertThat(response.getStatusCode()).isEqualTo(200);
- assertThat(response.getHeaders()).isEqualTo(mockHeaders);
- assertThat(response.getPayload()).isEqualTo(successMessage.getBytes(UTF_8));
- }
-
- @Test
- public void testPerformGetRequestFails() throws Exception {
- String failureMessage = "FAIL!";
- InputStream mockStream = new ByteArrayInputStream(failureMessage.getBytes(UTF_8));
- when(mMockHttpURLConnection.getErrorStream()).thenReturn(mockStream);
- when(mMockHttpURLConnection.getResponseCode()).thenReturn(503);
- when(mMockHttpURLConnection.getHeaderFields()).thenReturn(new HashMap<>());
- doReturn(mMockHttpURLConnection).when(mHttpClient).setup(ArgumentMatchers.any());
- when(mMockHttpURLConnection.getContentLengthLong())
- .thenReturn((long) failureMessage.length());
-
- FederatedComputeHttpResponse response = mHttpClient.performRequest(DEFAULT_GET_REQUEST);
-
- assertThat(response.getStatusCode()).isEqualTo(503);
- assertTrue(response.getHeaders().isEmpty());
- assertThat(response.getPayload()).isEqualTo(failureMessage.getBytes(UTF_8));
- }
-
- @Test
- public void testPerformGetRequestFailsWithRetry() throws Exception {
- String failureMessage = "FAIL!";
- when(mMockHttpURLConnection.getErrorStream())
- .then(invocation -> new ByteArrayInputStream(failureMessage.getBytes(UTF_8)));
- when(mMockHttpURLConnection.getResponseCode()).thenReturn(503);
- when(mMockHttpURLConnection.getHeaderFields()).thenReturn(new HashMap<>());
- when(mMockHttpURLConnection.getContentLengthLong())
- .thenReturn((long) failureMessage.length());
- doReturn(mMockHttpURLConnection).when(mHttpClient).setup(ArgumentMatchers.any());
-
- FederatedComputeHttpResponse response =
- mHttpClient.performRequestWithRetry(DEFAULT_GET_REQUEST);
-
- verify(mHttpClient, times(3)).performRequest(DEFAULT_GET_REQUEST);
- assertThat(response.getStatusCode()).isEqualTo(503);
- assertTrue(response.getHeaders().isEmpty());
- assertThat(response.getPayload()).isEqualTo(failureMessage.getBytes(UTF_8));
- }
-
- @Test
- public void testPerformGetRequestSuccessWithRetry() throws Exception {
- String failureMessage = "FAIL!";
- InputStream mockStream = new ByteArrayInputStream(failureMessage.getBytes(UTF_8));
- when(mMockHttpURLConnection.getErrorStream()).thenReturn(mockStream);
- when(mMockHttpURLConnection.getResponseCode()).thenReturn(503);
- when(mMockHttpURLConnection.getHeaderFields()).thenReturn(new HashMap<>());
- HttpURLConnection mockSuccessfulHttpURLConnection = Mockito.mock(HttpURLConnection.class);
- Map<String, List<String>> mockHeaders = new HashMap<>();
- mockHeaders.put("Header1", Arrays.asList("Value1"));
- when(mockSuccessfulHttpURLConnection.getOutputStream())
- .thenReturn(new ByteArrayOutputStream());
- when(mockSuccessfulHttpURLConnection.getResponseCode()).thenReturn(200);
- when(mockSuccessfulHttpURLConnection.getHeaderFields()).thenReturn(mockHeaders);
- final AtomicInteger countCall = new AtomicInteger();
- doAnswer(
- invocation -> {
- int count = countCall.incrementAndGet();
- if (count < 3) {
- return mMockHttpURLConnection;
- } else {
- return mockSuccessfulHttpURLConnection;
- }
- })
- .when(mHttpClient)
- .setup(ArgumentMatchers.any());
- when(mMockHttpURLConnection.getContentLengthLong())
- .thenReturn((long) failureMessage.length());
-
- FederatedComputeHttpResponse response =
- mHttpClient.performRequestWithRetry(DEFAULT_GET_REQUEST);
-
- verify(mHttpClient, times(3)).performRequest(DEFAULT_GET_REQUEST);
- assertThat(response.getStatusCode()).isEqualTo(200);
- assertThat(response.getHeaders()).isEqualTo(mockHeaders);
- }
-
- @Test
- public void testPerformPostRequestSuccess() throws Exception {
- FederatedComputeHttpRequest request =
- FederatedComputeHttpRequest.create(
- "https://google.com",
- HttpMethod.POST,
- new HashMap<>(),
- "payload".getBytes(UTF_8));
- Map<String, List<String>> mockHeaders = new HashMap<>();
- mockHeaders.put("Header1", Arrays.asList("Value1"));
- when(mMockHttpURLConnection.getOutputStream()).thenReturn(new ByteArrayOutputStream());
- when(mMockHttpURLConnection.getResponseCode()).thenReturn(200);
- when(mMockHttpURLConnection.getHeaderFields()).thenReturn(mockHeaders);
- doReturn(mMockHttpURLConnection).when(mHttpClient).setup(ArgumentMatchers.any());
-
- FederatedComputeHttpResponse response = mHttpClient.performRequest(request);
-
- assertThat(response.getStatusCode()).isEqualTo(200);
- assertThat(response.getHeaders()).isEqualTo(mockHeaders);
- }
-}
diff --git a/tests/federatedcomputetests/src/com/android/federatedcompute/services/http/HttpClientUtilTest.java b/tests/federatedcomputetests/src/com/android/federatedcompute/services/http/HttpClientUtilTest.java
index 1f16aec..127ce13 100644
--- a/tests/federatedcomputetests/src/com/android/federatedcompute/services/http/HttpClientUtilTest.java
+++ b/tests/federatedcomputetests/src/com/android/federatedcompute/services/http/HttpClientUtilTest.java
@@ -24,6 +24,8 @@
import androidx.test.core.app.ApplicationProvider;
+import com.android.odp.module.common.HttpClientUtils;
+
import org.junit.Test;
import java.io.ByteArrayOutputStream;
@@ -45,10 +47,10 @@
}
byte[] dataBeforeCompress = outputStream.toByteArray();
- byte[] dataAfterCompress = HttpClientUtil.compressWithGzip(dataBeforeCompress);
+ byte[] dataAfterCompress = HttpClientUtils.compressWithGzip(dataBeforeCompress);
assertThat(dataAfterCompress.length).isLessThan(dataBeforeCompress.length);
- byte[] unzipData = HttpClientUtil.uncompressWithGzip(dataAfterCompress);
+ byte[] unzipData = HttpClientUtils.uncompressWithGzip(dataAfterCompress);
assertThat(unzipData).isEqualTo(dataBeforeCompress);
}
}
diff --git a/tests/federatedcomputetests/src/com/android/federatedcompute/services/http/HttpFederatedProtocolTest.java b/tests/federatedcomputetests/src/com/android/federatedcompute/services/http/HttpFederatedProtocolTest.java
index c180dd9..b4c0873 100644
--- a/tests/federatedcomputetests/src/com/android/federatedcompute/services/http/HttpFederatedProtocolTest.java
+++ b/tests/federatedcomputetests/src/com/android/federatedcompute/services/http/HttpFederatedProtocolTest.java
@@ -19,15 +19,16 @@
import static com.android.federatedcompute.services.http.HttpClientUtil.ACCEPT_ENCODING_HDR;
import static com.android.federatedcompute.services.http.HttpClientUtil.CONTENT_ENCODING_HDR;
import static com.android.federatedcompute.services.http.HttpClientUtil.CONTENT_LENGTH_HDR;
-import static com.android.federatedcompute.services.http.HttpClientUtil.CONTENT_TYPE_HDR;
import static com.android.federatedcompute.services.http.HttpClientUtil.FCP_OWNER_ID_DIGEST;
import static com.android.federatedcompute.services.http.HttpClientUtil.GZIP_ENCODING_HDR;
import static com.android.federatedcompute.services.http.HttpClientUtil.HTTP_UNAUTHENTICATED_STATUS;
import static com.android.federatedcompute.services.http.HttpClientUtil.ODP_AUTHENTICATION_KEY;
import static com.android.federatedcompute.services.http.HttpClientUtil.ODP_AUTHORIZATION_KEY;
import static com.android.federatedcompute.services.http.HttpClientUtil.ODP_IDEMPOTENCY_KEY;
-import static com.android.federatedcompute.services.http.HttpClientUtil.PROTOBUF_CONTENT_TYPE;
-import static com.android.federatedcompute.services.http.HttpClientUtil.compressWithGzip;
+import static com.android.odp.module.common.FileUtils.createTempFile;
+import static com.android.odp.module.common.HttpClientUtils.CONTENT_TYPE_HDR;
+import static com.android.odp.module.common.HttpClientUtils.PROTOBUF_CONTENT_TYPE;
+import static com.android.odp.module.common.HttpClientUtils.compressWithGzip;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.util.concurrent.Futures.immediateFuture;
@@ -51,6 +52,7 @@
import androidx.test.core.app.ApplicationProvider;
+import com.android.federatedcompute.services.common.Flags;
import com.android.federatedcompute.services.common.NetworkStats;
import com.android.federatedcompute.services.common.PhFlags;
import com.android.federatedcompute.services.common.TrainingEventLogger;
@@ -59,14 +61,17 @@
import com.android.federatedcompute.services.data.ODPAuthorizationToken;
import com.android.federatedcompute.services.data.ODPAuthorizationTokenDao;
import com.android.federatedcompute.services.encryption.HpkeJniEncrypter;
-import com.android.federatedcompute.services.http.HttpClientUtil.HttpMethod;
import com.android.federatedcompute.services.security.AuthorizationContext;
import com.android.federatedcompute.services.security.KeyAttestation;
import com.android.federatedcompute.services.testutils.TrainingTestUtil;
import com.android.federatedcompute.services.training.util.ComputationResult;
import com.android.modules.utils.testing.ExtendedMockitoRule;
import com.android.odp.module.common.Clock;
+import com.android.odp.module.common.HttpClient;
+import com.android.odp.module.common.HttpClientUtils;
import com.android.odp.module.common.MonotonicClock;
+import com.android.odp.module.common.OdpHttpRequest;
+import com.android.odp.module.common.OdpHttpResponse;
import com.google.common.collect.BoundType;
import com.google.common.collect.ImmutableList;
@@ -82,13 +87,13 @@
import com.google.internal.federatedcompute.v1.Resource;
import com.google.internal.federatedcompute.v1.ResourceCapabilities;
import com.google.internal.federatedcompute.v1.ResourceCompressionFormat;
+import com.google.internal.federatedcompute.v1.UploadInstruction;
import com.google.ondevicepersonalization.federatedcompute.proto.CreateTaskAssignmentRequest;
import com.google.ondevicepersonalization.federatedcompute.proto.CreateTaskAssignmentResponse;
import com.google.ondevicepersonalization.federatedcompute.proto.ReportResultRequest;
import com.google.ondevicepersonalization.federatedcompute.proto.ReportResultRequest.Result;
import com.google.ondevicepersonalization.federatedcompute.proto.ReportResultResponse;
import com.google.ondevicepersonalization.federatedcompute.proto.TaskAssignment;
-import com.google.ondevicepersonalization.federatedcompute.proto.UploadInstruction;
import com.google.protobuf.ByteString;
import org.json.JSONArray;
@@ -104,6 +109,8 @@
import org.mockito.quality.Strictness;
import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Collection;
@@ -173,6 +180,21 @@
.setContributionResult(ContributionResult.FAIL)
.setErrorStatus(FLRunnerResult.ErrorStatus.NOT_ELIGIBLE)
.build();
+ private static final FLRunnerResult FL_RUNNER_TENSORFLOW_ERROR_RESULT =
+ FLRunnerResult.newBuilder()
+ .setContributionResult(ContributionResult.FAIL)
+ .setErrorStatus(FLRunnerResult.ErrorStatus.TENSORFLOW_ERROR)
+ .build();
+ private static final FLRunnerResult FL_RUNNER_INVALID_ARGUMENT_RESULT =
+ FLRunnerResult.newBuilder()
+ .setContributionResult(ContributionResult.FAIL)
+ .setErrorStatus(FLRunnerResult.ErrorStatus.INVALID_ARGUMENT)
+ .build();
+ private static final FLRunnerResult FL_RUNNER_EXAMPLE_ITEREATOR_EEROR_RESULT =
+ FLRunnerResult.newBuilder()
+ .setContributionResult(ContributionResult.FAIL)
+ .setErrorStatus(FLRunnerResult.ErrorStatus.EXAMPLE_ITERATOR_ERROR)
+ .build();
private static final CreateTaskAssignmentRequest
START_TASK_ASSIGNMENT_REQUEST_WITH_COMPRESSION =
CreateTaskAssignmentRequest.newBuilder()
@@ -187,11 +209,11 @@
.build())
.build();
- private static final FederatedComputeHttpResponse SUCCESS_EMPTY_HTTP_RESPONSE =
- new FederatedComputeHttpResponse.Builder().setStatusCode(200).build();
+ private static final OdpHttpResponse SUCCESS_EMPTY_HTTP_RESPONSE =
+ new OdpHttpResponse.Builder().setStatusCode(200).build();
private static final long ODP_AUTHORIZATION_TOKEN_TTL = 30 * 24 * 60 * 60 * 1000L;
- @Captor private ArgumentCaptor<FederatedComputeHttpRequest> mHttpRequestCaptor;
+ @Captor private ArgumentCaptor<OdpHttpRequest> mHttpRequestCaptor;
@Mock private HttpClient mMockHttpClient;
@@ -237,6 +259,8 @@
doNothing().when(mTrainingEventLogger).logTaskAssignmentAuthSucceeded();
doReturn(true).when(mMocKFlags).isEncryptionEnabled();
when(PhFlags.getInstance()).thenReturn(mMocKFlags);
+ when(mMocKFlags.getFcpCheckpointFileSizeLimit())
+ .thenReturn(Flags.FCP_DEFAULT_CHECKPOINT_FILE_SIZE_LIMIT);
}
@After
@@ -259,14 +283,14 @@
.downloadTaskAssignment(createTaskAssignmentResponse.getTaskAssignment())
.get();
- List<FederatedComputeHttpRequest> actualHttpRequests = mHttpRequestCaptor.getAllValues();
+ List<OdpHttpRequest> actualHttpRequests = mHttpRequestCaptor.getAllValues();
// Verify task assignment request.
- FederatedComputeHttpRequest actualStartTaskAssignmentRequest = actualHttpRequests.get(0);
+ OdpHttpRequest actualStartTaskAssignmentRequest = actualHttpRequests.get(0);
checkActualTARequest(actualStartTaskAssignmentRequest, 4);
// Verify fetch resource request.
- FederatedComputeHttpRequest actualFetchResourceRequest = actualHttpRequests.get(1);
+ OdpHttpRequest actualFetchResourceRequest = actualHttpRequests.get(1);
ImmutableSet<String> resourceUris = ImmutableSet.of(PLAN_URI, CHECKPOINT_URI);
assertTrue(resourceUris.contains(actualFetchResourceRequest.getUri()));
HashMap<String, String> expectedHeaders = new HashMap<>();
@@ -279,10 +303,55 @@
NetworkStats networkStats = mNetworkStatsArgumentCaptor.getValue();
assertTrue(networkStats.getDataTransferDurationInMillis() > 0);
if (mSupportCompression) {
- assertThat(networkStats.getTotalBytesDownloaded()).isEqualTo(248);
+ assertThat(networkStats.getTotalBytesDownloaded()).isEqualTo(213);
assertThat(networkStats.getTotalBytesUploaded()).isEqualTo(124);
} else {
- assertThat(networkStats.getTotalBytesDownloaded()).isEqualTo(125);
+ assertThat(networkStats.getTotalBytesDownloaded()).isEqualTo(110);
+ assertThat(networkStats.getTotalBytesUploaded()).isEqualTo(78);
+ }
+ }
+
+ @Test
+ public void testIssueCheckinFailure_checkpointTooBig() throws Exception {
+ when(mMocKFlags.getFcpCheckpointFileSizeLimit()).thenReturn(0);
+ setUpHttpFederatedProtocol(
+ createStartTaskAssignmentHttpResponse(),
+ createPlanHttpResponse(),
+ checkpointEmptyHttpResponse(),
+ createReportResultHttpResponse(),
+ SUCCESS_EMPTY_HTTP_RESPONSE);
+
+ CreateTaskAssignmentResponse createTaskAssignmentResponse =
+ mHttpFederatedProtocol.createTaskAssignment(createAuthContext()).get();
+ mHttpFederatedProtocol
+ .downloadTaskAssignment(createTaskAssignmentResponse.getTaskAssignment())
+ .get();
+
+ List<OdpHttpRequest> actualHttpRequests = mHttpRequestCaptor.getAllValues();
+
+ // Verify task assignment request.
+ OdpHttpRequest actualStartTaskAssignmentRequest = actualHttpRequests.get(0);
+ checkActualTARequest(actualStartTaskAssignmentRequest, 4);
+
+ // Verify fetch resource request.
+ OdpHttpRequest actualFetchResourceRequest = actualHttpRequests.get(1);
+ ImmutableSet<String> resourceUris = ImmutableSet.of(PLAN_URI, CHECKPOINT_URI);
+ assertTrue(resourceUris.contains(actualFetchResourceRequest.getUri()));
+ HashMap<String, String> expectedHeaders = new HashMap<>();
+ if (mSupportCompression) {
+ expectedHeaders.put(ACCEPT_ENCODING_HDR, GZIP_ENCODING_HDR);
+ }
+ assertThat(actualFetchResourceRequest.getExtraHeaders()).isEqualTo(expectedHeaders);
+ verify(mTrainingEventLogger).logCheckinStarted();
+ verify(mTrainingEventLogger)
+ .logCheckinInvalidPayload(mNetworkStatsArgumentCaptor.capture());
+ NetworkStats networkStats = mNetworkStatsArgumentCaptor.getValue();
+ assertTrue(networkStats.getDataTransferDurationInMillis() > 0);
+ if (mSupportCompression) {
+ assertThat(networkStats.getTotalBytesDownloaded()).isEqualTo(213);
+ assertThat(networkStats.getTotalBytesUploaded()).isEqualTo(124);
+ } else {
+ assertThat(networkStats.getTotalBytesDownloaded()).isEqualTo(110);
assertThat(networkStats.getTotalBytesUploaded()).isEqualTo(78);
}
}
@@ -300,10 +369,10 @@
CreateTaskAssignmentResponse createTaskAssignmentResponse =
mHttpFederatedProtocol.createTaskAssignment(createAuthContext()).get();
- List<FederatedComputeHttpRequest> actualHttpRequests = mHttpRequestCaptor.getAllValues();
+ List<OdpHttpRequest> actualHttpRequests = mHttpRequestCaptor.getAllValues();
// Verify task assignment request.
- FederatedComputeHttpRequest actualStartTaskAssignmentRequest = actualHttpRequests.get(0);
+ OdpHttpRequest actualStartTaskAssignmentRequest = actualHttpRequests.get(0);
checkActualTARequest(actualStartTaskAssignmentRequest, 5);
String authorizationKey =
actualStartTaskAssignmentRequest.getExtraHeaders().get(ODP_AUTHORIZATION_KEY);
@@ -342,10 +411,10 @@
mHttpFederatedProtocol
.downloadTaskAssignment(taskAssignmentResponse.getTaskAssignment())
.get();
- List<FederatedComputeHttpRequest> actualHttpRequests = mHttpRequestCaptor.getAllValues();
+ List<OdpHttpRequest> actualHttpRequests = mHttpRequestCaptor.getAllValues();
// Verify task assignment request.
- FederatedComputeHttpRequest actualStartTaskAssignmentRequest = actualHttpRequests.get(0);
+ OdpHttpRequest actualStartTaskAssignmentRequest = actualHttpRequests.get(0);
checkActualTARequest(actualStartTaskAssignmentRequest, 5);
String authorizationKey =
actualStartTaskAssignmentRequest.getExtraHeaders().get(ODP_AUTHORIZATION_KEY);
@@ -385,8 +454,7 @@
@Test
public void testCreateTaskAssignmentFailed() {
- FederatedComputeHttpResponse httpResponse =
- new FederatedComputeHttpResponse.Builder().setStatusCode(404).build();
+ OdpHttpResponse httpResponse = new OdpHttpResponse.Builder().setStatusCode(404).build();
when(mMockHttpClient.performRequestAsyncWithRetry(any()))
.thenReturn(immediateFuture(httpResponse));
@@ -418,10 +486,10 @@
.downloadTaskAssignment(taskAssignmentResponse.getTaskAssignment())
.get();
- List<FederatedComputeHttpRequest> actualHttpRequests = mHttpRequestCaptor.getAllValues();
+ List<OdpHttpRequest> actualHttpRequests = mHttpRequestCaptor.getAllValues();
// Verify task assignment request.
- FederatedComputeHttpRequest actualStartTaskAssignmentRequest = actualHttpRequests.get(0);
+ OdpHttpRequest actualStartTaskAssignmentRequest = actualHttpRequests.get(0);
checkActualTARequest(actualStartTaskAssignmentRequest, 6);
String authenticationKey =
actualStartTaskAssignmentRequest.getExtraHeaders().get(ODP_AUTHENTICATION_KEY);
@@ -448,7 +516,7 @@
.isEqualTo(OWNER_ID);
// Verify fetch resource request.
- FederatedComputeHttpRequest actualFetchResourceRequest = actualHttpRequests.get(1);
+ OdpHttpRequest actualFetchResourceRequest = actualHttpRequests.get(1);
ImmutableSet<String> resourceUris = ImmutableSet.of(PLAN_URI, CHECKPOINT_URI);
assertTrue(resourceUris.contains(actualFetchResourceRequest.getUri()));
HashMap<String, String> expectedHeaders = new HashMap<>();
@@ -481,8 +549,8 @@
CreateTaskAssignmentResponse.newBuilder()
.setRejectionInfo(RejectionInfo.getDefaultInstance())
.build();
- FederatedComputeHttpResponse httpResponse =
- new FederatedComputeHttpResponse.Builder()
+ OdpHttpResponse httpResponse =
+ new OdpHttpResponse.Builder()
.setStatusCode(200)
.setPayload(createTaskAssignmentResponse.toByteArray())
.build();
@@ -494,7 +562,8 @@
assertThat(taskAssignmentResponse.hasRejectionInfo()).isTrue();
verify(mTrainingEventLogger).logCheckinStarted();
- verify(mTrainingEventLogger).logCheckinRejected(mNetworkStatsArgumentCaptor.capture());
+ verify(mTrainingEventLogger)
+ .logCheckinRejected(any(), mNetworkStatsArgumentCaptor.capture());
NetworkStats networkStats = mNetworkStatsArgumentCaptor.getValue();
assertThat(networkStats.getTotalBytesDownloaded()).isEqualTo(2);
assertThat(networkStats.getTotalBytesUploaded()).isEqualTo(339);
@@ -502,8 +571,7 @@
@Test
public void testTaskAssignmentSuccessPlanFetchFailed() throws Exception {
- FederatedComputeHttpResponse planHttpResponse =
- new FederatedComputeHttpResponse.Builder().setStatusCode(404).build();
+ OdpHttpResponse planHttpResponse = new OdpHttpResponse.Builder().setStatusCode(404).build();
// The workflow: start task assignment success, download plan failed and download
// checkpoint success.
setUpHttpFederatedProtocol(
@@ -535,8 +603,8 @@
@Test
public void testTaskAssignmentSuccessCheckpointDataFetchFailed() throws Exception {
- FederatedComputeHttpResponse checkpointHttpResponse =
- new FederatedComputeHttpResponse.Builder().setStatusCode(404).build();
+ OdpHttpResponse checkpointHttpResponse =
+ new OdpHttpResponse.Builder().setStatusCode(404).build();
// The workflow: start task assignment success, download plan success and download
// checkpoint failed.
@@ -580,9 +648,9 @@
.get();
// Verify ReportResult request.
- List<FederatedComputeHttpRequest> actualHttpRequests = mHttpRequestCaptor.getAllValues();
+ List<OdpHttpRequest> actualHttpRequests = mHttpRequestCaptor.getAllValues();
assertThat(actualHttpRequests).hasSize(4);
- FederatedComputeHttpRequest actualReportResultRequest = actualHttpRequests.get(3);
+ OdpHttpRequest actualReportResultRequest = actualHttpRequests.get(3);
ReportResultRequest reportResultRequest =
ReportResultRequest.newBuilder()
.setResult(Result.FAILED)
@@ -615,9 +683,9 @@
.get();
// Verify ReportResult request.
- List<FederatedComputeHttpRequest> actualHttpRequests = mHttpRequestCaptor.getAllValues();
+ List<OdpHttpRequest> actualHttpRequests = mHttpRequestCaptor.getAllValues();
assertThat(actualHttpRequests).hasSize(4);
- FederatedComputeHttpRequest actualReportResultRequest = actualHttpRequests.get(3);
+ OdpHttpRequest actualReportResultRequest = actualHttpRequests.get(3);
ReportResultRequest reportResultRequest =
ReportResultRequest.newBuilder()
.setResult(Result.NOT_ELIGIBLE)
@@ -640,6 +708,137 @@
}
@Test
+ public void testReportTensorflowErrorTrainingResult_returnSuccess() throws Exception {
+ ComputationResult computationResult =
+ new ComputationResult(
+ createOutputCheckpointFile(), FL_RUNNER_TENSORFLOW_ERROR_RESULT, null);
+
+ setUpHttpFederatedProtocol();
+ // Setup task id, aggregation id for report result.
+ CreateTaskAssignmentResponse taskAssignmentResponse =
+ mHttpFederatedProtocol.createTaskAssignment(createAuthContext()).get();
+ mHttpFederatedProtocol
+ .downloadTaskAssignment(taskAssignmentResponse.getTaskAssignment())
+ .get();
+
+ mHttpFederatedProtocol
+ .reportResult(computationResult, ENCRYPTION_KEY, createAuthContext())
+ .get();
+
+ // Verify ReportResult request.
+ List<OdpHttpRequest> actualHttpRequests = mHttpRequestCaptor.getAllValues();
+ assertThat(actualHttpRequests).hasSize(4);
+ OdpHttpRequest actualReportResultRequest = actualHttpRequests.get(3);
+ ReportResultRequest reportResultRequest =
+ ReportResultRequest.newBuilder()
+ .setResult(Result.FAILED_MODEL_COMPUTATION)
+ .setResourceCapabilities(
+ ResourceCapabilities.newBuilder()
+ .addSupportedCompressionFormats(
+ ResourceCompressionFormat
+ .RESOURCE_COMPRESSION_FORMAT_GZIP))
+ .build();
+ checkActualReportResultRequest(actualReportResultRequest);
+ assertThat(actualReportResultRequest.getBody())
+ .isEqualTo(reportResultRequest.toByteArray());
+ verify(mTrainingEventLogger).logFailureResultUploadStarted();
+ verify(mTrainingEventLogger)
+ .logFailureResultUploadCompleted(mNetworkStatsArgumentCaptor.capture());
+ NetworkStats networkStats = mNetworkStatsArgumentCaptor.getValue();
+ assertTrue(networkStats.getDataTransferDurationInMillis() > 0);
+ assertThat(networkStats.getTotalBytesDownloaded()).isEqualTo(mSupportCompression ? 96 : 68);
+ assertThat(networkStats.getTotalBytesUploaded()).isEqualTo(231);
+ }
+
+ @Test
+ public void testReportInvalidArgTrainingResult_returnSuccess() throws Exception {
+ ComputationResult computationResult =
+ new ComputationResult(
+ createOutputCheckpointFile(), FL_RUNNER_INVALID_ARGUMENT_RESULT, null);
+
+ setUpHttpFederatedProtocol();
+ // Setup task id, aggregation id for report result.
+ CreateTaskAssignmentResponse taskAssignmentResponse =
+ mHttpFederatedProtocol.createTaskAssignment(createAuthContext()).get();
+ mHttpFederatedProtocol
+ .downloadTaskAssignment(taskAssignmentResponse.getTaskAssignment())
+ .get();
+
+ mHttpFederatedProtocol
+ .reportResult(computationResult, ENCRYPTION_KEY, createAuthContext())
+ .get();
+
+ // Verify ReportResult request.
+ List<OdpHttpRequest> actualHttpRequests = mHttpRequestCaptor.getAllValues();
+ assertThat(actualHttpRequests).hasSize(4);
+ OdpHttpRequest actualReportResultRequest = actualHttpRequests.get(3);
+ ReportResultRequest reportResultRequest =
+ ReportResultRequest.newBuilder()
+ .setResult(Result.FAILED_MODEL_COMPUTATION)
+ .setResourceCapabilities(
+ ResourceCapabilities.newBuilder()
+ .addSupportedCompressionFormats(
+ ResourceCompressionFormat
+ .RESOURCE_COMPRESSION_FORMAT_GZIP))
+ .build();
+ checkActualReportResultRequest(actualReportResultRequest);
+ assertThat(actualReportResultRequest.getBody())
+ .isEqualTo(reportResultRequest.toByteArray());
+ verify(mTrainingEventLogger).logFailureResultUploadStarted();
+ verify(mTrainingEventLogger)
+ .logFailureResultUploadCompleted(mNetworkStatsArgumentCaptor.capture());
+ NetworkStats networkStats = mNetworkStatsArgumentCaptor.getValue();
+ assertTrue(networkStats.getDataTransferDurationInMillis() > 0);
+ assertThat(networkStats.getTotalBytesDownloaded()).isEqualTo(mSupportCompression ? 96 : 68);
+ assertThat(networkStats.getTotalBytesUploaded()).isEqualTo(231);
+ }
+
+ @Test
+ public void testReportExampleIteratorErrorTrainingResult_returnSuccess() throws Exception {
+ ComputationResult computationResult =
+ new ComputationResult(
+ createOutputCheckpointFile(),
+ FL_RUNNER_EXAMPLE_ITEREATOR_EEROR_RESULT,
+ null);
+
+ setUpHttpFederatedProtocol();
+ // Setup task id, aggregation id for report result.
+ CreateTaskAssignmentResponse taskAssignmentResponse =
+ mHttpFederatedProtocol.createTaskAssignment(createAuthContext()).get();
+ mHttpFederatedProtocol
+ .downloadTaskAssignment(taskAssignmentResponse.getTaskAssignment())
+ .get();
+
+ mHttpFederatedProtocol
+ .reportResult(computationResult, ENCRYPTION_KEY, createAuthContext())
+ .get();
+
+ // Verify ReportResult request.
+ List<OdpHttpRequest> actualHttpRequests = mHttpRequestCaptor.getAllValues();
+ assertThat(actualHttpRequests).hasSize(4);
+ OdpHttpRequest actualReportResultRequest = actualHttpRequests.get(3);
+ ReportResultRequest reportResultRequest =
+ ReportResultRequest.newBuilder()
+ .setResult(Result.FAILED_EXAMPLE_GENERATION)
+ .setResourceCapabilities(
+ ResourceCapabilities.newBuilder()
+ .addSupportedCompressionFormats(
+ ResourceCompressionFormat
+ .RESOURCE_COMPRESSION_FORMAT_GZIP))
+ .build();
+ checkActualReportResultRequest(actualReportResultRequest);
+ assertThat(actualReportResultRequest.getBody())
+ .isEqualTo(reportResultRequest.toByteArray());
+ verify(mTrainingEventLogger).logFailureResultUploadStarted();
+ verify(mTrainingEventLogger)
+ .logFailureResultUploadCompleted(mNetworkStatsArgumentCaptor.capture());
+ NetworkStats networkStats = mNetworkStatsArgumentCaptor.getValue();
+ assertTrue(networkStats.getDataTransferDurationInMillis() > 0);
+ assertThat(networkStats.getTotalBytesDownloaded()).isEqualTo(mSupportCompression ? 96 : 68);
+ assertThat(networkStats.getTotalBytesUploaded()).isEqualTo(231);
+ }
+
+ @Test
public void testReportAndUploadResultSuccess() throws Exception {
ComputationResult computationResult =
new ComputationResult(createOutputCheckpointFile(), FL_RUNNER_SUCCESS_RESULT, null);
@@ -657,8 +856,8 @@
.get();
// Verify ReportResult request.
- List<FederatedComputeHttpRequest> actualHttpRequests = mHttpRequestCaptor.getAllValues();
- FederatedComputeHttpRequest actualReportResultRequest = actualHttpRequests.get(3);
+ List<OdpHttpRequest> actualHttpRequests = mHttpRequestCaptor.getAllValues();
+ OdpHttpRequest actualReportResultRequest = actualHttpRequests.get(3);
checkActualReportResultRequest(actualReportResultRequest);
ReportResultRequest reportResultRequest =
@@ -674,9 +873,10 @@
.isEqualTo(reportResultRequest.toByteArray());
// Verify upload data request.
- FederatedComputeHttpRequest actualDataUploadRequest = actualHttpRequests.get(4);
+ OdpHttpRequest actualDataUploadRequest = actualHttpRequests.get(4);
assertThat(actualDataUploadRequest.getUri()).isEqualTo(UPLOAD_LOCATION_URI);
- assertThat(actualReportResultRequest.getHttpMethod()).isEqualTo(HttpMethod.PUT);
+ assertThat(actualReportResultRequest.getHttpMethod())
+ .isEqualTo(HttpClientUtils.HttpMethod.PUT);
HashMap<String, String> expectedHeaders = new HashMap<>();
expectedHeaders.put(CONTENT_TYPE_HDR, OCTET_STREAM);
if (mSupportCompression) {
@@ -707,8 +907,8 @@
@Test
public void testReportResultFailed() throws Exception {
- FederatedComputeHttpResponse reportResultHttpResponse =
- new FederatedComputeHttpResponse.Builder().setStatusCode(503).build();
+ OdpHttpResponse reportResultHttpResponse =
+ new OdpHttpResponse.Builder().setStatusCode(503).build();
ComputationResult computationResult =
new ComputationResult(createOutputCheckpointFile(), FL_RUNNER_SUCCESS_RESULT, null);
@@ -764,8 +964,8 @@
.get();
// Verify ReportResult request.
- List<FederatedComputeHttpRequest> actualHttpRequests = mHttpRequestCaptor.getAllValues();
- FederatedComputeHttpRequest actualReportResultRequest = actualHttpRequests.get(3);
+ List<OdpHttpRequest> actualHttpRequests = mHttpRequestCaptor.getAllValues();
+ OdpHttpRequest actualReportResultRequest = actualHttpRequests.get(3);
checkActualReportResultRequest(actualReportResultRequest);
ReportResultRequest reportResultRequest =
ReportResultRequest.newBuilder()
@@ -808,8 +1008,8 @@
assertThat(reportResultRejection).isNull();
// Verify ReportResult request.
- List<FederatedComputeHttpRequest> actualHttpRequests = mHttpRequestCaptor.getAllValues();
- FederatedComputeHttpRequest actualReportResultRequest = actualHttpRequests.get(3);
+ List<OdpHttpRequest> actualHttpRequests = mHttpRequestCaptor.getAllValues();
+ OdpHttpRequest actualReportResultRequest = actualHttpRequests.get(3);
checkActualReportResultRequest(actualReportResultRequest);
ReportResultRequest reportResultRequest =
ReportResultRequest.newBuilder()
@@ -898,8 +1098,8 @@
@Test
public void testReportResultSuccessUploadFailed() throws Exception {
- FederatedComputeHttpResponse uploadResultHttpResponse =
- new FederatedComputeHttpResponse.Builder().setStatusCode(503).build();
+ OdpHttpResponse uploadResultHttpResponse =
+ new OdpHttpResponse.Builder().setStatusCode(503).build();
ComputationResult computationResult =
new ComputationResult(createOutputCheckpointFile(), FL_RUNNER_SUCCESS_RESULT, null);
@@ -973,9 +1173,9 @@
return outputCheckpointFile.getAbsolutePath();
}
- private FederatedComputeHttpResponse createPlanHttpResponse() {
+ private OdpHttpResponse createPlanHttpResponse() {
byte[] clientOnlyPlan = TrainingTestUtil.createFederatedAnalyticClientPlan().toByteArray();
- return new FederatedComputeHttpResponse.Builder()
+ return new OdpHttpResponse.Builder()
.setStatusCode(200)
.setHeaders(mSupportCompression ? compressionHeaderList() : new HashMap<>())
.setPayload(mSupportCompression ? compressWithGzip(clientOnlyPlan) : clientOnlyPlan)
@@ -991,23 +1191,37 @@
SUCCESS_EMPTY_HTTP_RESPONSE);
}
- private FederatedComputeHttpResponse checkpointHttpResponse() {
- return new FederatedComputeHttpResponse.Builder()
+ private OdpHttpResponse checkpointHttpResponse() {
+ String fileName = createTempFile("input", ".ckp");
+ try (FileOutputStream fos = new FileOutputStream(fileName)) {
+ fos.write(mSupportCompression ? compressWithGzip(CHECKPOINT) : CHECKPOINT);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ return new OdpHttpResponse.Builder()
.setStatusCode(200)
- .setPayload(mSupportCompression ? compressWithGzip(CHECKPOINT) : CHECKPOINT)
+ .setPayloadFileName(fileName)
+ .setHeaders(mSupportCompression ? compressionHeaderList() : new HashMap<>())
+ .build();
+ }
+
+ private OdpHttpResponse checkpointEmptyHttpResponse() {
+ return new OdpHttpResponse.Builder()
+ .setStatusCode(200)
+ .setPayloadFileName(null)
.setHeaders(mSupportCompression ? compressionHeaderList() : new HashMap<>())
.build();
}
private void setUpHttpFederatedProtocol(
- FederatedComputeHttpResponse createTaskAssignmentResponse,
- FederatedComputeHttpResponse planHttpResponse,
- FederatedComputeHttpResponse checkpointHttpResponse,
- FederatedComputeHttpResponse reportResultHttpResponse,
- FederatedComputeHttpResponse uploadResultHttpResponse) {
+ OdpHttpResponse createTaskAssignmentResponse,
+ OdpHttpResponse planHttpResponse,
+ OdpHttpResponse checkpointHttpResponse,
+ OdpHttpResponse reportResultHttpResponse,
+ OdpHttpResponse uploadResultHttpResponse) {
doAnswer(
invocation -> {
- FederatedComputeHttpRequest httpRequest = invocation.getArgument(0);
+ OdpHttpRequest httpRequest = invocation.getArgument(0);
String uri = httpRequest.getUri();
// Add sleep for latency metric.
Thread.sleep(50);
@@ -1026,6 +1240,20 @@
})
.when(mMockHttpClient)
.performRequestAsyncWithRetry(mHttpRequestCaptor.capture());
+
+ doAnswer(
+ invocation -> {
+ OdpHttpRequest httpRequest = invocation.getArgument(0);
+ String uri = httpRequest.getUri();
+ // Add sleep for latency metric.
+ Thread.sleep(50);
+ if (uri.equals(CHECKPOINT_URI)) {
+ return immediateFuture(checkpointHttpResponse);
+ }
+ return immediateFuture(SUCCESS_EMPTY_HTTP_RESPONSE);
+ })
+ .when(mMockHttpClient)
+ .performRequestIntoFileAsyncWithRetry(mHttpRequestCaptor.capture());
}
private HashMap<String, List<String>> compressionHeaderList() {
@@ -1035,7 +1263,7 @@
return headerList;
}
- private FederatedComputeHttpResponse createReportResultHttpResponse() {
+ private OdpHttpResponse createReportResultHttpResponse() {
UploadInstruction.Builder uploadInstruction =
UploadInstruction.newBuilder().setUploadLocation(UPLOAD_LOCATION_URI);
uploadInstruction.putExtraRequestHeaders(CONTENT_TYPE_HDR, OCTET_STREAM);
@@ -1048,13 +1276,13 @@
ReportResultResponse.newBuilder()
.setUploadInstruction(uploadInstruction.build())
.build();
- return new FederatedComputeHttpResponse.Builder()
+ return new OdpHttpResponse.Builder()
.setStatusCode(200)
.setPayload(reportResultResponse.toByteArray())
.build();
}
- private FederatedComputeHttpResponse createStartTaskAssignmentHttpResponse() {
+ private OdpHttpResponse createStartTaskAssignmentHttpResponse() {
CreateTaskAssignmentResponse createTaskAssignmentResponse =
createCreateTaskAssignmentResponse(
Resource.newBuilder()
@@ -1076,14 +1304,14 @@
.RESOURCE_COMPRESSION_FORMAT_UNSPECIFIED)
.build());
- return new FederatedComputeHttpResponse.Builder()
+ return new OdpHttpResponse.Builder()
.setStatusCode(200)
.setPayload(createTaskAssignmentResponse.toByteArray())
.build();
}
- private FederatedComputeHttpResponse createUnauthorizedResponse() {
- return new FederatedComputeHttpResponse.Builder().setStatusCode(403).build();
+ private OdpHttpResponse createUnauthorizedResponse() {
+ return new OdpHttpResponse.Builder().setStatusCode(403).build();
}
private CreateTaskAssignmentResponse createCreateTaskAssignmentResponse(
@@ -1109,7 +1337,7 @@
.build();
}
- private FederatedComputeHttpResponse createUnauthenticatedResponse() {
+ private OdpHttpResponse createUnauthenticatedResponse() {
CreateTaskAssignmentResponse payload =
CreateTaskAssignmentResponse.newBuilder()
.setRejectionInfo(
@@ -1118,7 +1346,7 @@
.setReason(RejectionReason.Enum.UNAUTHENTICATED)
.build())
.build();
- return new FederatedComputeHttpResponse.Builder()
+ return new OdpHttpResponse.Builder()
.setStatusCode(HTTP_UNAUTHENTICATED_STATUS)
.setPayload(payload.toByteArray())
.setHeaders(new HashMap<>())
@@ -1126,9 +1354,10 @@
}
private void checkActualTARequest(
- FederatedComputeHttpRequest actualStartTaskAssignmentRequest, int headerSize) {
+ OdpHttpRequest actualStartTaskAssignmentRequest, int headerSize) {
assertThat(actualStartTaskAssignmentRequest.getUri()).isEqualTo(START_TASK_ASSIGNMENT_URI);
- assertThat(actualStartTaskAssignmentRequest.getHttpMethod()).isEqualTo(HttpMethod.POST);
+ assertThat(actualStartTaskAssignmentRequest.getHttpMethod())
+ .isEqualTo(HttpClientUtils.HttpMethod.POST);
// check header
HashMap<String, String> expectedHeaders = new HashMap<>();
@@ -1149,10 +1378,10 @@
.containsAtLeastEntriesIn(expectedHeaders);
}
- private void checkActualReportResultRequest(
- FederatedComputeHttpRequest actualReportResultRequest) {
+ private void checkActualReportResultRequest(OdpHttpRequest actualReportResultRequest) {
assertThat(actualReportResultRequest.getUri()).isEqualTo(REPORT_RESULT_URI);
- assertThat(actualReportResultRequest.getHttpMethod()).isEqualTo(HttpMethod.PUT);
+ assertThat(actualReportResultRequest.getHttpMethod())
+ .isEqualTo(HttpClientUtils.HttpMethod.PUT);
HashMap<String, String> expectedHeaders = new HashMap<>();
expectedHeaders.put(CONTENT_LENGTH_HDR, String.valueOf(7));
expectedHeaders.put(CONTENT_TYPE_HDR, PROTOBUF_CONTENT_TYPE);
diff --git a/tests/federatedcomputetests/src/com/android/federatedcompute/services/http/ProtocolRequestCreatorTest.java b/tests/federatedcomputetests/src/com/android/federatedcompute/services/http/ProtocolRequestCreatorTest.java
index afcc97c..ce56173 100644
--- a/tests/federatedcomputetests/src/com/android/federatedcompute/services/http/ProtocolRequestCreatorTest.java
+++ b/tests/federatedcomputetests/src/com/android/federatedcompute/services/http/ProtocolRequestCreatorTest.java
@@ -17,14 +17,15 @@
package com.android.federatedcompute.services.http;
import static com.android.federatedcompute.services.http.HttpClientUtil.CONTENT_LENGTH_HDR;
-import static com.android.federatedcompute.services.http.HttpClientUtil.CONTENT_TYPE_HDR;
-import static com.android.federatedcompute.services.http.HttpClientUtil.PROTOBUF_CONTENT_TYPE;
+import static com.android.odp.module.common.HttpClientUtils.CONTENT_TYPE_HDR;
+import static com.android.odp.module.common.HttpClientUtils.PROTOBUF_CONTENT_TYPE;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
-import com.android.federatedcompute.services.http.HttpClientUtil.HttpMethod;
+import com.android.odp.module.common.HttpClientUtils;
+import com.android.odp.module.common.OdpHttpRequest;
import com.google.internal.federatedcompute.v1.ForwardingInfo;
@@ -45,12 +46,12 @@
ProtocolRequestCreator requestCreator =
new ProtocolRequestCreator(REQUEST_BASE_URI, new HashMap<String, String>());
- FederatedComputeHttpRequest request =
+ OdpHttpRequest request =
requestCreator.createProtoRequest(
- "/v1/request", HttpMethod.POST, REQUEST_BODY, true);
+ "/v1/request", HttpClientUtils.HttpMethod.POST, REQUEST_BODY, true);
assertThat(request.getUri()).isEqualTo("https://initial.uri/v1/request");
- assertThat(request.getHttpMethod()).isEqualTo(HttpMethod.POST);
+ assertThat(request.getHttpMethod()).isEqualTo(HttpClientUtils.HttpMethod.POST);
assertThat(request.getBody()).isEqualTo(REQUEST_BODY);
HashMap<String, String> expectedHeaders = new HashMap<String, String>();
expectedHeaders.put(CONTENT_LENGTH_HDR, String.valueOf(12));
@@ -80,7 +81,10 @@
IllegalArgumentException.class,
() ->
requestCreator.createProtoRequest(
- "v1/request", HttpMethod.POST, REQUEST_BODY, false));
+ "v1/request",
+ HttpClientUtils.HttpMethod.POST,
+ REQUEST_BODY,
+ false));
assertThat(exception)
.hasMessageThat()
@@ -93,9 +97,9 @@
ForwardingInfo.newBuilder().setTargetUriPrefix(AGGREGATION_TARGET_URI).build();
ProtocolRequestCreator requestCreator = ProtocolRequestCreator.create(forwardingInfo);
- FederatedComputeHttpRequest request =
+ OdpHttpRequest request =
requestCreator.createProtoRequest(
- "/v1/request", HttpMethod.POST, REQUEST_BODY, false);
+ "/v1/request", HttpClientUtils.HttpMethod.POST, REQUEST_BODY, false);
assertThat(request.getUri()).isEqualTo("https://aggregation.uri/v1/request");
}
@@ -105,12 +109,12 @@
ProtocolRequestCreator requestCreator =
new ProtocolRequestCreator(REQUEST_BASE_URI, new HashMap<String, String>());
- FederatedComputeHttpRequest request =
+ OdpHttpRequest request =
requestCreator.createProtoRequest(
- "/v1/request", HttpMethod.POST, REQUEST_BODY, true);
+ "/v1/request", HttpClientUtils.HttpMethod.POST, REQUEST_BODY, true);
assertThat(request.getUri()).isEqualTo("https://initial.uri/v1/request");
- assertThat(request.getHttpMethod()).isEqualTo(HttpMethod.POST);
+ assertThat(request.getHttpMethod()).isEqualTo(HttpClientUtils.HttpMethod.POST);
assertThat(request.getBody()).isEqualTo(REQUEST_BODY);
HashMap<String, String> expectedHeaders = new HashMap<String, String>();
expectedHeaders.put(CONTENT_LENGTH_HDR, String.valueOf(12));
diff --git a/tests/federatedcomputetests/src/com/android/federatedcompute/services/scheduling/JobSchedulerHelperTest.java b/tests/federatedcomputetests/src/com/android/federatedcompute/services/scheduling/JobSchedulerHelperTest.java
index b97d5c6..0b38ec7 100644
--- a/tests/federatedcomputetests/src/com/android/federatedcompute/services/scheduling/JobSchedulerHelperTest.java
+++ b/tests/federatedcomputetests/src/com/android/federatedcompute/services/scheduling/JobSchedulerHelperTest.java
@@ -83,6 +83,23 @@
.constraints(TRAINING_CONSTRAINTS)
.build();
+ private static final FederatedTrainingTask TRAINING_TASK_EARLY_RUN =
+ FederatedTrainingTask.builder()
+ .appPackageName(PACKAGE_NAME)
+ .populationName(POPULATION_NAME)
+ .intervalOptions(INTERVAL_OPTIONS)
+ .creationTime(CURRENT_TIME_MILLIS)
+ .lastScheduledTime(CURRENT_TIME_MILLIS)
+ .schedulingReason(SCHEDULING_REASON)
+ .jobId(JOB_ID)
+ .ownerPackageName(OWNER_PACKAGE)
+ .ownerClassName(OWNER_CLASS)
+ .ownerIdCertDigest(OWNER_ID_CERT_DIGEST)
+ .serverAddress(SERVER_ADDRESS)
+ .earliestNextRunTime(0L)
+ .constraints(TRAINING_CONSTRAINTS)
+ .build();
+
private JobSchedulerHelper mJobSchedulerHelper;
private JobScheduler mJobScheduler;
private Context mContext;
@@ -108,6 +125,15 @@
}
@Test
+ public void scheduleTaskEarlyRun() {
+ assertThat(mJobSchedulerHelper.scheduleTask(mContext, TRAINING_TASK_EARLY_RUN)).isTrue();
+
+ JobInfo jobInfo = Iterables.getOnlyElement(mJobScheduler.getAllPendingJobs());
+
+ verifyJobInfo(jobInfo);
+ }
+
+ @Test
public void schedule_collides_sameService_success() {
mJobSchedulerHelper.scheduleTask(mContext, TRAINING_TASK);
// Schedule a job with same job id.
diff --git a/tests/federatedcomputetests/src/com/android/federatedcompute/services/training/EligibilityDeciderTest.java b/tests/federatedcomputetests/src/com/android/federatedcompute/services/training/EligibilityDeciderTest.java
index af36426..eb44bf8 100644
--- a/tests/federatedcomputetests/src/com/android/federatedcompute/services/training/EligibilityDeciderTest.java
+++ b/tests/federatedcomputetests/src/com/android/federatedcompute/services/training/EligibilityDeciderTest.java
@@ -19,6 +19,10 @@
import static com.android.federatedcompute.services.stats.FederatedComputeStatsLog.FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_ELIGIBILITY_EVAL_COMPUTATION_COMPLETED;
import static com.android.federatedcompute.services.stats.FederatedComputeStatsLog.FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_ELIGIBILITY_EVAL_COMPUTATION_ELIGIBLE;
import static com.android.federatedcompute.services.stats.FederatedComputeStatsLog.FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_ELIGIBILITY_EVAL_COMPUTATION_STARTED;
+import static com.android.federatedcompute.services.stats.FederatedComputeStatsLog.FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_EXAMPLE_STORE_BIND_START;
+import static com.android.federatedcompute.services.stats.FederatedComputeStatsLog.FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_EXAMPLE_STORE_BIND_SUCCESS;
+import static com.android.federatedcompute.services.stats.FederatedComputeStatsLog.FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_EXAMPLE_STORE_START_QUERY_START;
+import static com.android.federatedcompute.services.stats.FederatedComputeStatsLog.FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_EXAMPLE_STORE_START_QUERY_SUCCESS;
import static com.google.common.truth.Truth.assertThat;
@@ -356,11 +360,16 @@
assertTrue(result.isEligible());
ArgumentCaptor<Integer> eventKindCaptor = ArgumentCaptor.forClass(Integer.class);
- verify(mMockTrainingEventLogger, times(2)).logEventKind(eventKindCaptor.capture());
+ verify(mMockTrainingEventLogger, times(6)).logEventKind(eventKindCaptor.capture());
assertThat(eventKindCaptor.getAllValues())
- .containsAtLeast(
+ .containsExactly(
FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_ELIGIBILITY_EVAL_COMPUTATION_STARTED,
- FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_ELIGIBILITY_EVAL_COMPUTATION_ELIGIBLE);
+ FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_ELIGIBILITY_EVAL_COMPUTATION_ELIGIBLE,
+ FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_EXAMPLE_STORE_BIND_START,
+ FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_EXAMPLE_STORE_BIND_SUCCESS,
+ FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_EXAMPLE_STORE_START_QUERY_START,
+ FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_EXAMPLE_STORE_START_QUERY_SUCCESS);
+
ArgumentCaptor<ExampleStats> exampleStatsCaptor =
ArgumentCaptor.forClass(ExampleStats.class);
verify(mMockTrainingEventLogger)
diff --git a/tests/federatedcomputetests/src/com/android/federatedcompute/services/training/FederatedComputeWorkerTest.java b/tests/federatedcomputetests/src/com/android/federatedcompute/services/training/FederatedComputeWorkerTest.java
index 5373db3..d151a3d 100644
--- a/tests/federatedcomputetests/src/com/android/federatedcompute/services/training/FederatedComputeWorkerTest.java
+++ b/tests/federatedcomputetests/src/com/android/federatedcompute/services/training/FederatedComputeWorkerTest.java
@@ -21,12 +21,18 @@
import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__CLIENT_PLAN_SPEC_ERROR;
import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__ISOLATED_TRAINING_PROCESS_ERROR;
import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__FEDERATED_COMPUTE;
-import static com.android.federatedcompute.services.common.FileUtils.createTempFile;
import static com.android.federatedcompute.services.stats.FederatedComputeStatsLog.FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_COMPUTATION_STARTED;
import static com.android.federatedcompute.services.stats.FederatedComputeStatsLog.FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_ELIGIBILITY_EVAL_COMPUTATION_ELIGIBLE;
import static com.android.federatedcompute.services.stats.FederatedComputeStatsLog.FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_ELIGIBILITY_EVAL_COMPUTATION_STARTED;
+import static com.android.federatedcompute.services.stats.FederatedComputeStatsLog.FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_EXAMPLE_STORE_BIND_START;
+import static com.android.federatedcompute.services.stats.FederatedComputeStatsLog.FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_EXAMPLE_STORE_BIND_SUCCESS;
+import static com.android.federatedcompute.services.stats.FederatedComputeStatsLog.FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_EXAMPLE_STORE_START_QUERY_START;
+import static com.android.federatedcompute.services.stats.FederatedComputeStatsLog.FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_EXAMPLE_STORE_START_QUERY_SUCCESS;
import static com.android.federatedcompute.services.stats.FederatedComputeStatsLog.FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_RUN_COMPLETE;
+import static com.android.federatedcompute.services.stats.FederatedComputeStatsLog.FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_RUN_FAILED_COMPUTATION_FAILED;
+import static com.android.federatedcompute.services.stats.FederatedComputeStatsLog.FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_RUN_STARTED;
import static com.android.federatedcompute.services.testutils.TrainingTestUtil.COLLECTION_URI;
+import static com.android.odp.module.common.FileUtils.createTempFile;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.util.concurrent.Futures.immediateFailedFuture;
@@ -733,13 +739,19 @@
anyInt(), anyString(), any(), any(), eq(ContributionResult.FAIL), eq(true));
ArgumentCaptor<Integer> captor = ArgumentCaptor.forClass(Integer.class);
- verify(mMockTrainingEventLogger, times(3)).logEventKind(captor.capture());
+ verify(mMockTrainingEventLogger, times(9)).logEventKind(captor.capture());
assertThat(captor.getAllValues())
.containsExactlyElementsIn(
Arrays.asList(
FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_COMPUTATION_STARTED,
FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_ELIGIBILITY_EVAL_COMPUTATION_ELIGIBLE,
- FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_ELIGIBILITY_EVAL_COMPUTATION_STARTED));
+ FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_ELIGIBILITY_EVAL_COMPUTATION_STARTED,
+ FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_EXAMPLE_STORE_BIND_START,
+ FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_EXAMPLE_STORE_BIND_SUCCESS,
+ FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_EXAMPLE_STORE_START_QUERY_START,
+ FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_EXAMPLE_STORE_START_QUERY_SUCCESS,
+ FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_RUN_FAILED_COMPUTATION_FAILED,
+ FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED__KIND__TRAIN_RUN_STARTED));
verify(mMockTrainingEventLogger).logComputationInvalidArgument(any());
}
diff --git a/tests/federatedcomputetests/src/com/android/federatedcompute/services/training/IsolatedTrainingServiceImplTest.java b/tests/federatedcomputetests/src/com/android/federatedcompute/services/training/IsolatedTrainingServiceImplTest.java
index f609c3b..0252727 100644
--- a/tests/federatedcomputetests/src/com/android/federatedcompute/services/training/IsolatedTrainingServiceImplTest.java
+++ b/tests/federatedcomputetests/src/com/android/federatedcompute/services/training/IsolatedTrainingServiceImplTest.java
@@ -32,13 +32,13 @@
import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.android.federatedcompute.services.common.Constants;
import com.android.federatedcompute.services.common.FederatedComputeExecutors;
-import com.android.federatedcompute.services.common.FileUtils;
import com.android.federatedcompute.services.data.fbs.TrainingFlags;
import com.android.federatedcompute.services.testutils.FakeExampleStoreIterator;
import com.android.federatedcompute.services.testutils.TrainingTestUtil;
import com.android.federatedcompute.services.training.aidl.ITrainingResultCallback;
import com.android.modules.utils.testing.ExtendedMockitoRule;
import com.android.modules.utils.testing.ExtendedMockitoRule.MockStatic;
+import com.android.odp.module.common.FileUtils;
import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.MoreExecutors;
diff --git a/tests/frameworktests/Android.bp b/tests/frameworktests/Android.bp
index 7ab946b..94c2dcc 100644
--- a/tests/frameworktests/Android.bp
+++ b/tests/frameworktests/Android.bp
@@ -23,7 +23,7 @@
name: "FrameworkOnDevicePersonalizationTests",
srcs: [
"**/*.java",
- "**/*.aidl"
+ "**/*.aidl",
],
defaults: ["framework-ondevicepersonalization-test-defaults"],
min_sdk_version: "Tiramisu",
@@ -35,12 +35,14 @@
"androidx.test.rules",
"frameworks-base-testutils",
"guava",
+ "hamcrest-library",
"libprotobuf-java-lite",
"tensorflow_core_proto_java_lite",
"mockito-target-minus-junit4",
"ondevicepersonalization-testing-utils",
"truth",
"compatibility-device-util-axt",
+ "platform-compat-test-rules",
],
libs: [
"android.test.runner.stubs",
diff --git a/tests/frameworktests/AndroidManifest.xml b/tests/frameworktests/AndroidManifest.xml
index 52a1c59..9094e39 100644
--- a/tests/frameworktests/AndroidManifest.xml
+++ b/tests/frameworktests/AndroidManifest.xml
@@ -17,7 +17,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android.ondevicepersonalization">
- <application android:label="FrameworkOnDevicePersonalizationTests">
+ <application android:debuggable="true" android:label="FrameworkOnDevicePersonalizationTests">
<uses-library android:name="android.test.runner" />
<service
android:name="android.adservices.ondevicepersonalization.IsolatedServiceExceptionSafetyTestImpl"
diff --git a/tests/frameworktests/src/android/adservices/ondevicepersonalization/ExecuteInIsolatedServiceRequestTest.java b/tests/frameworktests/src/android/adservices/ondevicepersonalization/ExecuteInIsolatedServiceRequestTest.java
new file mode 100644
index 0000000..8cb7f69
--- /dev/null
+++ b/tests/frameworktests/src/android/adservices/ondevicepersonalization/ExecuteInIsolatedServiceRequestTest.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.adservices.ondevicepersonalization;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.ComponentName;
+import android.os.PersistableBundle;
+
+import org.junit.Test;
+
+public class ExecuteInIsolatedServiceRequestTest {
+ private static final ComponentName COMPONENT_NAME =
+ ComponentName.createRelative("com.example.service", ".Example");
+
+ @Test
+ public void buildRequestWithOption_success() {
+ PersistableBundle bundle = new PersistableBundle();
+ bundle.putString("key", "ok");
+
+ ExecuteInIsolatedServiceRequest request =
+ new ExecuteInIsolatedServiceRequest.Builder(COMPONENT_NAME)
+ .setAppParams(bundle)
+ .setOutputSpec(
+ ExecuteInIsolatedServiceRequest.OutputSpec.buildBestValueSpec(100))
+ .build();
+
+ ExecuteInIsolatedServiceRequest.OutputSpec options = request.getOutputSpec();
+ assertThat(options.getMaxIntValue()).isEqualTo(100);
+ assertThat(options.getOutputType())
+ .isEqualTo(ExecuteInIsolatedServiceRequest.OutputSpec.OUTPUT_TYPE_BEST_VALUE);
+ assertThat(request.getAppParams()).isEqualTo(bundle);
+ assertThat(request.getService()).isEqualTo(COMPONENT_NAME);
+ }
+
+ @Test
+ public void buildRequestWithoutOption_success() {
+ PersistableBundle bundle = new PersistableBundle();
+ bundle.putString("key", "ok");
+
+ ExecuteInIsolatedServiceRequest request =
+ new ExecuteInIsolatedServiceRequest.Builder(COMPONENT_NAME)
+ .setAppParams(bundle)
+ .build();
+
+ ExecuteInIsolatedServiceRequest.OutputSpec options = request.getOutputSpec();
+ assertThat(options).isEqualTo(ExecuteInIsolatedServiceRequest.OutputSpec.DEFAULT);
+ assertThat(request.getAppParams()).isEqualTo(bundle);
+ assertThat(request.getService()).isEqualTo(COMPONENT_NAME);
+ }
+
+ @Test
+ public void buildRequest_noParams_success() {
+ ExecuteInIsolatedServiceRequest request =
+ new ExecuteInIsolatedServiceRequest.Builder(COMPONENT_NAME).build();
+
+ assertThat(request.getService()).isEqualTo(COMPONENT_NAME);
+ }
+}
diff --git a/tests/frameworktests/src/android/adservices/ondevicepersonalization/ExecuteInIsolatedServiceResponseTest.java b/tests/frameworktests/src/android/adservices/ondevicepersonalization/ExecuteInIsolatedServiceResponseTest.java
new file mode 100644
index 0000000..5781f57
--- /dev/null
+++ b/tests/frameworktests/src/android/adservices/ondevicepersonalization/ExecuteInIsolatedServiceResponseTest.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.adservices.ondevicepersonalization;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Test;
+
+public class ExecuteInIsolatedServiceResponseTest {
+
+ @Test
+ public void response() {
+ ExecuteInIsolatedServiceResponse response =
+ new ExecuteInIsolatedServiceResponse(new SurfacePackageToken("aaaa"));
+
+ assertThat(response.getBestValue()).isEqualTo(-1);
+ assertThat(response.getSurfacePackageToken().getTokenString()).isEqualTo("aaaa");
+ }
+
+ @Test
+ public void responseWithBestValue() {
+ ExecuteInIsolatedServiceResponse response =
+ new ExecuteInIsolatedServiceResponse(new SurfacePackageToken("aaaa"), 20);
+
+ assertThat(response.getBestValue()).isEqualTo(20);
+ assertThat(response.getSurfacePackageToken().getTokenString()).isEqualTo("aaaa");
+ }
+
+ @Test
+ public void responseIsNull() {
+ ExecuteInIsolatedServiceResponse response = new ExecuteInIsolatedServiceResponse(null);
+
+ assertThat(response.getBestValue()).isEqualTo(-1);
+ assertThat(response.getSurfacePackageToken()).isNull();
+ }
+}
diff --git a/tests/frameworktests/src/android/adservices/ondevicepersonalization/FederatedComputeSchedulerTest.java b/tests/frameworktests/src/android/adservices/ondevicepersonalization/FederatedComputeSchedulerTest.java
index 4f65626..45d45cc 100644
--- a/tests/frameworktests/src/android/adservices/ondevicepersonalization/FederatedComputeSchedulerTest.java
+++ b/tests/frameworktests/src/android/adservices/ondevicepersonalization/FederatedComputeSchedulerTest.java
@@ -18,7 +18,11 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
import android.adservices.ondevicepersonalization.aidl.IDataAccessService;
import android.adservices.ondevicepersonalization.aidl.IDataAccessServiceCallback;
@@ -31,16 +35,39 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import com.android.ondevicepersonalization.testing.utils.ResultReceiver;
+
import org.junit.Test;
import org.junit.runner.RunWith;
import java.time.Duration;
-/** Unit Tests of RemoteData API. */
+/** Unit Tests for {@link FederatedComputeScheduler}. */
@SmallTest
@RunWith(AndroidJUnit4.class)
public class FederatedComputeSchedulerTest {
- FederatedComputeScheduler mFederatedComputeScheduler =
+
+ private static final String VALID_POPULATION_NAME = "population";
+ private static final String ERROR_POPULATION_NAME = "err";
+
+ private static final String INVALID_MANIFEST_ERROR_POPULATION_NAME = "manifest_error";
+ private static final String POPULATION_NAME_PRIVACY_NOT_ELIGIBLE = "privacy_not_eligible";
+
+ private static final TrainingInterval TEST_TRAINING_INTERVAL =
+ new TrainingInterval.Builder()
+ .setMinimumInterval(Duration.ofHours(10))
+ .setSchedulingMode(TrainingInterval.SCHEDULING_MODE_ONE_TIME)
+ .build();
+
+ private static final FederatedComputeScheduler.Params TEST_SCHEDULER_PARAMS =
+ new FederatedComputeScheduler.Params(TEST_TRAINING_INTERVAL);
+
+ private static final FederatedComputeInput TEST_FC_INPUT =
+ new FederatedComputeInput.Builder().setPopulationName(VALID_POPULATION_NAME).build();
+ private static final FederatedComputeScheduleRequest TEST_SCHEDULE_INPUT =
+ new FederatedComputeScheduleRequest(TEST_SCHEDULER_PARAMS, VALID_POPULATION_NAME);
+
+ private final FederatedComputeScheduler mFederatedComputeScheduler =
new FederatedComputeScheduler(
IFederatedComputeService.Stub.asInterface(new FederatedComputeService()),
IDataAccessService.Stub.asInterface(new TestDataService()));
@@ -48,86 +75,158 @@
private boolean mCancelCalled = false;
private boolean mScheduleCalled = false;
private boolean mLogApiCalled = false;
+ private int mResponseCode = Constants.STATUS_SUCCESS;
@Test
public void testScheduleSuccess() {
- TrainingInterval interval =
- new TrainingInterval.Builder()
- .setMinimumInterval(Duration.ofHours(10))
- .setSchedulingMode(TrainingInterval.SCHEDULING_MODE_ONE_TIME)
- .build();
- FederatedComputeScheduler.Params params = new FederatedComputeScheduler.Params(interval);
- FederatedComputeInput input =
- new FederatedComputeInput.Builder().setPopulationName("population").build();
- mFederatedComputeScheduler.schedule(params, input);
+ mFederatedComputeScheduler.schedule(TEST_SCHEDULER_PARAMS, TEST_FC_INPUT);
+
assertThat(mScheduleCalled).isTrue();
assertThat(mLogApiCalled).isTrue();
+ assertThat(mResponseCode).isEqualTo(Constants.STATUS_SUCCESS);
+ }
+
+ @Test
+ public void testSchedule_withOutcomeReceiver_success() throws Exception {
+ var receiver = new ResultReceiver();
+
+ mFederatedComputeScheduler.schedule(TEST_SCHEDULE_INPUT, receiver);
+
+ assertNotNull(receiver.getResult());
+ assertTrue(receiver.isSuccess());
+ assertThat(mScheduleCalled).isTrue();
+ assertThat(mLogApiCalled).isTrue();
+ assertThat(mResponseCode).isEqualTo(Constants.STATUS_SUCCESS);
+ }
+
+ @Test
+ public void testSchedule_withOutcomeReceiver_error() throws Exception {
+ FederatedComputeScheduleRequest scheduleInput =
+ new FederatedComputeScheduleRequest(TEST_SCHEDULER_PARAMS, ERROR_POPULATION_NAME);
+ var receiver = new ResultReceiver();
+
+ mFederatedComputeScheduler.schedule(scheduleInput, receiver);
+
+ assertNull(receiver.getResult());
+ assertTrue(receiver.isError());
+ assertTrue(receiver.getException() instanceof OnDevicePersonalizationException);
+ assertEquals(
+ OnDevicePersonalizationException.ERROR_SCHEDULE_TRAINING_FAILED,
+ ((OnDevicePersonalizationException) receiver.getException()).getErrorCode());
+ assertThat(mScheduleCalled).isTrue();
+ assertThat(mLogApiCalled).isTrue();
+ assertThat(mResponseCode).isEqualTo(Constants.STATUS_INTERNAL_ERROR);
+ }
+
+ @Test
+ public void testSchedule_withOutcomeReceiver_manifestError() throws Exception {
+ FederatedComputeScheduleRequest scheduleInput =
+ new FederatedComputeScheduleRequest(
+ TEST_SCHEDULER_PARAMS, INVALID_MANIFEST_ERROR_POPULATION_NAME);
+ var receiver = new ResultReceiver();
+
+ mFederatedComputeScheduler.schedule(scheduleInput, receiver);
+
+ assertNull(receiver.getResult());
+ assertTrue(receiver.isError());
+ assertTrue(receiver.getException() instanceof OnDevicePersonalizationException);
+ assertEquals(
+ OnDevicePersonalizationException.ERROR_INVALID_TRAINING_MANIFEST,
+ ((OnDevicePersonalizationException) receiver.getException()).getErrorCode());
+ assertThat(mScheduleCalled).isTrue();
+ assertThat(mLogApiCalled).isTrue();
+ assertThat(mResponseCode).isEqualTo(Constants.STATUS_FCP_MANIFEST_INVALID);
}
@Test
public void testScheduleNull() {
FederatedComputeScheduler fcs = new FederatedComputeScheduler(null, new TestDataService());
- TrainingInterval interval =
- new TrainingInterval.Builder()
- .setMinimumInterval(Duration.ofHours(10))
- .setSchedulingMode(TrainingInterval.SCHEDULING_MODE_ONE_TIME)
- .build();
- FederatedComputeScheduler.Params params = new FederatedComputeScheduler.Params(interval);
- FederatedComputeInput input =
- new FederatedComputeInput.Builder().setPopulationName("population").build();
- assertThrows(IllegalStateException.class, () -> fcs.schedule(params, input));
+
+ assertThrows(
+ IllegalStateException.class,
+ () -> fcs.schedule(TEST_SCHEDULER_PARAMS, TEST_FC_INPUT));
+ assertThat(mResponseCode).isEqualTo(Constants.STATUS_INTERNAL_ERROR);
}
@Test
- public void testScheduleErr() {
- TrainingInterval interval =
- new TrainingInterval.Builder()
- .setMinimumInterval(Duration.ofHours(10))
- .setSchedulingMode(TrainingInterval.SCHEDULING_MODE_ONE_TIME)
- .build();
- FederatedComputeScheduler.Params params = new FederatedComputeScheduler.Params(interval);
+ public void testScheduleError() {
FederatedComputeInput input =
- new FederatedComputeInput.Builder().setPopulationName("err").build();
- mFederatedComputeScheduler.schedule(params, input);
+ new FederatedComputeInput.Builder()
+ .setPopulationName(ERROR_POPULATION_NAME)
+ .build();
+
+ mFederatedComputeScheduler.schedule(TEST_SCHEDULER_PARAMS, input);
+
assertThat(mScheduleCalled).isTrue();
assertThat(mLogApiCalled).isTrue();
+ assertThat(mResponseCode).isEqualTo(Constants.STATUS_INTERNAL_ERROR);
+ }
+
+ @Test
+ public void testSchedulePrivacyNotEligible() {
+ FederatedComputeInput input =
+ new FederatedComputeInput.Builder()
+ .setPopulationName(POPULATION_NAME_PRIVACY_NOT_ELIGIBLE)
+ .build();
+
+ mFederatedComputeScheduler.schedule(TEST_SCHEDULER_PARAMS, input);
+
+ assertThat(mScheduleCalled).isTrue();
+ assertThat(mLogApiCalled).isTrue();
+ assertThat(mResponseCode).isEqualTo(Constants.STATUS_PERSONALIZATION_DISABLED);
}
@Test
public void testCancelSuccess() {
- FederatedComputeInput input =
- new FederatedComputeInput.Builder().setPopulationName("population").build();
- mFederatedComputeScheduler.cancel(input);
+ mFederatedComputeScheduler.cancel(TEST_FC_INPUT);
+
assertThat(mCancelCalled).isTrue();
assertThat(mLogApiCalled).isTrue();
+ assertThat(mResponseCode).isEqualTo(Constants.STATUS_SUCCESS);
}
@Test
public void testCancelNull() {
- FederatedComputeInput input =
- new FederatedComputeInput.Builder().setPopulationName("population").build();
FederatedComputeScheduler fcs = new FederatedComputeScheduler(null, new TestDataService());
- assertThrows(IllegalStateException.class, () -> fcs.cancel(input));
+
+ assertThrows(IllegalStateException.class, () -> fcs.cancel(TEST_FC_INPUT));
+ assertThat(mResponseCode).isEqualTo(Constants.STATUS_INTERNAL_ERROR);
}
@Test
- public void testCancelErr() {
+ public void testCancelError() {
FederatedComputeInput input =
- new FederatedComputeInput.Builder().setPopulationName("err").build();
+ new FederatedComputeInput.Builder()
+ .setPopulationName(ERROR_POPULATION_NAME)
+ .build();
+
mFederatedComputeScheduler.cancel(input);
+
assertThat(mCancelCalled).isTrue();
assertThat(mLogApiCalled).isTrue();
+ assertThat(mResponseCode).isEqualTo(Constants.STATUS_INTERNAL_ERROR);
}
- class FederatedComputeService extends IFederatedComputeService.Stub {
+ private class FederatedComputeService extends IFederatedComputeService.Stub {
@Override
public void schedule(
TrainingOptions trainingOptions,
IFederatedComputeCallback iFederatedComputeCallback)
throws RemoteException {
mScheduleCalled = true;
- if (trainingOptions.getPopulationName().equals("err")) {
- iFederatedComputeCallback.onFailure(1);
+ if (trainingOptions.getPopulationName().equals(ERROR_POPULATION_NAME)) {
+ iFederatedComputeCallback.onFailure(Constants.STATUS_INTERNAL_ERROR);
+ return;
+ }
+ if (trainingOptions.getPopulationName().equals(POPULATION_NAME_PRIVACY_NOT_ELIGIBLE)) {
+ iFederatedComputeCallback.onFailure(Constants.STATUS_PERSONALIZATION_DISABLED);
+ return;
+ }
+ if (trainingOptions
+ .getPopulationName()
+ .equals(INVALID_MANIFEST_ERROR_POPULATION_NAME)) {
+ iFederatedComputeCallback.onFailure(Constants.STATUS_FCP_MANIFEST_INVALID);
+ return;
}
iFederatedComputeCallback.onSuccess();
}
@@ -136,20 +235,23 @@
public void cancel(String s, IFederatedComputeCallback iFederatedComputeCallback)
throws RemoteException {
mCancelCalled = true;
- if (s.equals("err")) {
+ if (s.equals(ERROR_POPULATION_NAME)) {
iFederatedComputeCallback.onFailure(1);
+ return;
}
iFederatedComputeCallback.onSuccess();
}
}
- class TestDataService extends IDataAccessService.Stub {
+ private class TestDataService extends IDataAccessService.Stub {
+
@Override
public void onRequest(int operation, Bundle params, IDataAccessServiceCallback callback) {}
@Override
public void logApiCallStats(int apiName, long latencyMillis, int responseCode) {
mLogApiCalled = true;
+ mResponseCode = responseCode;
}
}
}
diff --git a/tests/frameworktests/src/android/adservices/ondevicepersonalization/IsolatedServiceExceptionSafetyTest.java b/tests/frameworktests/src/android/adservices/ondevicepersonalization/IsolatedServiceExceptionSafetyTest.java
index 441d3ac..7a53369 100644
--- a/tests/frameworktests/src/android/adservices/ondevicepersonalization/IsolatedServiceExceptionSafetyTest.java
+++ b/tests/frameworktests/src/android/adservices/ondevicepersonalization/IsolatedServiceExceptionSafetyTest.java
@@ -63,6 +63,7 @@
private AbstractServiceBinder<IIsolatedService> mServiceBinder;
private int mCallbackErrorCode;
private int mIsolatedServiceErrorCode;
+ private byte[] mSerializedExceptionInfo;
private CountDownLatch mLatch;
@Parameterized.Parameter(0)
@@ -207,9 +208,13 @@
}
@Override
- public void onError(int errorCode, int isolatedServiceErrorCode) {
+ public void onError(
+ int errorCode,
+ int isolatedServiceErrorCode,
+ byte[] serializedExceptionInfo) {
mCallbackErrorCode = errorCode;
mIsolatedServiceErrorCode = isolatedServiceErrorCode;
+ mSerializedExceptionInfo = serializedExceptionInfo;
mLatch.countDown();
}
}
diff --git a/tests/frameworktests/src/android/adservices/ondevicepersonalization/IsolatedServiceTest.java b/tests/frameworktests/src/android/adservices/ondevicepersonalization/IsolatedServiceTest.java
index ced94f1..b44776c 100644
--- a/tests/frameworktests/src/android/adservices/ondevicepersonalization/IsolatedServiceTest.java
+++ b/tests/frameworktests/src/android/adservices/ondevicepersonalization/IsolatedServiceTest.java
@@ -70,6 +70,7 @@
private Bundle mCallbackResult;
private int mCallbackErrorCode;
private int mIsolatedServiceErrorCode;
+ private byte[] mSerializedExceptionInfo;
@Before
public void setUp() {
@@ -742,9 +743,13 @@
}
@Override
- public void onError(int errorCode, int isolatedServiceErrorCode) {
+ public void onError(
+ int errorCode,
+ int isolatedServiceErrorCode,
+ byte[] serializedExceptionInfo) {
mCallbackErrorCode = errorCode;
mIsolatedServiceErrorCode = isolatedServiceErrorCode;
+ mSerializedExceptionInfo = serializedExceptionInfo;
mLatch.countDown();
}
}
diff --git a/tests/frameworktests/src/android/adservices/ondevicepersonalization/ModelManagerTest.java b/tests/frameworktests/src/android/adservices/ondevicepersonalization/ModelManagerTest.java
index 0a7cdb6..2ea5885 100644
--- a/tests/frameworktests/src/android/adservices/ondevicepersonalization/ModelManagerTest.java
+++ b/tests/frameworktests/src/android/adservices/ondevicepersonalization/ModelManagerTest.java
@@ -16,9 +16,10 @@
package android.adservices.ondevicepersonalization;
+import static com.google.common.truth.Truth.assertThat;
+
import static junit.framework.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
@@ -27,12 +28,13 @@
import android.adservices.ondevicepersonalization.aidl.IIsolatedModelService;
import android.adservices.ondevicepersonalization.aidl.IIsolatedModelServiceCallback;
import android.os.Bundle;
-import android.os.OutcomeReceiver;
import android.os.RemoteException;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import com.android.ondevicepersonalization.testing.utils.ResultReceiver;
+
import com.google.common.util.concurrent.MoreExecutors;
import org.junit.Before;
@@ -40,7 +42,6 @@
import org.junit.runner.RunWith;
import java.util.HashMap;
-import java.util.concurrent.CountDownLatch;
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -76,13 +77,11 @@
new InferenceOutput.Builder().setDataOutputs(outputData).build())
.build();
- var callback = new MyTestCallback();
+ var callback = new ResultReceiver<InferenceOutput>();
mModelManager.run(inferenceContext, MoreExecutors.directExecutor(), callback);
- callback.mLatch.await();
- assertTrue(mRunInferenceCalled);
- assertNotNull(callback.mInferenceOutput);
- float[] value = (float[]) callback.mInferenceOutput.getDataOutputs().get(0);
+ assertTrue(callback.isSuccess());
+ float[] value = (float[]) callback.getResult().getDataOutputs().get(0);
assertEquals(value[0], 5.0f, 0.01f);
}
@@ -100,20 +99,22 @@
new InferenceOutput.Builder().setDataOutputs(outputData).build())
.build();
- var callback = new MyTestCallback();
+ var callback = new ResultReceiver<InferenceOutput>();
mModelManager.run(inferenceContext, MoreExecutors.directExecutor(), callback);
- callback.mLatch.await();
- assertTrue(callback.mError);
+ assertTrue(callback.isError());
+ OnDevicePersonalizationException exception =
+ (OnDevicePersonalizationException) callback.getException();
+ assertThat(exception.getErrorCode())
+ .isEqualTo(OnDevicePersonalizationException.ERROR_INFERENCE_MODEL_NOT_FOUND);
}
@Test
public void runInference_contextNull_throw() {
+ var callback = new ResultReceiver<InferenceOutput>();
assertThrows(
NullPointerException.class,
- () ->
- mModelManager.run(
- null, MoreExecutors.directExecutor(), new MyTestCallback()));
+ () -> mModelManager.run(null, MoreExecutors.directExecutor(), callback));
}
@Test
@@ -130,29 +131,13 @@
new InferenceOutput.Builder().setDataOutputs(outputData).build())
.build();
- var callback = new MyTestCallback();
+ var callback = new ResultReceiver<InferenceOutput>();
mModelManager.run(inferenceContext, MoreExecutors.directExecutor(), callback);
- callback.mLatch.await();
- assertTrue(callback.mError);
- }
-
- public class MyTestCallback implements OutcomeReceiver<InferenceOutput, Exception> {
- public boolean mError = false;
- public InferenceOutput mInferenceOutput = null;
- private final CountDownLatch mLatch = new CountDownLatch(1);
-
- @Override
- public void onResult(InferenceOutput result) {
- mInferenceOutput = result;
- mLatch.countDown();
- }
-
- @Override
- public void onError(Exception error) {
- mError = true;
- mLatch.countDown();
- }
+ OnDevicePersonalizationException exception =
+ (OnDevicePersonalizationException) callback.getException();
+ assertThat(exception.getErrorCode())
+ .isEqualTo(OnDevicePersonalizationException.ERROR_INFERENCE_FAILED);
}
class TestIsolatedModelService extends IIsolatedModelService.Stub {
@@ -164,11 +149,11 @@
params.getParcelable(
Constants.EXTRA_INFERENCE_INPUT, InferenceInputParcel.class);
if (inputParcel.getModelId().getKey().equals(INVALID_MODEL_KEY)) {
- callback.onError(Constants.STATUS_INTERNAL_ERROR);
+ callback.onError(OnDevicePersonalizationException.ERROR_INFERENCE_MODEL_NOT_FOUND);
return;
}
if (inputParcel.getModelId().getKey().equals(MISSING_OUTPUT_KEY)) {
- callback.onSuccess(new Bundle());
+ callback.onError(OnDevicePersonalizationException.ERROR_INFERENCE_FAILED);
return;
}
HashMap<Integer, Object> result = new HashMap<>();
@@ -185,6 +170,7 @@
static class TestDataAccessService extends IDataAccessService.Stub {
@Override
public void onRequest(int operation, Bundle params, IDataAccessServiceCallback callback) {}
+
@Override
public void logApiCallStats(int apiName, long latencyMillis, int responseCode) {}
}
diff --git a/tests/frameworktests/src/android/adservices/ondevicepersonalization/OnDevicePersonalizationConfigManagerTest.java b/tests/frameworktests/src/android/adservices/ondevicepersonalization/OnDevicePersonalizationConfigManagerTest.java
index 6832f47..dffa3d1 100644
--- a/tests/frameworktests/src/android/adservices/ondevicepersonalization/OnDevicePersonalizationConfigManagerTest.java
+++ b/tests/frameworktests/src/android/adservices/ondevicepersonalization/OnDevicePersonalizationConfigManagerTest.java
@@ -16,120 +16,28 @@
package android.adservices.ondevicepersonalization;
-import static org.junit.Assert.assertThrows;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.isNull;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
+import static org.junit.Assert.assertTrue;
-import android.adservices.ondevicepersonalization.aidl.IOnDevicePersonalizationConfigService;
-import android.adservices.ondevicepersonalization.aidl.IOnDevicePersonalizationConfigServiceCallback;
-import android.os.OutcomeReceiver;
-import android.os.RemoteException;
+import android.content.Context;
-import com.android.federatedcompute.internal.util.AbstractServiceBinder;
+import androidx.test.core.app.ApplicationProvider;
-import org.junit.Before;
+import com.android.ondevicepersonalization.testing.utils.ResultReceiver;
+
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.mockito.Mock;
-import org.mockito.Mockito;
+import org.junit.runners.JUnit4;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.concurrent.Executor;
-
-@RunWith(Parameterized.class)
-public class OnDevicePersonalizationConfigManagerTest {
-
- @Mock private IOnDevicePersonalizationConfigService mMockConfigService;
-
- private OnDevicePersonalizationConfigManager mConfigManager;
-
- @Parameterized.Parameter(0)
- public String scenario;
-
- @Before
- public void setUp() {
- mMockConfigService = Mockito.mock(IOnDevicePersonalizationConfigService.class);
- TestServiceBinder mTestBinder = new TestServiceBinder(mMockConfigService);
- mConfigManager = new OnDevicePersonalizationConfigManager(mTestBinder);
- }
-
- @Parameterized.Parameters
- public static Collection<Object[]> data() {
- return Arrays.asList(
- new Object[][] {
- {"testSuccess"}, {"testNPE"}, {"testGenericException"},
- });
- }
+@RunWith(JUnit4.class)
+public final class OnDevicePersonalizationConfigManagerTest {
+ private final Context mContext = ApplicationProvider.getApplicationContext();
@Test
- public void testSetPersonalizationStatus() throws RemoteException {
- OutcomeReceiver<Void, Exception> spyCallback = spy(new MyTestCallback());
-
- switch (scenario) {
- case "testSuccess":
- doAnswer(
- invocation -> {
- IOnDevicePersonalizationConfigServiceCallback serviceCallback =
- invocation.getArgument(1);
- serviceCallback.onSuccess();
- return null;
- })
- .when(mMockConfigService)
- .setPersonalizationStatus(anyBoolean(), any());
- mConfigManager.setPersonalizationEnabled(true, Runnable::run, spyCallback);
- verify(spyCallback, times(1)).onResult(isNull());
- break;
- case "testNPE":
- doThrow(new NullPointerException())
- .when(mMockConfigService)
- .setPersonalizationStatus(anyBoolean(), any());
- assertThrows(
- NullPointerException.class,
- () ->
- mConfigManager.setPersonalizationEnabled(
- true, Runnable::run, spyCallback));
- break;
- case "testGenericException":
- doThrow(new RuntimeException())
- .when(mMockConfigService)
- .setPersonalizationStatus(anyBoolean(), any());
- mConfigManager.setPersonalizationEnabled(true, Runnable::run, spyCallback);
- verify(spyCallback, times(1)).onError(any(RuntimeException.class));
- break;
- }
- }
-
- static class TestServiceBinder
- extends AbstractServiceBinder<IOnDevicePersonalizationConfigService> {
- private final IOnDevicePersonalizationConfigService mService;
-
- TestServiceBinder(IOnDevicePersonalizationConfigService service) {
- mService = service;
- }
-
- @Override
- public IOnDevicePersonalizationConfigService getService(Executor executor) {
- return mService;
- }
-
- @Override
- public void unbindFromService() {}
- }
-
- public static class MyTestCallback implements OutcomeReceiver<Void, Exception> {
-
- @Override
- public void onResult(Void result) {}
-
- @Override
- public void onError(Exception error) {}
+ public void testSetPersonalizationStatus() throws Exception {
+ OnDevicePersonalizationConfigManager manager =
+ new OnDevicePersonalizationConfigManager(mContext);
+ ResultReceiver<Void> receiver = new ResultReceiver<>();
+ manager.setPersonalizationEnabled(true, Runnable::run, receiver);
+ assertTrue(receiver.isCalled());
}
}
diff --git a/tests/frameworktests/src/android/adservices/ondevicepersonalization/OnDevicePersonalizationManagerTest.java b/tests/frameworktests/src/android/adservices/ondevicepersonalization/OnDevicePersonalizationManagerTest.java
index 7f73f71..6193c7d 100644
--- a/tests/frameworktests/src/android/adservices/ondevicepersonalization/OnDevicePersonalizationManagerTest.java
+++ b/tests/frameworktests/src/android/adservices/ondevicepersonalization/OnDevicePersonalizationManagerTest.java
@@ -13,10 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package android.adservices.ondevicepersonalization;
-import static org.junit.Assert.assertArrayEquals;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -29,6 +29,7 @@
import android.adservices.ondevicepersonalization.aidl.IRequestSurfacePackageCallback;
import android.content.ComponentName;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.IBinder;
import android.os.PersistableBundle;
@@ -41,6 +42,8 @@
import com.android.compatibility.common.util.ShellUtils;
import com.android.federatedcompute.internal.util.AbstractServiceBinder;
import com.android.ondevicepersonalization.internal.util.ByteArrayParceledSlice;
+import com.android.ondevicepersonalization.internal.util.ExceptionInfo;
+import com.android.ondevicepersonalization.internal.util.LoggerFactory;
import com.android.ondevicepersonalization.internal.util.PersistableBundleUtils;
import com.android.ondevicepersonalization.testing.utils.ResultReceiver;
@@ -48,6 +51,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
+import org.mockito.MockitoAnnotations;
import java.util.Arrays;
import java.util.Collection;
@@ -56,32 +60,39 @@
@RunWith(Parameterized.class)
public final class OnDevicePersonalizationManagerTest {
+ private static final LoggerFactory.Logger sLogger = LoggerFactory.getLogger();
private static final String TAG = "OnDevicePersonalizationManagerTest";
private static final String KEY_OP = "op";
private static final String KEY_STATUS_CODE = "status";
private static final String KEY_SERVICE_ERROR_CODE = "serviceerror";
private static final String KEY_ERROR_MESSAGE = "errormessage";
+ private static final int BEST_VALUE = 10;
+ private static final ComponentName TEST_SERVICE_COMPONENT_NAME =
+ ComponentName.createRelative("com.example.service", ".Example");
private final Context mContext = ApplicationProvider.getApplicationContext();
- private final TestServiceBinder mTestBinder = new TestServiceBinder(
- IOnDevicePersonalizationManagingService.Stub.asInterface(new TestService()));
+ private final TestServiceBinder mTestBinder =
+ new TestServiceBinder(
+ IOnDevicePersonalizationManagingService.Stub.asInterface(new TestService()));
private final OnDevicePersonalizationManager mManager =
new OnDevicePersonalizationManager(mContext, mTestBinder);
- private boolean mLogApiStatsCalled = false;
+
+ private volatile boolean mLogApiStatsCalled = false;
@Parameterized.Parameter(0)
public boolean mIsSipFeatureEnabled;
+ @Parameterized.Parameter(1)
+ public boolean mRunExecuteInIsolatedService;
+
@Parameterized.Parameters
public static Collection<Object[]> data() {
return Arrays.asList(
- new Object[][] {
- {true}, {false}
- }
- );
+ new Object[][] {{true, true}, {true, false}, {false, true}, {false, false}});
}
@Before
- public void setUp() {
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
ShellUtils.runShellCommand(
"device_config put on_device_personalization "
+ "shared_isolated_process_feature_enabled "
@@ -92,17 +103,47 @@
public void testExecuteSuccess() throws Exception {
PersistableBundle params = new PersistableBundle();
params.putString(KEY_OP, "ok");
- var receiver = new ResultReceiver<ExecuteResult>();
- mManager.execute(
- ComponentName.createRelative("com.example.service", ".Example"),
- params,
- Executors.newSingleThreadExecutor(),
- receiver);
+ var receiver = new ResultReceiver();
+
+ runExecute(params, receiver);
+
assertTrue(receiver.isSuccess());
assertFalse(receiver.isError());
assertNotNull(receiver.getResult());
- assertEquals(receiver.getResult().getSurfacePackageToken().getTokenString(), "aaaa");
- assertArrayEquals(receiver.getResult().getOutputData(), new byte[]{1, 2, 3});
+ if (mRunExecuteInIsolatedService) {
+ ExecuteInIsolatedServiceResponse response =
+ (ExecuteInIsolatedServiceResponse) receiver.getResult();
+ assertThat(response.getSurfacePackageToken().getTokenString()).isEqualTo("aaaa");
+ assertThat(response.getBestValue()).isEqualTo(-1);
+ } else {
+ ExecuteResult response = (ExecuteResult) receiver.getResult();
+ assertThat(response.getSurfacePackageToken().getTokenString()).isEqualTo("aaaa");
+ assertThat(response.getOutputData()).isNull();
+ }
+ assertTrue(mLogApiStatsCalled);
+ }
+
+ @Test
+ public void testExecuteSuccessWithBestValueSpec() throws Exception {
+ PersistableBundle bundle = new PersistableBundle();
+ bundle.putString(KEY_OP, "best_value");
+ var receiver = new ResultReceiver<ExecuteInIsolatedServiceResponse>();
+ ExecuteInIsolatedServiceRequest request =
+ new ExecuteInIsolatedServiceRequest.Builder(TEST_SERVICE_COMPONENT_NAME)
+ .setAppParams(bundle)
+ .setOutputSpec(
+ ExecuteInIsolatedServiceRequest.OutputSpec.buildBestValueSpec(100))
+ .build();
+
+ mManager.executeInIsolatedService(request, Executors.newSingleThreadExecutor(), receiver);
+
+ assertTrue(receiver.isSuccess());
+ assertFalse(receiver.isError());
+ assertNotNull(receiver.getResult());
+
+ ExecuteInIsolatedServiceResponse response = receiver.getResult();
+ assertThat(response.getSurfacePackageToken().getTokenString()).isEqualTo("aaaa");
+ assertThat(response.getBestValue()).isEqualTo(BEST_VALUE);
assertTrue(mLogApiStatsCalled);
}
@@ -110,12 +151,10 @@
public void testExecuteUnknownError() throws Exception {
PersistableBundle params = new PersistableBundle();
params.putString(KEY_OP, "error");
- var receiver = new ResultReceiver<ExecuteResult>();
- mManager.execute(
- ComponentName.createRelative("com.example.service", ".Example"),
- params,
- Executors.newSingleThreadExecutor(),
- receiver);
+ var receiver = new ResultReceiver();
+
+ runExecute(params, receiver);
+
assertFalse(receiver.isSuccess());
assertTrue(receiver.isError());
assertTrue(receiver.getException() instanceof IllegalStateException);
@@ -127,12 +166,10 @@
PersistableBundle params = new PersistableBundle();
params.putString(KEY_OP, "error");
params.putInt(KEY_STATUS_CODE, Constants.STATUS_SERVICE_FAILED);
- var receiver = new ResultReceiver<ExecuteResult>();
- mManager.execute(
- ComponentName.createRelative("com.example.service", ".Example"),
- params,
- Executors.newSingleThreadExecutor(),
- receiver);
+ var receiver = new ResultReceiver();
+
+ runExecute(params, receiver);
+
assertFalse(receiver.isSuccess());
assertTrue(receiver.isError());
assertTrue(receiver.getException() instanceof OnDevicePersonalizationException);
@@ -141,23 +178,24 @@
@Test
public void testExecuteErrorWithCode() throws Exception {
+ int isolatedServiceErrorCode = 42;
PersistableBundle params = new PersistableBundle();
params.putString(KEY_OP, "error");
params.putInt(KEY_STATUS_CODE, Constants.STATUS_SERVICE_FAILED);
- params.putInt(KEY_SERVICE_ERROR_CODE, 42);
- var receiver = new ResultReceiver<ExecuteResult>();
- mManager.execute(
- ComponentName.createRelative("com.example.service", ".Example"),
- params,
- Executors.newSingleThreadExecutor(),
- receiver);
+ params.putInt(KEY_SERVICE_ERROR_CODE, isolatedServiceErrorCode);
+ var receiver = new ResultReceiver();
+
+ runExecute(params, receiver);
+
assertFalse(receiver.isSuccess());
assertTrue(receiver.isError());
assertTrue(receiver.getException() instanceof OnDevicePersonalizationException);
- assertEquals(OnDevicePersonalizationException.ERROR_ISOLATED_SERVICE_FAILED,
+ assertEquals(
+ OnDevicePersonalizationException.ERROR_ISOLATED_SERVICE_FAILED,
((OnDevicePersonalizationException) receiver.getException()).getErrorCode());
assertTrue(receiver.getException().getCause() instanceof IsolatedServiceException);
- assertEquals(42,
+ assertEquals(
+ isolatedServiceErrorCode,
((IsolatedServiceException) receiver.getException().getCause()).getErrorCode());
assertTrue(mLogApiStatsCalled);
}
@@ -168,28 +206,148 @@
params.putString(KEY_OP, "error");
params.putInt(KEY_STATUS_CODE, Constants.STATUS_SERVICE_FAILED);
params.putString(KEY_ERROR_MESSAGE, "TestErrorMessage");
- var receiver = new ResultReceiver<ExecuteResult>();
- mManager.execute(
- ComponentName.createRelative("com.example.service", ".Example"),
- params,
- Executors.newSingleThreadExecutor(),
- receiver);
+ var receiver = new ResultReceiver();
+
+ runExecute(params, receiver);
+
assertFalse(receiver.isSuccess());
assertTrue(receiver.isError());
- assertEquals("TestErrorMessage", receiver.getException().getMessage());
+ assertTrue(receiver.getException() instanceof OnDevicePersonalizationException);
+ assertEquals(
+ OnDevicePersonalizationException.ERROR_ISOLATED_SERVICE_FAILED,
+ ((OnDevicePersonalizationException) receiver.getException()).getErrorCode());
+ Throwable cause = receiver.getException().getCause();
+ assertNotNull(cause);
+ assertThat(cause.getMessage()).containsMatch(".*RuntimeException.*TestErrorMessage.*");
assertTrue(mLogApiStatsCalled);
}
@Test
+ public void testExecuteManifestParsingError() throws Exception {
+ // The manifest parsing failure gets translated back to PackageManager.NameNotFound
+ // when the legacy execute API is called. The new execute API returns targeted error code.
+ PersistableBundle params = new PersistableBundle();
+ params.putString(KEY_OP, "error");
+ params.putInt(KEY_STATUS_CODE, Constants.STATUS_MANIFEST_PARSING_FAILED);
+ params.putString(KEY_ERROR_MESSAGE, "Failed parsing manifest");
+ var receiver = new ResultReceiver();
+
+ runExecute(params, receiver);
+
+ assertFalse(receiver.isSuccess());
+ assertTrue(receiver.isError());
+ Throwable cause = receiver.getException().getCause();
+ assertNotNull(cause);
+ assertThat(cause.getMessage()).containsMatch(".*RuntimeException.*parsing.*");
+ assertTrue(mLogApiStatsCalled);
+ if (mRunExecuteInIsolatedService) {
+ assertTrue(receiver.getException() instanceof OnDevicePersonalizationException);
+ assertEquals(
+ OnDevicePersonalizationException.ERROR_ISOLATED_SERVICE_MANIFEST_PARSING_FAILED,
+ ((OnDevicePersonalizationException) receiver.getException()).getErrorCode());
+ } else {
+ assertTrue(receiver.getException() instanceof PackageManager.NameNotFoundException);
+ }
+ }
+
+ @Test
+ public void testExecuteManifestMisconfigurationError() throws Exception {
+ // The manifest misconfigured failure gets translated back to Class not found
+ // when the legacy execute API is used. The new execute API returns the targeted error code.
+ PersistableBundle params = new PersistableBundle();
+ params.putString(KEY_OP, "error");
+ params.putInt(KEY_STATUS_CODE, Constants.STATUS_MANIFEST_MISCONFIGURED);
+ params.putString(KEY_ERROR_MESSAGE, "Failed parsing manifest");
+ var receiver = new ResultReceiver();
+
+ runExecute(params, receiver);
+
+ assertFalse(receiver.isSuccess());
+ assertTrue(receiver.isError());
+ Throwable cause = receiver.getException().getCause();
+ assertNotNull(cause);
+ assertThat(cause.getMessage()).containsMatch(".*RuntimeException.*parsing.*");
+ assertTrue(mLogApiStatsCalled);
+ if (mRunExecuteInIsolatedService) {
+ assertTrue(receiver.getException() instanceof OnDevicePersonalizationException);
+ assertEquals(
+ OnDevicePersonalizationException.ERROR_ISOLATED_SERVICE_MANIFEST_PARSING_FAILED,
+ ((OnDevicePersonalizationException) receiver.getException()).getErrorCode());
+ } else {
+ assertTrue(receiver.getException() instanceof ClassNotFoundException);
+ }
+ }
+
+ @Test
+ public void testExecuteServiceTimeoutError() throws Exception {
+ // The service timeout failure gets exposed via corresponding OdpException
+ // when the new execute API is used.
+ PersistableBundle params = new PersistableBundle();
+ params.putString(KEY_OP, "error");
+ params.putInt(KEY_STATUS_CODE, Constants.STATUS_ISOLATED_SERVICE_TIMEOUT);
+ params.putString(KEY_ERROR_MESSAGE, "Service timeout");
+ var receiver = new ResultReceiver<ExecuteResult>();
+
+ runExecute(params, receiver);
+
+ assertFalse(receiver.isSuccess());
+ assertTrue(receiver.isError());
+ Throwable cause = receiver.getException().getCause();
+ assertNotNull(cause);
+ assertThat(cause.getMessage()).containsMatch(".*RuntimeException.*timeout.*");
+ assertTrue(mLogApiStatsCalled);
+ if (mRunExecuteInIsolatedService) {
+ assertTrue(receiver.getException() instanceof OnDevicePersonalizationException);
+ assertEquals(
+ OnDevicePersonalizationException.ERROR_ISOLATED_SERVICE_TIMEOUT,
+ ((OnDevicePersonalizationException) receiver.getException()).getErrorCode());
+ } else {
+ assertTrue(receiver.getException() instanceof OnDevicePersonalizationException);
+ assertEquals(
+ OnDevicePersonalizationException.ERROR_ISOLATED_SERVICE_FAILED,
+ ((OnDevicePersonalizationException) receiver.getException()).getErrorCode());
+ }
+ }
+
+ @Test
+ public void testExecuteServiceLoadingError() throws Exception {
+ // The service loading failure gets exposed via corresponding OdpException
+ // when the new execute API is used.
+ PersistableBundle params = new PersistableBundle();
+ params.putString(KEY_OP, "error");
+ params.putInt(KEY_STATUS_CODE, Constants.STATUS_ISOLATED_SERVICE_LOADING_FAILED);
+ params.putString(KEY_ERROR_MESSAGE, "Service loading failed.");
+ var receiver = new ResultReceiver<ExecuteResult>();
+
+ runExecute(params, receiver);
+
+ assertFalse(receiver.isSuccess());
+ assertTrue(receiver.isError());
+ Throwable cause = receiver.getException().getCause();
+ assertNotNull(cause);
+ assertThat(cause.getMessage()).containsMatch(".*RuntimeException.*loading.*");
+ assertTrue(mLogApiStatsCalled);
+ if (mRunExecuteInIsolatedService) {
+ assertTrue(receiver.getException() instanceof OnDevicePersonalizationException);
+ assertEquals(
+ OnDevicePersonalizationException.ERROR_ISOLATED_SERVICE_LOADING_FAILED,
+ ((OnDevicePersonalizationException) receiver.getException()).getErrorCode());
+ } else {
+ assertTrue(receiver.getException() instanceof OnDevicePersonalizationException);
+ assertEquals(
+ OnDevicePersonalizationException.ERROR_ISOLATED_SERVICE_FAILED,
+ ((OnDevicePersonalizationException) receiver.getException()).getErrorCode());
+ }
+ }
+
+ @Test
public void testExecuteCatchesIaeFromService() throws Exception {
PersistableBundle params = new PersistableBundle();
params.putString(KEY_OP, "iae");
- var receiver = new ResultReceiver<ExecuteResult>();
- mManager.execute(
- ComponentName.createRelative("com.example.service", ".Example"),
- params,
- Executors.newSingleThreadExecutor(),
- receiver);
+ var receiver = new ResultReceiver();
+
+ runExecute(params, receiver);
+
assertFalse(receiver.isSuccess());
assertTrue(receiver.isError());
assertTrue(receiver.getException() instanceof IllegalArgumentException);
@@ -200,12 +358,10 @@
public void testExecuteCatchesNpeFromService() throws Exception {
PersistableBundle params = new PersistableBundle();
params.putString(KEY_OP, "npe");
- var receiver = new ResultReceiver<ExecuteResult>();
- mManager.execute(
- ComponentName.createRelative("com.example.service", ".Example"),
- params,
- Executors.newSingleThreadExecutor(),
- receiver);
+ var receiver = new ResultReceiver();
+
+ runExecute(params, receiver);
+
assertFalse(receiver.isSuccess());
assertTrue(receiver.isError());
assertTrue(receiver.getException() instanceof NullPointerException);
@@ -216,19 +372,34 @@
public void testExecuteCatchesOtherExceptions() throws Exception {
PersistableBundle params = new PersistableBundle();
params.putString(KEY_OP, "ise");
- var receiver = new ResultReceiver<ExecuteResult>();
- mManager.execute(
- ComponentName.createRelative("com.example.service", ".Example"),
- params,
- Executors.newSingleThreadExecutor(),
- receiver);
+ var receiver = new ResultReceiver();
+
+ runExecute(params, receiver);
+
assertFalse(receiver.isSuccess());
assertTrue(receiver.isError());
assertTrue(receiver.getException() instanceof IllegalStateException);
assertTrue(mLogApiStatsCalled);
}
- class TestService extends IOnDevicePersonalizationManagingService.Stub {
+ private void runExecute(PersistableBundle params, ResultReceiver receiver) {
+ if (mRunExecuteInIsolatedService) {
+ ExecuteInIsolatedServiceRequest request =
+ new ExecuteInIsolatedServiceRequest.Builder(TEST_SERVICE_COMPONENT_NAME)
+ .setAppParams(params)
+ .build();
+ mManager.executeInIsolatedService(
+ request, Executors.newSingleThreadExecutor(), receiver);
+ } else {
+ mManager.execute(
+ TEST_SERVICE_COMPONENT_NAME,
+ params,
+ Executors.newSingleThreadExecutor(),
+ receiver);
+ }
+ }
+
+ private class TestService extends IOnDevicePersonalizationManagingService.Stub {
@Override
public String getVersion() {
return "1.0";
@@ -240,13 +411,16 @@
ComponentName handler,
Bundle wrappedParams,
CallerMetadata metadata,
+ ExecuteOptionsParcel options,
IExecuteCallback callback) {
try {
PersistableBundle params;
String op;
try {
- ByteArrayParceledSlice paramsBuffer = wrappedParams.getParcelable(
- Constants.EXTRA_APP_PARAMS_SERIALIZED, ByteArrayParceledSlice.class);
+ ByteArrayParceledSlice paramsBuffer =
+ wrappedParams.getParcelable(
+ Constants.EXTRA_APP_PARAMS_SERIALIZED,
+ ByteArrayParceledSlice.class);
params = PersistableBundleUtils.fromByteArray(paramsBuffer.getByteArray());
op = params.getString(KEY_OP);
} catch (Exception e) {
@@ -256,18 +430,33 @@
if (op.equals("ok")) {
Bundle bundle = new Bundle();
bundle.putString(Constants.EXTRA_SURFACE_PACKAGE_TOKEN_STRING, "aaaa");
- bundle.putByteArray(Constants.EXTRA_OUTPUT_DATA, new byte[]{1, 2, 3});
- callback.onSuccess(bundle,
- new CalleeMetadata.Builder().setCallbackInvokeTimeMillis(
- SystemClock.elapsedRealtime()).build());
+ callback.onSuccess(
+ bundle,
+ new CalleeMetadata.Builder()
+ .setCallbackInvokeTimeMillis(SystemClock.elapsedRealtime())
+ .build());
+ } else if (options.getOutputType()
+ == ExecuteInIsolatedServiceRequest.OutputSpec.OUTPUT_TYPE_BEST_VALUE) {
+ Bundle bundle = new Bundle();
+ bundle.putString(Constants.EXTRA_SURFACE_PACKAGE_TOKEN_STRING, "aaaa");
+ bundle.putInt(Constants.EXTRA_OUTPUT_BEST_VALUE, BEST_VALUE);
+ callback.onSuccess(
+ bundle,
+ new CalleeMetadata.Builder()
+ .setCallbackInvokeTimeMillis(SystemClock.elapsedRealtime())
+ .build());
} else if (op.equals("error")) {
- int statusCode = params.getInt(KEY_STATUS_CODE,
- Constants.STATUS_INTERNAL_ERROR);
+ int statusCode =
+ params.getInt(KEY_STATUS_CODE, Constants.STATUS_INTERNAL_ERROR);
int serviceErrorCode = params.getInt(KEY_SERVICE_ERROR_CODE, 0);
String errorMessage = params.getString(KEY_ERROR_MESSAGE);
- callback.onError(statusCode, serviceErrorCode, errorMessage,
- new CalleeMetadata.Builder().setCallbackInvokeTimeMillis(
- SystemClock.elapsedRealtime()).build());
+ callback.onError(
+ statusCode,
+ serviceErrorCode,
+ ExceptionInfo.toByteArray(new RuntimeException(errorMessage), 3),
+ new CalleeMetadata.Builder()
+ .setCallbackInvokeTimeMillis(SystemClock.elapsedRealtime())
+ .build());
} else if (op.equals("iae")) {
throw new IllegalArgumentException();
} else if (op.equals("npe")) {
@@ -318,9 +507,10 @@
}
}
- class TestServiceBinder
+ private static class TestServiceBinder
extends AbstractServiceBinder<IOnDevicePersonalizationManagingService> {
private final IOnDevicePersonalizationManagingService mService;
+
TestServiceBinder(IOnDevicePersonalizationManagingService service) {
mService = service;
}
diff --git a/tests/frameworktests/src/android/adservices/ondevicepersonalization/OnDevicePersonalizationSystemEventManagerTest.java b/tests/frameworktests/src/android/adservices/ondevicepersonalization/OnDevicePersonalizationSystemEventManagerTest.java
index 4255cf9..3698076 100644
--- a/tests/frameworktests/src/android/adservices/ondevicepersonalization/OnDevicePersonalizationSystemEventManagerTest.java
+++ b/tests/frameworktests/src/android/adservices/ondevicepersonalization/OnDevicePersonalizationSystemEventManagerTest.java
@@ -141,6 +141,7 @@
ComponentName handler,
Bundle params,
CallerMetadata metadata,
+ ExecuteOptionsParcel options,
IExecuteCallback callback) {
throw new UnsupportedOperationException();
}
diff --git a/tests/frameworktests/src/android/adservices/ondevicepersonalization/RemoteDataTest.java b/tests/frameworktests/src/android/adservices/ondevicepersonalization/RemoteDataTest.java
index 1a3b00d..2334f3f 100644
--- a/tests/frameworktests/src/android/adservices/ondevicepersonalization/RemoteDataTest.java
+++ b/tests/frameworktests/src/android/adservices/ondevicepersonalization/RemoteDataTest.java
@@ -16,8 +16,9 @@
package android.adservices.ondevicepersonalization;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import android.adservices.ondevicepersonalization.aidl.IDataAccessService;
@@ -30,12 +31,14 @@
import com.android.ondevicepersonalization.internal.util.ByteArrayParceledSlice;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
+import java.util.concurrent.CountDownLatch;
/**
* Unit Tests of RemoteData API.
@@ -43,35 +46,62 @@
@SmallTest
@RunWith(AndroidJUnit4.class)
public class RemoteDataTest {
- KeyValueStore mRemoteData = new RemoteDataImpl(
- IDataAccessService.Stub.asInterface(
- new RemoteDataService()));
+
+ private KeyValueStore mRemoteData;
+ private RemoteDataService mRemoteDataService;
+
+ @Before
+ public void setup() {
+ mRemoteDataService = new RemoteDataService();
+ mRemoteData = new RemoteDataImpl(IDataAccessService.Stub.asInterface(mRemoteDataService));
+ }
@Test
public void testLookupSuccess() throws Exception {
assertArrayEquals(new byte[] {1, 2, 3}, mRemoteData.get("a"));
assertArrayEquals(new byte[] {7, 8, 9}, mRemoteData.get("c"));
assertNull(mRemoteData.get("e"));
+
+ mRemoteDataService.mLatch.await();
+ assertThat(mRemoteDataService.mResponseCode).isEqualTo(Constants.STATUS_SUCCESS);
}
@Test
- public void testLookupError() {
+ public void testLookupError() throws Exception {
// Triggers an expected error in the mock service.
assertNull(mRemoteData.get("z"));
+
+ mRemoteDataService.mLatch.await();
+ assertThat(mRemoteDataService.mResponseCode).isEqualTo(Constants.STATUS_INTERNAL_ERROR);
}
@Test
- public void testKeysetSuccess() {
+ public void testLookupEmptyResult() throws Exception {
+ // Triggers an expected error in the mock service.
+ assertNull(mRemoteData.get("empty"));
+
+ mRemoteDataService.mLatch.await();
+ assertThat(mRemoteDataService.mResponseCode)
+ .isEqualTo(Constants.STATUS_SUCCESS_EMPTY_RESULT);
+ }
+
+ @Test
+ public void testKeysetSuccess() throws Exception {
Set<String> expectedResult = new HashSet<>();
expectedResult.add("a");
expectedResult.add("b");
expectedResult.add("c");
- assertEquals(expectedResult, mRemoteData.keySet());
+ assertThat(expectedResult).isEqualTo(mRemoteData.keySet());
+
+ mRemoteDataService.mLatch.await();
+ assertThat(mRemoteDataService.mResponseCode).isEqualTo(Constants.STATUS_SUCCESS);
}
public static class RemoteDataService extends IDataAccessService.Stub {
HashMap<String, byte[]> mContents = new HashMap<String, byte[]>();
+ int mResponseCode;
+ CountDownLatch mLatch = new CountDownLatch(1);
public RemoteDataService() {
mContents.put("a", new byte[] {1, 2, 3});
@@ -105,6 +135,15 @@
if (key == null) {
throw new NullPointerException("key");
}
+ if (key.equals("empty")) {
+ // Raise expected error.
+ try {
+ callback.onSuccess(null);
+ } catch (RemoteException e) {
+ // Ignored.
+ }
+ return;
+ }
if (key.equals("z")) {
// Raise expected error.
@@ -129,6 +168,9 @@
}
@Override
- public void logApiCallStats(int apiName, long latencyMillis, int responseCode) {}
+ public void logApiCallStats(int apiName, long latencyMillis, int responseCode) {
+ mLatch.countDown();
+ mResponseCode = responseCode;
+ }
}
}
diff --git a/tests/frameworktests/src/com/android/ondevicepersonalization/internal/util/ExceptionInfoTest.java b/tests/frameworktests/src/com/android/ondevicepersonalization/internal/util/ExceptionInfoTest.java
new file mode 100644
index 0000000..0971b49
--- /dev/null
+++ b/tests/frameworktests/src/com/android/ondevicepersonalization/internal/util/ExceptionInfoTest.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ondevicepersonalization.internal.util;
+
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.matchesPattern;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+/**
+ * Unit Tests of ExceptionInfo.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ExceptionInfoTest {
+ @Test
+ public void testExceptionRoundTrip() {
+ Exception e = createException();
+ assertNotNull(e);
+ byte[] serialized = ExceptionInfo.toByteArray(e, 5);
+ Exception e2 = ExceptionInfo.fromByteArray(serialized);
+ assertNotNull(e2);
+ assertThat(e2.getMessage(), matchesPattern(".*IllegalStateException.*Exception2.*"));
+ assertNotNull(e2.getCause());
+ assertThat(e2.getCause().getMessage(),
+ matchesPattern(".*NullPointerException.*Exception1.*"));
+ String stackTrace = getStackTrace(e2);
+ assertThat(stackTrace, containsString("function2"));
+ assertThat(stackTrace, containsString("function1"));
+ }
+
+ private Exception createException() {
+ try {
+ function2();
+ } catch (Exception e) {
+ return e;
+ }
+ return null;
+ }
+
+ private static String getStackTrace(Throwable t) {
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+ t.printStackTrace(pw);
+ pw.flush();
+ return sw.toString();
+ }
+
+ private static void function1() {
+ throw new NullPointerException("Exception1");
+ }
+
+ private static void function2() {
+ try {
+ function1();
+ } catch (Exception e) {
+ throw new IllegalStateException("Exception2", e);
+ }
+ }
+}
diff --git a/tests/manualtests/Android.bp b/tests/manualtests/Android.bp
index d3ce42a..0cea200 100644
--- a/tests/manualtests/Android.bp
+++ b/tests/manualtests/Android.bp
@@ -43,6 +43,7 @@
"androidx.test.ext.junit",
"androidx.test.ext.truth",
"androidx.test.rules",
+ "federated-compute-java-proto-lite",
"kotlin-stdlib",
"kotlin-test",
"kotlinx-coroutines-android",
diff --git a/tests/manualtests/AndroidManifest.xml b/tests/manualtests/AndroidManifest.xml
index 43c4d6d..c05685a 100644
--- a/tests/manualtests/AndroidManifest.xml
+++ b/tests/manualtests/AndroidManifest.xml
@@ -21,7 +21,8 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
- <application android:name="com.android.ondevicepersonalization.services.OnDevicePersonalizationApplication"
+ <application android:debuggable="true"
+ android:name="com.android.ondevicepersonalization.services.OnDevicePersonalizationApplication"
android:label="OnDevicePersonalizationManualTests">
<uses-library android:name="android.test.runner"/>
<property android:name="android.ondevicepersonalization.ON_DEVICE_PERSONALIZATION_CONFIG"
diff --git a/tests/perftests/scenarios/tests/AndroidManifest.xml b/tests/perftests/scenarios/tests/AndroidManifest.xml
index 350196d..2139198 100644
--- a/tests/perftests/scenarios/tests/AndroidManifest.xml
+++ b/tests/perftests/scenarios/tests/AndroidManifest.xml
@@ -45,7 +45,7 @@
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
- <application android:label="OnDevicePersonalizationPerfScenariosTests">
+ <application android:debuggable="true" android:label="OnDevicePersonalizationPerfScenariosTests">
<uses-library android:name="android.test.runner"/>
</application>
diff --git a/tests/plugintests/AndroidManifest.xml b/tests/plugintests/AndroidManifest.xml
index d298988..e2bd0cd 100644
--- a/tests/plugintests/AndroidManifest.xml
+++ b/tests/plugintests/AndroidManifest.xml
@@ -17,7 +17,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.ondevicepersonalization.plugintests">
- <application android:label="OnDevicePersonalizationPluginTests">
+ <application android:debuggable="true" android:label="OnDevicePersonalizationPluginTests">
<uses-library android:name="android.test.runner" />
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/tests/servicetests/Android.bp b/tests/servicetests/Android.bp
index 07f0193..da85380 100644
--- a/tests/servicetests/Android.bp
+++ b/tests/servicetests/Android.bp
@@ -47,6 +47,7 @@
"androidx.test.ext.junit",
"androidx.test.ext.truth",
"androidx.test.rules",
+ "federated-compute-java-proto-lite",
"mockito-target-extended-minus-junit4",
"kotlin-stdlib",
"kotlin-test",
@@ -61,6 +62,7 @@
"compatibility-device-util-axt",
"tensorflowlite_java",
"adservices-shared-spe",
+ "ondevicepersonalization-testing-utils",
],
sdk_version: "module_current",
target_sdk_version: "current",
diff --git a/tests/servicetests/AndroidManifest.xml b/tests/servicetests/AndroidManifest.xml
index 6fae2d3..014f9d9 100644
--- a/tests/servicetests/AndroidManifest.xml
+++ b/tests/servicetests/AndroidManifest.xml
@@ -25,6 +25,8 @@
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<uses-permission android:name="android.permission.INTERNAL_SYSTEM_WINDOW"/>
+ <!-- Permissions required for reading device configs -->
+ <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
<!-- Required for reading and writing device configs -->
@@ -45,11 +47,6 @@
<action android:name="android.OnDevicePersonalizationService" />
</intent-filter>
</service>
- <service android:name="com.android.ondevicepersonalization.services.OnDevicePersonalizationConfigServiceImpl" android:exported="true" >
- <intent-filter>
- <action android:name="android.OnDevicePersonalizationConfigService" />
- </intent-filter>
- </service>
<service android:name="com.android.ondevicepersonalization.services.OnDevicePersonalizationDebugServiceImpl" android:exported="true" >
<intent-filter>
<action android:name="android.OnDevicePersonalizationService" />
@@ -80,6 +77,10 @@
android:exported="false"
android:permission="android.permission.BIND_JOB_SERVICE">
</service>
+ <service android:name="com.android.ondevicepersonalization.services.data.errors.AggregateErrorDataReportingService"
+ android:exported="false"
+ android:permission="android.permission.BIND_JOB_SERVICE">
+ </service>
<service
android:name="com.android.ondevicepersonalization.services.federatedcompute.OdpExampleStoreService"
android:enabled="true"
diff --git a/tests/servicetests/src/com/android/ondevicepersonalization/services/OnDevicePersonalizationConfigServiceTest.java b/tests/servicetests/src/com/android/ondevicepersonalization/services/OnDevicePersonalizationConfigServiceTest.java
deleted file mode 100644
index fa54921..0000000
--- a/tests/servicetests/src/com/android/ondevicepersonalization/services/OnDevicePersonalizationConfigServiceTest.java
+++ /dev/null
@@ -1,251 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ondevicepersonalization.services;
-
-import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__ON_DEVICE_PERSONALIZATION_ERROR;
-import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__ODP;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertThrows;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.ArgumentMatchers.isA;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.adservices.ondevicepersonalization.Constants;
-import android.adservices.ondevicepersonalization.aidl.IOnDevicePersonalizationConfigServiceCallback;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.os.Build;
-import android.os.IBinder;
-
-import androidx.test.core.app.ApplicationProvider;
-import androidx.test.filters.SdkSuppress;
-import androidx.test.rule.ServiceTestRule;
-
-import com.android.modules.utils.testing.ExtendedMockitoRule;
-import com.android.modules.utils.testing.ExtendedMockitoRule.MockStatic;
-import com.android.ondevicepersonalization.services.data.user.RawUserData;
-import com.android.ondevicepersonalization.services.data.user.UserDataCollector;
-import com.android.ondevicepersonalization.services.data.user.UserPrivacyStatus;
-import com.android.ondevicepersonalization.services.statsd.errorlogging.ClientErrorLogger;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.mockito.Mock;
-import org.mockito.quality.Strictness;
-
-import java.util.TimeZone;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-
-@RunWith(JUnit4.class)
-@MockStatic(ClientErrorLogger.class)
-public class OnDevicePersonalizationConfigServiceTest {
- @Rule
- public final ExtendedMockitoRule extendedMockitoRule =
- new ExtendedMockitoRule.Builder(this).setStrictness(Strictness.LENIENT).build();
-
- @Rule
- public final ServiceTestRule serviceRule = new ServiceTestRule();
- private Context mContext = spy(ApplicationProvider.getApplicationContext());
- private OnDevicePersonalizationConfigServiceDelegate mBinder;
- private UserPrivacyStatus mUserPrivacyStatus;
- private RawUserData mUserData;
- private UserDataCollector mUserDataCollector;
- @Mock
- private ClientErrorLogger mMockClientErrorLogger;
-
- @Before
- public void setup() throws Exception {
-
- PhFlagsTestUtil.setUpDeviceConfigPermissions();
- PhFlagsTestUtil.disableGlobalKillSwitch();
- PhFlagsTestUtil.disablePersonalizationStatusOverride();
- when(mContext.checkCallingPermission(anyString()))
- .thenReturn(PackageManager.PERMISSION_GRANTED);
- mBinder = new OnDevicePersonalizationConfigServiceDelegate(mContext);
- mUserPrivacyStatus = UserPrivacyStatus.getInstanceForTest();
- mUserPrivacyStatus.setPersonalizationStatusEnabled(false);
- mUserData = RawUserData.getInstance();
- TimeZone pstTime = TimeZone.getTimeZone("GMT-08:00");
- TimeZone.setDefault(pstTime);
- mUserDataCollector = UserDataCollector.getInstanceForTest(mContext);
- when(ClientErrorLogger.getInstance()).thenReturn(mMockClientErrorLogger);
- }
-
- @Test
- public void testThrowIfGlobalKillSwitchEnabled() throws Exception {
- PhFlagsTestUtil.enableGlobalKillSwitch();
- try {
- assertThrows(
- IllegalStateException.class,
- () ->
- mBinder.setPersonalizationStatus(true, null)
- );
- } finally {
- PhFlagsTestUtil.disableGlobalKillSwitch();
- }
- }
-
- @Test
- public void testSetPersonalizationStatusNoCallingPermission() throws Exception {
- when(mContext.checkCallingPermission(anyString()))
- .thenReturn(PackageManager.PERMISSION_DENIED);
- assertThrows(SecurityException.class, () -> {
- mBinder.setPersonalizationStatus(true, null);
- });
- }
-
- @Test
- public void testSetPersonalizationStatusChanged() throws Exception {
- assertFalse(mUserPrivacyStatus.isPersonalizationStatusEnabled());
- populateUserData();
- assertNotEquals(0, mUserData.utcOffset);
- assertTrue(mUserDataCollector.isInitialized());
-
- CountDownLatch latch = new CountDownLatch(1);
- mBinder.setPersonalizationStatus(true,
- new IOnDevicePersonalizationConfigServiceCallback.Stub() {
- @Override
- public void onSuccess() {
- latch.countDown();
- }
-
- @Override
- public void onFailure(int errorCode) {
- latch.countDown();
- }
- });
-
- latch.await();
- assertTrue(mUserPrivacyStatus.isPersonalizationStatusEnabled());
-
- assertEquals(0, mUserData.utcOffset);
- assertFalse(mUserDataCollector.isInitialized());
- }
-
- @Test
- public void testSetPersonalizationStatusIfCallbackMissing() throws Exception {
- assertThrows(NullPointerException.class, () -> {
- mBinder.setPersonalizationStatus(true, null);
- });
- }
-
- @Test
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
- public void testSetPersonalizationStatusThrowsRuntimeException() throws Exception {
- when(mContext.getSystemService(any(Class.class))).thenThrow(RuntimeException.class);
- CountDownLatch latch = new CountDownLatch(1);
- TestCallback callback = new TestCallback(latch);
-
- mBinder.setPersonalizationStatus(true, callback);
-
- assertTrue(latch.await(10000, TimeUnit.MILLISECONDS));
- assertEquals(Constants.STATUS_INTERNAL_ERROR, callback.getErrCode());
- verify(mMockClientErrorLogger)
- .logErrorWithExceptionInfo(
- isA(RuntimeException.class),
- eq(AD_SERVICES_ERROR_REPORTED__ERROR_CODE__ON_DEVICE_PERSONALIZATION_ERROR),
- eq(AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__ODP));
- }
-
- @Test
- public void testSetPersonalizationStatusNoOps() throws Exception {
- mUserPrivacyStatus.setPersonalizationStatusEnabled(true);
-
- populateUserData();
- assertNotEquals(0, mUserData.utcOffset);
- int utcOffset = mUserData.utcOffset;
- assertTrue(mUserDataCollector.isInitialized());
-
- CountDownLatch latch = new CountDownLatch(1);
- mBinder.setPersonalizationStatus(true,
- new IOnDevicePersonalizationConfigServiceCallback.Stub() {
- @Override
- public void onSuccess() {
- latch.countDown();
- }
-
- @Override
- public void onFailure(int errorCode) {
- latch.countDown();
- }
- });
-
- latch.await();
-
- assertTrue(mUserPrivacyStatus.isPersonalizationStatusEnabled());
- // Adult data should not be roll-back'ed
- assertEquals(utcOffset, mUserData.utcOffset);
- assertTrue(mUserDataCollector.isInitialized());
- }
-
- @Test
- public void testWithBoundService() throws TimeoutException {
- Intent serviceIntent = new Intent(mContext,
- OnDevicePersonalizationConfigServiceImpl.class);
- IBinder binder = serviceRule.bindService(serviceIntent);
- assertTrue(binder instanceof OnDevicePersonalizationConfigServiceDelegate);
- }
-
- @After
- public void tearDown() throws Exception {
- mUserDataCollector.clearUserData(mUserData);
- mUserDataCollector.clearMetadata();
- }
-
- private void populateUserData() {
- mUserDataCollector.updateUserData(mUserData);
- }
-
- class TestCallback extends IOnDevicePersonalizationConfigServiceCallback.Stub {
-
- int mErrCode;
- CountDownLatch mLatch;
-
- TestCallback(CountDownLatch latch) {
- this.mLatch = latch;
- }
-
- @Override
- public void onSuccess() {
- }
-
- @Override
- public void onFailure(int errorCode) {
- mErrCode = errorCode;
- mLatch.countDown();
- }
-
- public int getErrCode() {
- return mErrCode;
- }
- }
-}
diff --git a/tests/servicetests/src/com/android/ondevicepersonalization/services/OnDevicePersonalizationManagingServiceTest.java b/tests/servicetests/src/com/android/ondevicepersonalization/services/OnDevicePersonalizationManagingServiceTest.java
index c9d84aa..f6deec1 100644
--- a/tests/servicetests/src/com/android/ondevicepersonalization/services/OnDevicePersonalizationManagingServiceTest.java
+++ b/tests/servicetests/src/com/android/ondevicepersonalization/services/OnDevicePersonalizationManagingServiceTest.java
@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package com.android.ondevicepersonalization.services;
import static android.adservices.ondevicepersonalization.OnDevicePersonalizationPermissions.NOTIFY_MEASUREMENT_EVENT;
@@ -36,6 +35,8 @@
import android.adservices.ondevicepersonalization.CalleeMetadata;
import android.adservices.ondevicepersonalization.CallerMetadata;
import android.adservices.ondevicepersonalization.Constants;
+import android.adservices.ondevicepersonalization.ExecuteInIsolatedServiceRequest;
+import android.adservices.ondevicepersonalization.ExecuteOptionsParcel;
import android.adservices.ondevicepersonalization.aidl.IExecuteCallback;
import android.adservices.ondevicepersonalization.aidl.IRegisterMeasurementEventCallback;
import android.adservices.ondevicepersonalization.aidl.IRequestSurfacePackageCallback;
@@ -60,6 +61,7 @@
import com.android.ondevicepersonalization.services.data.user.UserDataCollectionJobService;
import com.android.ondevicepersonalization.services.data.user.UserPrivacyStatus;
import com.android.ondevicepersonalization.services.download.mdd.MobileDataDownloadFactory;
+import com.android.ondevicepersonalization.services.enrollment.PartnerEnrollmentChecker;
import com.android.ondevicepersonalization.services.maintenance.OnDevicePersonalizationMaintenanceJobService;
import com.google.android.libraries.mobiledatadownload.MobileDataDownload;
@@ -70,7 +72,6 @@
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.mockito.Mock;
-import org.mockito.Spy;
import org.mockito.quality.Strictness;
import java.util.concurrent.CountDownLatch;
@@ -78,40 +79,38 @@
@RunWith(JUnit4.class)
public class OnDevicePersonalizationManagingServiceTest {
- @Rule
- public final ServiceTestRule serviceRule = new ServiceTestRule();
+ @Rule public final ServiceTestRule serviceRule = new ServiceTestRule();
private final Context mContext = spy(ApplicationProvider.getApplicationContext());
- private OnDevicePersonalizationManagingServiceDelegate mService =
- new OnDevicePersonalizationManagingServiceDelegate(mContext);
-
- @Mock
- private UserPrivacyStatus mUserPrivacyStatus;
+ private OnDevicePersonalizationManagingServiceDelegate mService;
+ @Mock private UserPrivacyStatus mUserPrivacyStatus;
@Mock private MobileDataDownload mMockMdd;
- @Spy
- private Flags mSpyFlags = spy(FlagsFactory.getFlags());
+ @Mock private Flags mMockFlags;
+
@Rule
- public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
- .mockStatic(FlagsFactory.class)
- .spyStatic(UserPrivacyStatus.class)
- .spyStatic(DeviceUtils.class)
- .spyStatic(OnDevicePersonalizationMaintenanceJobService.class)
- .spyStatic(UserDataCollectionJobService.class)
- .spyStatic(MobileDataDownloadFactory.class)
- .setStrictness(Strictness.LENIENT)
- .build();
+ public final ExtendedMockitoRule mExtendedMockitoRule =
+ new ExtendedMockitoRule.Builder(this)
+ .spyStatic(FlagsFactory.class)
+ .spyStatic(UserPrivacyStatus.class)
+ .spyStatic(DeviceUtils.class)
+ .spyStatic(OnDevicePersonalizationMaintenanceJobService.class)
+ .spyStatic(UserDataCollectionJobService.class)
+ .spyStatic(MobileDataDownloadFactory.class)
+ .spyStatic(PartnerEnrollmentChecker.class)
+ .setStrictness(Strictness.LENIENT)
+ .build();
@Before
public void setup() throws Exception {
- ExtendedMockito.doReturn(mSpyFlags).when(FlagsFactory::getFlags);
- when(mSpyFlags.getGlobalKillSwitch()).thenReturn(false);
- PhFlagsTestUtil.setUpDeviceConfigPermissions();
+ ExtendedMockito.doReturn(mMockFlags).when(FlagsFactory::getFlags);
+ mService = new OnDevicePersonalizationManagingServiceDelegate(mContext);
+ when(mMockFlags.getGlobalKillSwitch()).thenReturn(false);
+ when(mMockFlags.getMaxIntValuesLimit()).thenReturn(100);
ExtendedMockito.doReturn(true).when(() -> DeviceUtils.isOdpSupported(any()));
ExtendedMockito.doReturn(mUserPrivacyStatus).when(UserPrivacyStatus::getInstance);
doReturn(true).when(mUserPrivacyStatus).isMeasurementEnabled();
doReturn(true).when(mUserPrivacyStatus).isProtectedAudienceEnabled();
when(mContext.checkCallingPermission(NOTIFY_MEASUREMENT_EVENT))
.thenReturn(PackageManager.PERMISSION_GRANTED);
-
ExtendedMockito.doReturn(SCHEDULING_RESULT_CODE_SUCCESSFUL)
.when(
() ->
@@ -120,6 +119,10 @@
ExtendedMockito.doReturn(1).when(() -> UserDataCollectionJobService.schedule(any()));
ExtendedMockito.doReturn(mMockMdd).when(() -> MobileDataDownloadFactory.getMdd(any()));
doReturn(immediateVoidFuture()).when(mMockMdd).schedulePeriodicBackgroundTasks();
+ ExtendedMockito.doReturn(true)
+ .when(() -> PartnerEnrollmentChecker.isCallerAppEnrolled(any()));
+ ExtendedMockito.doReturn(true)
+ .when(() -> PartnerEnrollmentChecker.isIsolatedServiceEnrolled(any()));
}
@Test
@@ -129,7 +132,7 @@
@Test
public void testEnabledGlobalKillSwitchOnExecute() throws Exception {
- when(mSpyFlags.getGlobalKillSwitch()).thenReturn(true);
+ when(mMockFlags.getGlobalKillSwitch()).thenReturn(true);
var callback = new ExecuteCallback();
assertThrows(
IllegalStateException.class,
@@ -141,8 +144,8 @@
"com.test.TestPersonalizationHandler"),
createWrappedAppParams(),
new CallerMetadata.Builder().build(),
- callback
- ));
+ ExecuteOptionsParcel.DEFAULT,
+ callback));
}
@Test
@@ -158,8 +161,8 @@
"com.test.TestPersonalizationHandler"),
createWrappedAppParams(),
new CallerMetadata.Builder().build(),
- new ExecuteCallback()
- ));
+ ExecuteOptionsParcel.DEFAULT,
+ new ExecuteCallback()));
}
@Test
@@ -167,16 +170,53 @@
var callback = new ExecuteCallback();
mService.execute(
mContext.getPackageName(),
- new ComponentName(
- mContext.getPackageName(), "com.test.TestPersonalizationHandler"),
+ new ComponentName(mContext.getPackageName(), "com.test.TestPersonalizationHandler"),
createWrappedAppParams(),
new CallerMetadata.Builder().build(),
+ ExecuteOptionsParcel.DEFAULT,
callback);
callback.await();
assertTrue(callback.mWasInvoked);
}
@Test
+ public void testExecuteInvokesAppRequestFlowWithBestValue() throws Exception {
+ var callback = new ExecuteCallback();
+ ExecuteOptionsParcel options =
+ new ExecuteOptionsParcel(
+ ExecuteInIsolatedServiceRequest.OutputSpec.OUTPUT_TYPE_BEST_VALUE, 50);
+ mService.execute(
+ mContext.getPackageName(),
+ new ComponentName(mContext.getPackageName(), "com.test.TestPersonalizationHandler"),
+ createWrappedAppParams(),
+ new CallerMetadata.Builder().build(),
+ options,
+ callback);
+ callback.await();
+ assertTrue(callback.mWasInvoked);
+ }
+
+ @Test
+ public void testExecuteInvokesAppRequestFlowWithBestValue_exceedLimit() throws Exception {
+ var callback = new ExecuteCallback();
+ ExecuteOptionsParcel options =
+ new ExecuteOptionsParcel(
+ ExecuteInIsolatedServiceRequest.OutputSpec.OUTPUT_TYPE_BEST_VALUE, 150);
+ assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ mService.execute(
+ mContext.getPackageName(),
+ new ComponentName(
+ mContext.getPackageName(),
+ "com.test.TestPersonalizationHandler"),
+ createWrappedAppParams(),
+ new CallerMetadata.Builder().build(),
+ options,
+ callback));
+ }
+
+ @Test
public void testExecuteThrowsIfAppPackageNameIncorrect() throws Exception {
var callback = new ExecuteCallback();
assertThrows(
@@ -189,6 +229,7 @@
"com.test.TestPersonalizationHandler"),
createWrappedAppParams(),
new CallerMetadata.Builder().build(),
+ ExecuteOptionsParcel.DEFAULT,
callback));
}
@@ -205,6 +246,7 @@
"com.test.TestPersonalizationHandler"),
createWrappedAppParams(),
new CallerMetadata.Builder().build(),
+ ExecuteOptionsParcel.DEFAULT,
callback));
}
@@ -221,6 +263,7 @@
"com.test.TestPersonalizationHandler"),
createWrappedAppParams(),
new CallerMetadata.Builder().build(),
+ ExecuteOptionsParcel.DEFAULT,
callback));
}
@@ -235,6 +278,7 @@
null,
createWrappedAppParams(),
new CallerMetadata.Builder().build(),
+ ExecuteOptionsParcel.DEFAULT,
callback));
}
@@ -249,6 +293,7 @@
new ComponentName("", "ServiceClass"),
createWrappedAppParams(),
new CallerMetadata.Builder().build(),
+ ExecuteOptionsParcel.DEFAULT,
callback));
}
@@ -263,6 +308,7 @@
new ComponentName("com.test.TestPackage", ""),
createWrappedAppParams(),
new CallerMetadata.Builder().build(),
+ ExecuteOptionsParcel.DEFAULT,
callback));
}
@@ -279,6 +325,7 @@
"com.test.TestPersonalizationHandler"),
createWrappedAppParams(),
null,
+ ExecuteOptionsParcel.DEFAULT,
callback));
}
@@ -294,57 +341,53 @@
"com.test.TestPersonalizationHandler"),
createWrappedAppParams(),
new CallerMetadata.Builder().build(),
+ ExecuteOptionsParcel.DEFAULT,
null));
}
@Test
- public void testExecuteThrowsIfCallerNotEnrolled() throws Exception {
+ public void testExecuteThrowsIfCallerNotEnrolled() {
var callback = new ExecuteCallback();
- var originalCallerAppAllowList = mSpyFlags.getCallerAppAllowList();
- PhFlagsTestUtil.setCallerAppAllowList("");
- try {
- assertThrows(
- IllegalStateException.class,
- () ->
- mService.execute(
- mContext.getPackageName(),
- new ComponentName(
- mContext.getPackageName(),
- "com.test.TestPersonalizationHandler"),
- createWrappedAppParams(),
- new CallerMetadata.Builder().build(),
- callback));
- } finally {
- PhFlagsTestUtil.setCallerAppAllowList(originalCallerAppAllowList);
- }
+ ExtendedMockito.doReturn(false)
+ .when(() -> PartnerEnrollmentChecker.isCallerAppEnrolled(any()));
+
+ assertThrows(
+ IllegalStateException.class,
+ () ->
+ mService.execute(
+ mContext.getPackageName(),
+ new ComponentName(
+ mContext.getPackageName(),
+ "com.test.TestPersonalizationHandler"),
+ createWrappedAppParams(),
+ new CallerMetadata.Builder().build(),
+ ExecuteOptionsParcel.DEFAULT,
+ callback));
}
@Test
- public void testExecuteThrowsIfIsolatedServiceNotEnrolled() throws Exception {
+ public void testExecuteThrowsIfIsolatedServiceNotEnrolled() {
var callback = new ExecuteCallback();
- var originalIsolatedServiceAllowList =
- FlagsFactory.getFlags().getIsolatedServiceAllowList();
- PhFlagsTestUtil.setIsolatedServiceAllowList("");
- try {
- assertThrows(
- IllegalStateException.class,
- () ->
- mService.execute(
- mContext.getPackageName(),
- new ComponentName(
- mContext.getPackageName(),
- "com.test.TestPersonalizationHandler"),
- createWrappedAppParams(),
- new CallerMetadata.Builder().build(),
- callback));
- } finally {
- PhFlagsTestUtil.setIsolatedServiceAllowList(originalIsolatedServiceAllowList);
- }
+ ExtendedMockito.doReturn(false)
+ .when(() -> PartnerEnrollmentChecker.isIsolatedServiceEnrolled(any()));
+
+ assertThrows(
+ IllegalStateException.class,
+ () ->
+ mService.execute(
+ mContext.getPackageName(),
+ new ComponentName(
+ mContext.getPackageName(),
+ "com.test.TestPersonalizationHandler"),
+ createWrappedAppParams(),
+ new CallerMetadata.Builder().build(),
+ ExecuteOptionsParcel.DEFAULT,
+ callback));
}
@Test
public void testEnabledGlobalKillSwitchOnRequestSurfacePackage() throws Exception {
- when(mSpyFlags.getGlobalKillSwitch()).thenReturn(true);
+ when(mMockFlags.getGlobalKillSwitch()).thenReturn(true);
var callback = new RequestSurfacePackageCallback();
assertThrows(
IllegalStateException.class,
@@ -356,8 +399,7 @@
100,
50,
new CallerMetadata.Builder().build(),
- callback
- ));
+ callback));
}
@Test
@@ -374,8 +416,7 @@
100,
50,
new CallerMetadata.Builder().build(),
- callback
- ));
+ callback));
}
@Test
@@ -480,13 +521,7 @@
NullPointerException.class,
() ->
mService.requestSurfacePackage(
- "resultToken",
- new Binder(),
- 0,
- 100,
- 50,
- null,
- callback));
+ "resultToken", new Binder(), 0, 100, 50, null, callback));
}
@Test
@@ -506,7 +541,7 @@
@Test
public void testEnabledGlobalKillSwitchOnRegisterMeasurementEvent() throws Exception {
- when(mSpyFlags.getGlobalKillSwitch()).thenReturn(true);
+ when(mMockFlags.getGlobalKillSwitch()).thenReturn(true);
assertThrows(
IllegalStateException.class,
() ->
@@ -558,8 +593,8 @@
@Test
public void testWithBoundService() throws TimeoutException {
- Intent serviceIntent = new Intent(mContext,
- OnDevicePersonalizationManagingServiceImpl.class);
+ Intent serviceIntent =
+ new Intent(mContext, OnDevicePersonalizationManagingServiceImpl.class);
IBinder binder = serviceRule.bindService(serviceIntent);
assertTrue(binder instanceof OnDevicePersonalizationManagingServiceDelegate);
}
@@ -581,8 +616,9 @@
private Bundle createWrappedAppParams() throws Exception {
Bundle wrappedParams = new Bundle();
- ByteArrayParceledSlice buffer = new ByteArrayParceledSlice(
- PersistableBundleUtils.toByteArray(PersistableBundle.EMPTY));
+ ByteArrayParceledSlice buffer =
+ new ByteArrayParceledSlice(
+ PersistableBundleUtils.toByteArray(PersistableBundle.EMPTY));
wrappedParams.putParcelable(Constants.EXTRA_APP_PARAMS_SERIALIZED, buffer);
return wrappedParams;
}
@@ -593,7 +629,7 @@
public boolean mError = false;
public int mErrorCode = 0;
public int mIsolatedServiceErrorCode = 0;
- public String mErrorMessage = null;
+ public byte[] mSerializedException = null;
public String mToken = null;
private final CountDownLatch mLatch = new CountDownLatch(1);
@@ -608,13 +644,16 @@
}
@Override
- public void onError(int errorCode, int isolatedServiceErrorCode, String message,
+ public void onError(
+ int errorCode,
+ int isolatedServiceErrorCode,
+ byte[] serializedException,
CalleeMetadata calleeMetadata) {
mWasInvoked = true;
mError = true;
mErrorCode = errorCode;
mIsolatedServiceErrorCode = isolatedServiceErrorCode;
- mErrorMessage = message;
+ mSerializedException = serializedException;
mLatch.countDown();
}
@@ -629,25 +668,28 @@
public boolean mError = false;
public int mErrorCode = 0;
public int mIsolatedServiceErrorCode = 0;
- public String mErrorMessage = null;
+ public byte[] mSerializedException = null;
private final CountDownLatch mLatch = new CountDownLatch(1);
@Override
- public void onSuccess(SurfaceControlViewHost.SurfacePackage s,
- CalleeMetadata calleeMetadata) {
+ public void onSuccess(
+ SurfaceControlViewHost.SurfacePackage s, CalleeMetadata calleeMetadata) {
mWasInvoked = true;
mSuccess = true;
mLatch.countDown();
}
@Override
- public void onError(int errorCode, int isolatedServiceErrorCode, String message,
+ public void onError(
+ int errorCode,
+ int isolatedServiceErrorCode,
+ byte[] serializedException,
CalleeMetadata calleeMetadata) {
mWasInvoked = true;
mError = true;
mErrorCode = errorCode;
mIsolatedServiceErrorCode = isolatedServiceErrorCode;
- mErrorMessage = message;
+ mSerializedException = serializedException;
mLatch.countDown();
}
diff --git a/tests/servicetests/src/com/android/ondevicepersonalization/services/PhFlagsTest.java b/tests/servicetests/src/com/android/ondevicepersonalization/services/PhFlagsTest.java
index 386074d..5069fe9 100644
--- a/tests/servicetests/src/com/android/ondevicepersonalization/services/PhFlagsTest.java
+++ b/tests/servicetests/src/com/android/ondevicepersonalization/services/PhFlagsTest.java
@@ -16,11 +16,15 @@
package com.android.ondevicepersonalization.services;
-import static com.android.adservices.shared.common.flags.ModuleSharedFlags.BACKGROUND_JOB_LOGGING_ENABLED;
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_ENABLED;
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;
+import static com.android.ondevicepersonalization.services.Flags.DEFAULT_AGGREGATED_ERROR_REPORTING_URL_PATH;
+import static com.android.ondevicepersonalization.services.Flags.DEFAULT_AGGREGATED_ERROR_REPORT_TTL_DAYS;
import static com.android.ondevicepersonalization.services.Flags.DEFAULT_APP_INSTALL_HISTORY_TTL_MILLIS;
import static com.android.ondevicepersonalization.services.Flags.DEFAULT_CALLER_APP_ALLOW_LIST;
import static com.android.ondevicepersonalization.services.Flags.DEFAULT_CLIENT_ERROR_LOGGING_ENABLED;
@@ -40,16 +44,21 @@
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;
+import static com.android.ondevicepersonalization.services.PhFlags.KEY_AGGREGATED_ERROR_REPORT_TTL_DAYS;
import static com.android.ondevicepersonalization.services.PhFlags.KEY_APP_REQUEST_FLOW_DEADLINE_SECONDS;
import static com.android.ondevicepersonalization.services.PhFlags.KEY_CALLER_APP_ALLOW_LIST;
import static com.android.ondevicepersonalization.services.PhFlags.KEY_DOWNLOAD_FLOW_DEADLINE_SECONDS;
+import static com.android.ondevicepersonalization.services.PhFlags.KEY_ENABLE_AGGREGATED_ERROR_REPORTING;
import static com.android.ondevicepersonalization.services.PhFlags.KEY_ENABLE_PERSONALIZATION_STATUS_OVERRIDE;
import static com.android.ondevicepersonalization.services.PhFlags.KEY_EXAMPLE_STORE_FLOW_DEADLINE_SECONDS;
import static com.android.ondevicepersonalization.services.PhFlags.KEY_GLOBAL_KILL_SWITCH;
import static com.android.ondevicepersonalization.services.PhFlags.KEY_ISOLATED_SERVICE_ALLOW_LIST;
import static com.android.ondevicepersonalization.services.PhFlags.KEY_ISOLATED_SERVICE_DEBUGGING_ENABLED;
import static com.android.ondevicepersonalization.services.PhFlags.KEY_IS_ART_IMAGE_LOADING_OPTIMIZATION_ENABLED;
-import static com.android.ondevicepersonalization.services.PhFlags.KEY_ODP_BACKGROUND_JOBS_LOGGING_ENABLED;
import static com.android.ondevicepersonalization.services.PhFlags.KEY_ODP_BACKGROUND_JOB_SAMPLING_LOGGING_RATE;
import static com.android.ondevicepersonalization.services.PhFlags.KEY_ODP_ENABLE_CLIENT_ERROR_LOGGING;
import static com.android.ondevicepersonalization.services.PhFlags.KEY_ODP_JOB_SCHEDULING_LOGGING_ENABLED;
@@ -89,19 +98,6 @@
PhFlagsTestUtil.setUpDeviceConfigPermissions();
}
- @Test(expected = IllegalArgumentException.class)
- public void testInvalidStableFlags() {
- FlagsFactory.getFlags().getStableFlag("INVALID_FLAG_NAME");
- }
-
- @Test
- public void testValidStableFlags() {
- Object isSipFeatureEnabled = FlagsFactory.getFlags()
- .getStableFlag(KEY_SHARED_ISOLATED_PROCESS_FEATURE_ENABLED);
-
- assertThat(isSipFeatureEnabled).isNotNull();
- }
-
@Test
public void testGetGlobalKillSwitch() {
// Without any overriding, the value is the hard coded constant.
@@ -513,28 +509,13 @@
@Test
public void testGetBackgroundJobsLoggingEnabled() {
- // read a stable flag value and verify it's equal to the default value.
- boolean stableValue = FlagsFactory.getFlags().getBackgroundJobsLoggingEnabled();
- assertThat(stableValue).isEqualTo(BACKGROUND_JOB_LOGGING_ENABLED);
-
- // override the value in device config.
- boolean overrideEnabled = !stableValue;
- DeviceConfig.setProperty(
- DeviceConfig.NAMESPACE_ON_DEVICE_PERSONALIZATION,
- KEY_ODP_BACKGROUND_JOBS_LOGGING_ENABLED,
- Boolean.toString(overrideEnabled),
- /* makeDefault= */ false);
-
- // the flag value remains stable
assertThat(FlagsFactory.getFlags().getBackgroundJobsLoggingEnabled())
- .isEqualTo(stableValue);
+ .isEqualTo(true);
}
@Test
public void testGetBackgroundJobSamplingLoggingRate() {
int defaultValue = BACKGROUND_JOB_SAMPLING_LOGGING_RATE;
- assertThat(FlagsFactory.getFlags().getBackgroundJobSamplingLoggingRate())
- .isEqualTo(defaultValue);
// Override the value in device config.
int overrideRate = defaultValue + 1;
@@ -551,7 +532,6 @@
public void testGetJobSchedulingLoggingEnabled() {
// read a stable flag value and verify it's equal to the default value.
boolean stableValue = FlagsFactory.getFlags().getJobSchedulingLoggingEnabled();
- assertThat(stableValue).isEqualTo(DEFAULT_JOB_SCHEDULING_LOGGING_ENABLED);
// override the value in device config.
boolean overrideEnabled = !stableValue;
@@ -569,8 +549,6 @@
@Test
public void testGetJobSchedulingLoggingSamplingRate() {
int defaultValue = DEFAULT_JOB_SCHEDULING_LOGGING_SAMPLING_RATE;
- assertThat(FlagsFactory.getFlags().getJobSchedulingLoggingSamplingRate())
- .isEqualTo(defaultValue);
// Override the value in device config.
int overrideRate = defaultValue + 1;
@@ -633,4 +611,135 @@
assertThat(FlagsFactory.getFlags().getAppInstallHistoryTtlInMillis())
.isEqualTo(overrideEnabled);
}
+
+ @Test
+ public void testAggregateErrorReportingEnabled() {
+ boolean testValue = !DEFAULT_AGGREGATED_ERROR_REPORTING_ENABLED;
+
+ DeviceConfig.setProperty(
+ DeviceConfig.NAMESPACE_ON_DEVICE_PERSONALIZATION,
+ KEY_ENABLE_AGGREGATED_ERROR_REPORTING,
+ Boolean.toString(testValue),
+ /* makeDefault */ false);
+
+ assertThat(FlagsFactory.getFlags().getAggregatedErrorReportingEnabled())
+ .isEqualTo(testValue);
+
+ DeviceConfig.setProperty(
+ DeviceConfig.NAMESPACE_ON_DEVICE_PERSONALIZATION,
+ KEY_ENABLE_AGGREGATED_ERROR_REPORTING,
+ Boolean.toString(DEFAULT_AGGREGATED_ERROR_REPORTING_ENABLED),
+ /* makeDefault */ false);
+ assertThat(FlagsFactory.getFlags().getAggregatedErrorReportingEnabled())
+ .isEqualTo(DEFAULT_AGGREGATED_ERROR_REPORTING_ENABLED);
+ }
+
+ @Test
+ public void testAggregateErrorReportingTtlDays() {
+ int testValue = 4;
+
+ DeviceConfig.setProperty(
+ DeviceConfig.NAMESPACE_ON_DEVICE_PERSONALIZATION,
+ KEY_AGGREGATED_ERROR_REPORT_TTL_DAYS,
+ Integer.toString(testValue),
+ /* makeDefault */ false);
+
+ assertThat(FlagsFactory.getFlags().getAggregatedErrorReportingTtlInDays())
+ .isEqualTo(testValue);
+
+ DeviceConfig.setProperty(
+ DeviceConfig.NAMESPACE_ON_DEVICE_PERSONALIZATION,
+ KEY_AGGREGATED_ERROR_REPORT_TTL_DAYS,
+ Integer.toString(DEFAULT_AGGREGATED_ERROR_REPORT_TTL_DAYS),
+ /* makeDefault */ false);
+ assertThat(FlagsFactory.getFlags().getAggregatedErrorReportingTtlInDays())
+ .isEqualTo(DEFAULT_AGGREGATED_ERROR_REPORT_TTL_DAYS);
+ }
+
+ @Test
+ public void testAggregateErrorReportingUrlPath() {
+ String testValue = "foo/bar";
+
+ DeviceConfig.setProperty(
+ DeviceConfig.NAMESPACE_ON_DEVICE_PERSONALIZATION,
+ KEY_AGGREGATED_ERROR_REPORTING_PATH,
+ testValue,
+ /* makeDefault */ false);
+
+ assertThat(FlagsFactory.getFlags().getAggregatedErrorReportingServerPath())
+ .isEqualTo(testValue);
+
+ DeviceConfig.setProperty(
+ DeviceConfig.NAMESPACE_ON_DEVICE_PERSONALIZATION,
+ KEY_AGGREGATED_ERROR_REPORTING_PATH,
+ DEFAULT_AGGREGATED_ERROR_REPORTING_URL_PATH,
+ /* makeDefault */ false);
+ assertThat(FlagsFactory.getFlags().getAggregatedErrorReportingServerPath())
+ .isEqualTo(DEFAULT_AGGREGATED_ERROR_REPORTING_URL_PATH);
+ }
+
+ @Test
+ public void testAggregateErrorReportingThreshold() {
+ int testValue = 5;
+
+ DeviceConfig.setProperty(
+ DeviceConfig.NAMESPACE_ON_DEVICE_PERSONALIZATION,
+ KEY_AGGREGATED_ERROR_REPORTING_THRESHOLD,
+ Integer.toString(testValue),
+ /* makeDefault */ false);
+
+ assertThat(FlagsFactory.getFlags().getAggregatedErrorMinThreshold()).isEqualTo(testValue);
+
+ DeviceConfig.setProperty(
+ DeviceConfig.NAMESPACE_ON_DEVICE_PERSONALIZATION,
+ KEY_AGGREGATED_ERROR_REPORTING_THRESHOLD,
+ Integer.toString(DEFAULT_AGGREGATED_ERROR_REPORTING_THRESHOLD),
+ /* makeDefault */ false);
+ assertThat(FlagsFactory.getFlags().getAggregatedErrorMinThreshold())
+ .isEqualTo(DEFAULT_AGGREGATED_ERROR_REPORTING_THRESHOLD);
+ }
+
+ @Test
+ public void testAggregateErrorReportingInterval() {
+ int testValue = 4;
+
+ DeviceConfig.setProperty(
+ DeviceConfig.NAMESPACE_ON_DEVICE_PERSONALIZATION,
+ KEY_AGGREGATED_ERROR_REPORTING_INTERVAL_HOURS,
+ Integer.toString(testValue),
+ /* makeDefault */ false);
+
+ assertThat(FlagsFactory.getFlags().getAggregatedErrorReportingIntervalInHours())
+ .isEqualTo(testValue);
+
+ DeviceConfig.setProperty(
+ DeviceConfig.NAMESPACE_ON_DEVICE_PERSONALIZATION,
+ KEY_AGGREGATED_ERROR_REPORTING_INTERVAL_HOURS,
+ Integer.toString(DEFAULT_AGGREGATED_ERROR_REPORTING_INTERVAL_HOURS),
+ /* makeDefault */ false);
+ 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/PhFlagsTestUtil.java b/tests/servicetests/src/com/android/ondevicepersonalization/services/PhFlagsTestUtil.java
index ef54006..c4efe55 100644
--- a/tests/servicetests/src/com/android/ondevicepersonalization/services/PhFlagsTestUtil.java
+++ b/tests/servicetests/src/com/android/ondevicepersonalization/services/PhFlagsTestUtil.java
@@ -26,7 +26,7 @@
import android.provider.DeviceConfig;
-import androidx.test.InstrumentationRegistry;
+import androidx.test.platform.app.InstrumentationRegistry;
public class PhFlagsTestUtil {
private static final String WRITE_DEVICE_CONFIG_PERMISSION =
diff --git a/tests/servicetests/src/com/android/ondevicepersonalization/services/StableFlagsTest.java b/tests/servicetests/src/com/android/ondevicepersonalization/services/StableFlagsTest.java
new file mode 100644
index 0000000..56caab0
--- /dev/null
+++ b/tests/servicetests/src/com/android/ondevicepersonalization/services/StableFlagsTest.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ondevicepersonalization.services;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.modules.utils.testing.ExtendedMockitoRule;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.quality.Strictness;
+
+
+@RunWith(JUnit4.class)
+public final class StableFlagsTest {
+ @Rule
+ public final ExtendedMockitoRule mExtendedMockitoRule =
+ new ExtendedMockitoRule.Builder(this)
+ .spyStatic(FlagsFactory.class)
+ .setStrictness(Strictness.LENIENT)
+ .build();
+
+ @Before
+ public void setUp() {
+ ExtendedMockito.doReturn(new Flags() {}).when(FlagsFactory::getFlags);
+ }
+
+ @Test
+ public void testValidStableFlags() {
+ Object isSipFeatureEnabled =
+ StableFlags.get(PhFlags.KEY_SHARED_ISOLATED_PROCESS_FEATURE_ENABLED);
+
+ assertThat(isSipFeatureEnabled).isNotNull();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testInvalidStableFlags() {
+ StableFlags.get("INVALID_FLAG_NAME");
+ }
+}
diff --git a/tests/servicetests/src/com/android/ondevicepersonalization/services/data/errors/AggregateErrorDataReportingServiceTest.java b/tests/servicetests/src/com/android/ondevicepersonalization/services/data/errors/AggregateErrorDataReportingServiceTest.java
new file mode 100644
index 0000000..91fc9b2
--- /dev/null
+++ b/tests/servicetests/src/com/android/ondevicepersonalization/services/data/errors/AggregateErrorDataReportingServiceTest.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ondevicepersonalization.services.data.errors;
+
+import static com.android.ondevicepersonalization.services.OnDevicePersonalizationConfig.AGGREGATE_ERROR_DATA_REPORTING_JOB_ID;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.content.Context;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.ondevicepersonalization.services.Flags;
+import com.android.ondevicepersonalization.services.OnDevicePersonalizationConfig;
+
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(JUnit4.class)
+public class AggregateErrorDataReportingServiceTest {
+ private final Context mContext = ApplicationProvider.getApplicationContext();
+ private final JobScheduler mJobScheduler = mContext.getSystemService(JobScheduler.class);
+
+ private AggregateErrorDataReportingService mService;
+
+ @Mock private Flags mMockFlags;
+
+ @Before
+ public void setup() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ mService = spy(new AggregateErrorDataReportingService(new TestInjector()));
+ doNothing().when(mService).jobFinished(any(), anyBoolean());
+
+ // Setup tests with the global kill switch is disabled and error reporting enabled.
+ when(mMockFlags.getGlobalKillSwitch()).thenReturn(false);
+ when(mMockFlags.getAggregatedErrorReportingEnabled()).thenReturn(true);
+ if (mJobScheduler != null) {
+ // Cleanup any pending jobs
+ mJobScheduler.cancel(AGGREGATE_ERROR_DATA_REPORTING_JOB_ID);
+ }
+ }
+
+ @Test
+ public void onStartJobTestKillSwitchEnabled_jobCancelled() {
+ // Given that the aggregate error reporting job service is already scheduled and the global
+ // kill switch is enabled (that is ODP is disabled).
+ when(mMockFlags.getGlobalKillSwitch()).thenReturn(true);
+ doReturn(mJobScheduler).when(mService).getSystemService(JobScheduler.class);
+ assertEquals(
+ JobScheduler.RESULT_SUCCESS,
+ AggregateErrorDataReportingService.scheduleIfNeeded(mContext, mMockFlags));
+ assertNotNull(
+ mJobScheduler.getPendingJob(
+ OnDevicePersonalizationConfig.AGGREGATE_ERROR_DATA_REPORTING_JOB_ID));
+
+ // When the job is started.
+ boolean result = mService.onStartJob(mock(JobParameters.class));
+
+ // Expect that the pending job is cancelled.
+ assertTrue(result);
+ verify(mService, times(1)).jobFinished(any(), eq(false));
+ assertNull(
+ mJobScheduler.getPendingJob(
+ OnDevicePersonalizationConfig.AGGREGATE_ERROR_DATA_REPORTING_JOB_ID));
+ }
+
+ @Test
+ public void onStartJobTestAggregateReportingDisabled_jobCancelled() {
+ // Given that the aggregate error reporting job service is already scheduled and the error
+ // reporting flag has been disabled.
+ doReturn(mJobScheduler).when(mService).getSystemService(JobScheduler.class);
+ assertEquals(
+ JobScheduler.RESULT_SUCCESS,
+ AggregateErrorDataReportingService.scheduleIfNeeded(mContext, mMockFlags));
+ assertNotNull(
+ mJobScheduler.getPendingJob(
+ OnDevicePersonalizationConfig.AGGREGATE_ERROR_DATA_REPORTING_JOB_ID));
+
+ // When the job is started with error reporting disabled.
+ when(mMockFlags.getAggregatedErrorReportingEnabled()).thenReturn(false);
+ boolean result = mService.onStartJob(mock(JobParameters.class));
+
+ // Expect that the job is cancelled and no more pending jobs.
+ assertTrue(result);
+ verify(mService, times(1)).jobFinished(any(), eq(false));
+ assertNull(
+ mJobScheduler.getPendingJob(
+ OnDevicePersonalizationConfig.AGGREGATE_ERROR_DATA_REPORTING_JOB_ID));
+ }
+
+ @Test
+ public void onStopJobTest() {
+ assertTrue(mService.onStopJob(mock(JobParameters.class)));
+ }
+
+ @Test
+ public void scheduleIfNeeded_AggregateErrorReportingDisabled() {
+ when(mMockFlags.getAggregatedErrorReportingEnabled()).thenReturn(false);
+
+ assertEquals(
+ JobScheduler.RESULT_FAILURE,
+ AggregateErrorDataReportingService.scheduleIfNeeded(mContext, mMockFlags));
+ }
+
+ @Test
+ public void scheduleIfNeeded_AggregateErrorReportingEnabled() {
+ when(mMockFlags.getAggregatedErrorReportingEnabled()).thenReturn(true);
+
+ assertEquals(
+ JobScheduler.RESULT_SUCCESS,
+ AggregateErrorDataReportingService.scheduleIfNeeded(mContext, mMockFlags));
+ }
+
+ private class TestInjector extends AggregateErrorDataReportingService.Injector {
+ @Override
+ ListeningExecutorService getExecutor() {
+ return MoreExecutors.newDirectExecutorService();
+ }
+
+ @Override
+ Flags getFlags() {
+ return mMockFlags;
+ }
+ }
+}
diff --git a/tests/servicetests/src/com/android/ondevicepersonalization/services/data/errors/AggregatedErrorCodesLoggerTest.java b/tests/servicetests/src/com/android/ondevicepersonalization/services/data/errors/AggregatedErrorCodesLoggerTest.java
new file mode 100644
index 0000000..b943932
--- /dev/null
+++ b/tests/servicetests/src/com/android/ondevicepersonalization/services/data/errors/AggregatedErrorCodesLoggerTest.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ondevicepersonalization.services.data.errors;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.quality.Strictness.LENIENT;
+
+import android.content.ComponentName;
+import android.content.Context;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.odp.module.common.PackageUtils;
+import com.android.ondevicepersonalization.services.Flags;
+import com.android.ondevicepersonalization.services.FlagsFactory;
+import com.android.ondevicepersonalization.services.OnDevicePersonalizationExecutors;
+
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.MoreExecutors;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.MockitoSession;
+
+import java.util.List;
+
+@RunWith(JUnit4.class)
+public class AggregatedErrorCodesLoggerTest {
+
+ private static final String TEST_CERT_DIGEST = "test_cert_digest";
+ private static final String TEST_PACKAGE = "test_package";
+ private static final String TEST_CLASS = "test_class";
+
+ private static final int TEST_ISOLATED_SERVICE_ERROR_CODE = 2;
+
+ private static final ComponentName TEST_COMPONENT_NAME =
+ new ComponentName(TEST_PACKAGE, TEST_CLASS);
+ private final Context mContext = ApplicationProvider.getApplicationContext();
+ private MockitoSession mSession;
+
+ private int mDayIndexUtc;
+ private final OnDevicePersonalizationAggregatedErrorDataDao mErrorDataDao =
+ OnDevicePersonalizationAggregatedErrorDataDao.getInstance(
+ mContext, TEST_COMPONENT_NAME, TEST_CERT_DIGEST);
+
+ @Before
+ public void setUp() {
+ mDayIndexUtc = DateTimeUtils.dayIndexUtc();
+
+ mSession =
+ ExtendedMockito.mockitoSession()
+ .mockStatic(FlagsFactory.class)
+ .mockStatic(PackageUtils.class)
+ .spyStatic(OnDevicePersonalizationExecutors.class)
+ .strictness(LENIENT)
+ .startMocking();
+
+ ExtendedMockito.doReturn(MoreExecutors.newDirectExecutorService())
+ .when(OnDevicePersonalizationExecutors::getBackgroundExecutor);
+ doReturn(TEST_CERT_DIGEST).when(() -> PackageUtils.getCertDigest(any(), any()));
+ mErrorDataDao.deleteExceptionData();
+ }
+
+ @Test
+ public void logIsolatedServiceErrorCode_flagDisabled_skipsLogging() throws Exception {
+ doReturn(new TestFlags(false)).when(FlagsFactory::getFlags);
+
+ ListenableFuture<?> loggingFuture =
+ AggregatedErrorCodesLogger.logIsolatedServiceErrorCode(
+ TEST_ISOLATED_SERVICE_ERROR_CODE, TEST_COMPONENT_NAME, mContext);
+
+ assertTrue(loggingFuture.isDone());
+ assertTrue(mErrorDataDao.getExceptionData().isEmpty());
+ }
+
+ @Test
+ public void logIsolatedServiceErrorCode_flagEnabled_logsException() {
+ doReturn(new TestFlags(true)).when(FlagsFactory::getFlags);
+
+ ListenableFuture<?> loggingFuture =
+ AggregatedErrorCodesLogger.logIsolatedServiceErrorCode(
+ TEST_ISOLATED_SERVICE_ERROR_CODE, TEST_COMPONENT_NAME, mContext);
+
+ List<ErrorData> exceptionData = mErrorDataDao.getExceptionData();
+ assertTrue(loggingFuture.isDone());
+ assertEquals(1, exceptionData.size());
+ assertEquals(getExpectedErrorData(mDayIndexUtc), exceptionData.get(0));
+ }
+
+ @Test
+ public void cleanupAggregatedErrorData_flagDisabled_skipsCleanup() {
+ doReturn(new TestFlags(false)).when(FlagsFactory::getFlags);
+ mErrorDataDao.addExceptionCount(TEST_ISOLATED_SERVICE_ERROR_CODE, /* exceptionCount= */ 1);
+
+ ListenableFuture<?> cleanupFuture =
+ AggregatedErrorCodesLogger.cleanupAggregatedErrorData(mContext);
+
+ List<ErrorData> exceptionData = mErrorDataDao.getExceptionData();
+ assertTrue(cleanupFuture.isDone());
+ assertEquals(1, exceptionData.size());
+ assertEquals(getExpectedErrorData(mDayIndexUtc), exceptionData.get(0));
+ }
+
+ @Test
+ public void cleanupAggregatedErrorData_flagEnabled_performsCleanup() {
+ doReturn(new TestFlags(true)).when(FlagsFactory::getFlags);
+ mErrorDataDao.addExceptionCount(TEST_ISOLATED_SERVICE_ERROR_CODE, /* exceptionCount= */ 1);
+
+ ListenableFuture<?> cleanupFuture =
+ AggregatedErrorCodesLogger.cleanupAggregatedErrorData(mContext);
+
+ assertTrue(cleanupFuture.isDone());
+ assertTrue(mErrorDataDao.getExceptionData().isEmpty());
+ assertTrue(
+ OnDevicePersonalizationAggregatedErrorDataDao.getErrorDataTableNames(mContext)
+ .isEmpty());
+ }
+
+ @After
+ public void tearDown() {
+ mSession.finishMocking();
+ }
+
+ private static ErrorData getExpectedErrorData(int dayIndexUtc) {
+ return new ErrorData.Builder(TEST_ISOLATED_SERVICE_ERROR_CODE, 1, dayIndexUtc, 0).build();
+ }
+
+ private static final class TestFlags implements Flags {
+ private final boolean mAggregateErrorReportingEnabled;
+
+ private TestFlags(boolean aggregateErrorReportingEnabled) {
+ mAggregateErrorReportingEnabled = aggregateErrorReportingEnabled;
+ }
+
+ @Override
+ public boolean getAggregatedErrorReportingEnabled() {
+ return mAggregateErrorReportingEnabled;
+ }
+ }
+}
diff --git a/tests/servicetests/src/com/android/ondevicepersonalization/services/data/errors/DateTimeUtilsTest.java b/tests/servicetests/src/com/android/ondevicepersonalization/services/data/errors/DateTimeUtilsTest.java
new file mode 100644
index 0000000..e07e1f4
--- /dev/null
+++ b/tests/servicetests/src/com/android/ondevicepersonalization/services/data/errors/DateTimeUtilsTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ondevicepersonalization.services.data.errors;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.modules.utils.testing.ExtendedMockitoRule;
+import com.android.odp.module.common.Clock;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.quality.Strictness;
+
+import java.util.TimeZone;
+
+@RunWith(JUnit4.class)
+public class DateTimeUtilsTest {
+ @Rule
+ public final ExtendedMockitoRule extendedMockitoRule =
+ new ExtendedMockitoRule.Builder(this).setStrictness(Strictness.LENIENT).build();
+
+ // PST: Friday, August 23, 2024 10:59:11 PM
+ private static final long DEFAULT_CURRENT_TIME_MILLIS = 1724479151000L;
+ private static final int CURRENT_DAYS_EPOCH_PST = 19958;
+ private Context mContext;
+
+ @Mock private Clock mMockClock;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mContext = ApplicationProvider.getApplicationContext();
+
+ TimeZone pstTime = TimeZone.getTimeZone("GMT-08:00");
+ TimeZone.setDefault(pstTime);
+ when(mMockClock.currentTimeMillis()).thenReturn(DEFAULT_CURRENT_TIME_MILLIS);
+ }
+
+ @Test
+ public void testDayIndexUtc() {
+ // UTC day is into the next day, Aug 24th 2024.
+ int dayEpoch = DateTimeUtils.dayIndexUtc(mMockClock);
+
+ assertEquals(CURRENT_DAYS_EPOCH_PST + 1, dayEpoch);
+ }
+
+ @Test
+ public void testDayIndexLocal() {
+ int dayEpoch = DateTimeUtils.dayIndexLocal(mMockClock);
+
+ assertEquals(CURRENT_DAYS_EPOCH_PST, dayEpoch);
+ }
+}
diff --git a/tests/servicetests/src/com/android/ondevicepersonalization/services/data/errors/OnDevicePersonalizationAggregatedErrorDataDaoTest.java b/tests/servicetests/src/com/android/ondevicepersonalization/services/data/errors/OnDevicePersonalizationAggregatedErrorDataDaoTest.java
new file mode 100644
index 0000000..e074cd6
--- /dev/null
+++ b/tests/servicetests/src/com/android/ondevicepersonalization/services/data/errors/OnDevicePersonalizationAggregatedErrorDataDaoTest.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ondevicepersonalization.services.data.errors;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+
+import android.content.ComponentName;
+import android.content.Context;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.modules.utils.testing.ExtendedMockitoRule;
+import com.android.odp.module.common.PackageUtils;
+
+import com.google.common.collect.ImmutableList;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.quality.Strictness;
+
+@RunWith(JUnit4.class)
+public class OnDevicePersonalizationAggregatedErrorDataDaoTest {
+ private static final String TEST_PACKAGE = "ownerPkg";
+ private static final String OTHER_PACKAGE = "otherPkg";
+ private static final ComponentName TEST_OWNER = new ComponentName(TEST_PACKAGE, "ownerCls");
+ private static final ComponentName OTHER_OWNER = new ComponentName(OTHER_PACKAGE, "otherCls");
+ private static final String TEST_CERT_DIGEST = "certDigest1";
+ private static final String OTHER_CERT_DIGEST = "certDigest2";
+ private final Context mContext = ApplicationProvider.getApplicationContext();
+ private OnDevicePersonalizationAggregatedErrorDataDao mDao;
+
+ @Rule
+ public final ExtendedMockitoRule mExtendedMockitoRule =
+ new ExtendedMockitoRule.Builder(this)
+ .spyStatic(PackageUtils.class)
+ .setStrictness(Strictness.LENIENT)
+ .build();
+
+ @Before
+ public void setup() {
+ mDao =
+ OnDevicePersonalizationAggregatedErrorDataDao.getInstance(
+ mContext, TEST_OWNER, TEST_CERT_DIGEST);
+
+ // Cleanup any existing records
+ mDao.deleteExceptionData();
+ ExtendedMockito.doReturn(TEST_CERT_DIGEST)
+ .when(() -> PackageUtils.getCertDigest(any(), eq(TEST_PACKAGE)));
+ ExtendedMockito.doReturn(OTHER_CERT_DIGEST)
+ .when(() -> PackageUtils.getCertDigest(any(), eq(OTHER_PACKAGE)));
+ OnDevicePersonalizationAggregatedErrorDataDao.cleanupErrorData(
+ mContext, /* excludedServices= */ ImmutableList.of());
+ }
+
+ @Test
+ public void testAddExceptionCount_InvalidCode_Fails() {
+ assertFalse(
+ mDao.addExceptionCount(
+ OnDevicePersonalizationAggregatedErrorDataDao.MAX_ALLOWED_ERROR_CODE + 1,
+ 1));
+ }
+
+ @Test
+ public void testAddExceptionCount_Success() {
+ assertTrue(mDao.addExceptionCount(1, 1));
+ assertTrue(mDao.addExceptionCount(1, 1));
+ assertThat(mDao.getExceptionData()).hasSize(1);
+ }
+
+ @Test
+ public void testDeleteExceptionData_Success() {
+ // Given two records are added to the Dao
+ mDao.addExceptionCount(1, 1);
+ mDao.addExceptionCount(2, 1);
+ ImmutableList<ErrorData> originalData = mDao.getExceptionData();
+
+ // Expect that calling delete clears the table
+ assertTrue(mDao.deleteExceptionData());
+ assertThat(mDao.getExceptionData()).isEmpty();
+ assertThat(originalData).hasSize(2);
+ }
+
+ @Test
+ public void testGetInstance() {
+ OnDevicePersonalizationAggregatedErrorDataDao owner1Instance1 =
+ OnDevicePersonalizationAggregatedErrorDataDao.getInstance(
+ mContext, TEST_OWNER, TEST_CERT_DIGEST);
+ OnDevicePersonalizationAggregatedErrorDataDao owner1Instance2 =
+ OnDevicePersonalizationAggregatedErrorDataDao.getInstance(
+ mContext, TEST_OWNER, TEST_CERT_DIGEST);
+ OnDevicePersonalizationAggregatedErrorDataDao owner2Instance1 =
+ OnDevicePersonalizationAggregatedErrorDataDao.getInstance(
+ mContext, OTHER_OWNER, TEST_CERT_DIGEST);
+
+ assertNotNull(owner1Instance1);
+ assertNotNull(owner2Instance1);
+ assertThat(owner1Instance1).isSameInstanceAs(owner1Instance2);
+ assertNotEquals(owner1Instance1, owner2Instance1);
+ }
+
+ @Test
+ public void testGetMatchingTables() {
+ // Given two tables with some error data
+ OnDevicePersonalizationAggregatedErrorDataDao instance1 =
+ OnDevicePersonalizationAggregatedErrorDataDao.getInstance(
+ mContext, TEST_OWNER, TEST_CERT_DIGEST);
+ OnDevicePersonalizationAggregatedErrorDataDao instance2 =
+ OnDevicePersonalizationAggregatedErrorDataDao.getInstance(
+ mContext, OTHER_OWNER, TEST_CERT_DIGEST);
+ instance1.addExceptionCount(1, 1);
+ instance2.addExceptionCount(2, 1);
+ int originalCount =
+ OnDevicePersonalizationAggregatedErrorDataDao.getErrorDataTableNames(mContext)
+ .size();
+
+ // Expect that no tables exist after cleanup
+ OnDevicePersonalizationAggregatedErrorDataDao.cleanupErrorData(
+ mContext, /* excludedServices= */ ImmutableList.of());
+
+ assertEquals(2, originalCount);
+ assertThat(OnDevicePersonalizationAggregatedErrorDataDao.getErrorDataTableNames(mContext))
+ .isEmpty();
+ }
+}
diff --git a/tests/servicetests/src/com/android/ondevicepersonalization/services/data/user/UserDataCollectionJobServiceTest.java b/tests/servicetests/src/com/android/ondevicepersonalization/services/data/user/UserDataCollectionJobServiceTest.java
index b2b213e..7eac2c5 100644
--- a/tests/servicetests/src/com/android/ondevicepersonalization/services/data/user/UserDataCollectionJobServiceTest.java
+++ b/tests/servicetests/src/com/android/ondevicepersonalization/services/data/user/UserDataCollectionJobServiceTest.java
@@ -18,6 +18,7 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -28,6 +29,7 @@
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.app.job.JobParameters;
import android.app.job.JobScheduler;
@@ -36,122 +38,45 @@
import androidx.test.core.app.ApplicationProvider;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.modules.utils.testing.ExtendedMockitoRule;
+import com.android.ondevicepersonalization.services.Flags;
import com.android.ondevicepersonalization.services.OnDevicePersonalizationConfig;
-import com.android.ondevicepersonalization.services.OnDevicePersonalizationExecutors;
-import com.android.ondevicepersonalization.services.PhFlagsTestUtil;
+import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
-import org.mockito.MockitoSession;
+import org.mockito.Mock;
import org.mockito.quality.Strictness;
@RunWith(JUnit4.class)
public class UserDataCollectionJobServiceTest {
+ @Rule(order = 0)
+ public final ExtendedMockitoRule extendedMockitoRule =
+ new ExtendedMockitoRule.Builder(this)
+ .spyStatic(UserPrivacyStatus.class)
+ .setStrictness(Strictness.LENIENT)
+ .build();
+
private final Context mContext = ApplicationProvider.getApplicationContext();
private final JobScheduler mJobScheduler = mContext.getSystemService(JobScheduler.class);
private UserDataCollector mUserDataCollector;
private UserDataCollectionJobService mService;
private UserPrivacyStatus mUserPrivacyStatus;
+ @Mock private Flags mMockFlags;
@Before
public void setup() throws Exception {
- PhFlagsTestUtil.setUpDeviceConfigPermissions();
- PhFlagsTestUtil.disableGlobalKillSwitch();
mUserPrivacyStatus = spy(UserPrivacyStatus.getInstance());
mUserDataCollector = UserDataCollector.getInstanceForTest(mContext);
- mService = spy(new UserDataCollectionJobService());
- }
-
- @Test
- public void testDefaultNoArgConstructor() {
- UserDataCollectionJobService instance = new UserDataCollectionJobService();
- assertNotNull("default no-arg constructor is required by JobService", instance);
- }
-
- @Test
- public void onStartJobTest() {
- MockitoSession session = ExtendedMockito.mockitoSession()
- .spyStatic(UserPrivacyStatus.class)
- .spyStatic(OnDevicePersonalizationExecutors.class)
- .strictness(Strictness.LENIENT).startMocking();
- try {
- doNothing().when(mService).jobFinished(any(), anyBoolean());
- doReturn(mContext.getPackageManager()).when(mService).getPackageManager();
- ExtendedMockito.doReturn(MoreExecutors.newDirectExecutorService()).when(
- OnDevicePersonalizationExecutors::getBackgroundExecutor);
- ExtendedMockito.doReturn(MoreExecutors.newDirectExecutorService()).when(
- OnDevicePersonalizationExecutors::getLightweightExecutor);
- ExtendedMockito.doReturn(mUserPrivacyStatus).when(UserPrivacyStatus::getInstance);
- ExtendedMockito.doReturn(true).when(mUserPrivacyStatus).isProtectedAudienceEnabled();
- ExtendedMockito.doReturn(true).when(mUserPrivacyStatus).isMeasurementEnabled();
-
- boolean result = mService.onStartJob(mock(JobParameters.class));
- assertTrue(result);
- verify(mService, times(1)).jobFinished(any(), eq(false));
- } finally {
- session.finishMocking();
- }
- }
-
- @Test
- public void onStartJobTestKillSwitchEnabled() {
- PhFlagsTestUtil.enableGlobalKillSwitch();
- MockitoSession session = ExtendedMockito.mockitoSession().startMocking();
- try {
- doReturn(mJobScheduler).when(mService).getSystemService(JobScheduler.class);
- mService.schedule(mContext);
- assertTrue(mJobScheduler.getPendingJob(
- OnDevicePersonalizationConfig.USER_DATA_COLLECTION_ID)
- != null);
- doNothing().when(mService).jobFinished(any(), anyBoolean());
- boolean result = mService.onStartJob(mock(JobParameters.class));
- assertTrue(result);
- verify(mService, times(1)).jobFinished(any(), eq(false));
- assertTrue(mJobScheduler.getPendingJob(
- OnDevicePersonalizationConfig.USER_DATA_COLLECTION_ID)
- == null);
- } finally {
- session.finishMocking();
- }
- }
-
- @Test
- public void onStartJobTestUserControlRevoked() {
- mUserDataCollector.updateUserData(RawUserData.getInstance());
- assertTrue(mUserDataCollector.isInitialized());
- MockitoSession session = ExtendedMockito.mockitoSession()
- .spyStatic(UserPrivacyStatus.class)
- .strictness(Strictness.LENIENT).startMocking();
- try {
- doNothing().when(mService).jobFinished(any(), anyBoolean());
- ExtendedMockito.doReturn(mUserPrivacyStatus).when(UserPrivacyStatus::getInstance);
- ExtendedMockito.doReturn(false)
- .when(mUserPrivacyStatus).isMeasurementEnabled();
- ExtendedMockito.doReturn(false)
- .when(mUserPrivacyStatus).isProtectedAudienceEnabled();
- boolean result = mService.onStartJob(mock(JobParameters.class));
- assertTrue(result);
- verify(mService, times(1)).jobFinished(any(), eq(false));
- assertFalse(mUserDataCollector.isInitialized());
- } finally {
- session.finishMocking();
- }
- }
-
- @Test
- public void onStopJobTest() {
- MockitoSession session = ExtendedMockito.mockitoSession().strictness(
- Strictness.LENIENT).startMocking();
- try {
- assertTrue(mService.onStopJob(mock(JobParameters.class)));
- } finally {
- session.finishMocking();
- }
+ mService = spy(new UserDataCollectionJobService(new TestInjector()));
+ doNothing().when(mService).jobFinished(any(), anyBoolean());
+ when(mMockFlags.getGlobalKillSwitch()).thenReturn(false);
}
@After
@@ -159,4 +84,73 @@
mUserDataCollector.clearUserData(RawUserData.getInstance());
mUserDataCollector.clearMetadata();
}
+
+ @Test
+ public void testDefaultNoArgConstructor() {
+ UserDataCollectionJobService instance =
+ new UserDataCollectionJobService(new TestInjector());
+ assertNotNull("default no-arg constructor is required by JobService", instance);
+ }
+
+ @Test
+ public void onStartJobTest() throws Exception {
+ doReturn(mContext.getPackageManager()).when(mService).getPackageManager();
+ ExtendedMockito.doReturn(mUserPrivacyStatus).when(UserPrivacyStatus::getInstance);
+ ExtendedMockito.doReturn(false).when(mUserPrivacyStatus)
+ .isProtectedAudienceAndMeasurementBothDisabled();
+
+ boolean result = mService.onStartJob(mock(JobParameters.class));
+ assertTrue(result);
+ Thread.sleep(2000);
+ verify(mService, times(1)).jobFinished(any(), eq(false));
+ }
+
+ @Test
+ public void onStartJobTestKillSwitchEnabled() {
+ when(mMockFlags.getGlobalKillSwitch()).thenReturn(true);
+ doReturn(mJobScheduler).when(mService).getSystemService(JobScheduler.class);
+ mService.schedule(mContext);
+ assertNotNull(
+ mJobScheduler.getPendingJob(OnDevicePersonalizationConfig.USER_DATA_COLLECTION_ID));
+
+ boolean result = mService.onStartJob(mock(JobParameters.class));
+
+ assertTrue(result);
+ verify(mService, times(1)).jobFinished(any(), eq(false));
+ assertNull(
+ mJobScheduler.getPendingJob(OnDevicePersonalizationConfig.USER_DATA_COLLECTION_ID));
+ }
+
+ @Test
+ public void onStartJobTestUserControlRevoked() throws Exception {
+ mUserDataCollector.updateUserData(RawUserData.getInstance());
+ assertTrue(mUserDataCollector.isInitialized());
+ ExtendedMockito.doReturn(mUserPrivacyStatus).when(UserPrivacyStatus::getInstance);
+ ExtendedMockito.doReturn(true).when(mUserPrivacyStatus)
+ .isProtectedAudienceAndMeasurementBothDisabled();
+
+ boolean result = mService.onStartJob(mock(JobParameters.class));
+
+ assertTrue(result);
+ Thread.sleep(2000);
+ verify(mService, times(1)).jobFinished(any(), eq(false));
+ assertFalse(mUserDataCollector.isInitialized());
+ }
+
+ @Test
+ public void onStopJobTest() {
+ assertTrue(mService.onStopJob(mock(JobParameters.class)));
+ }
+
+ private class TestInjector extends UserDataCollectionJobService.Injector {
+ @Override
+ ListeningExecutorService getExecutor() {
+ return MoreExecutors.newDirectExecutorService();
+ }
+
+ @Override
+ Flags getFlags() {
+ return mMockFlags;
+ }
+ }
}
diff --git a/tests/servicetests/src/com/android/ondevicepersonalization/services/data/user/UserDataCollectorTest.java b/tests/servicetests/src/com/android/ondevicepersonalization/services/data/user/UserDataCollectorTest.java
index 0e558a9..ee69287 100644
--- a/tests/servicetests/src/com/android/ondevicepersonalization/services/data/user/UserDataCollectorTest.java
+++ b/tests/servicetests/src/com/android/ondevicepersonalization/services/data/user/UserDataCollectorTest.java
@@ -21,8 +21,8 @@
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.when;
@@ -112,12 +112,10 @@
assertTrue(mUserData.availableStorageBytes >= 0);
assertTrue(mUserData.batteryPercentage >= 0);
assertTrue(mUserData.batteryPercentage <= 100);
- assertNotNull(mUserData.networkCapabilities);
-
assertTrue(UserDataCollector.ALLOWED_NETWORK_TYPE.contains(mUserData.dataNetworkType));
mCollector.updateUserData(mUserData);
- assertTrue(mUserData.installedApps.size() > 0);
+ assertFalse(mUserData.installedApps.isEmpty());
}
@Test
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 9e46098..fa2491f 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
@@ -16,23 +16,42 @@
package com.android.ondevicepersonalization.services.data.user;
+import static android.adservices.ondevicepersonalization.Constants.STATUS_CALLER_NOT_ALLOWED;
+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;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.ondevicepersonalization.services.PhFlags.KEY_ENABLE_PERSONALIZATION_STATUS_OVERRIDE;
+import static com.android.ondevicepersonalization.services.PhFlags.KEY_PERSONALIZATION_STATUS_OVERRIDE_VALUE;
+import static com.android.ondevicepersonalization.services.PhFlags.KEY_USER_CONTROL_CACHE_IN_MILLIS;
+
+import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.spy;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.when;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.android.modules.utils.testing.ExtendedMockitoRule;
+import com.android.odp.module.common.Clock;
import com.android.ondevicepersonalization.services.Flags;
import com.android.ondevicepersonalization.services.FlagsFactory;
import com.android.ondevicepersonalization.services.PhFlagsTestUtil;
+import com.android.ondevicepersonalization.services.StableFlags;
import com.android.ondevicepersonalization.services.reset.ResetDataJobService;
+import com.android.ondevicepersonalization.services.util.DebugUtils;
+import com.android.ondevicepersonalization.services.util.StatsUtils;
+
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
import org.junit.After;
import org.junit.Before;
@@ -40,21 +59,53 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
-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;
private static final int CONTROL_RESET_STATUS_CODE = 5;
+ private static final long CACHE_TIMEOUT_MILLIS = 10000;
+ private long mClockTime = 1000L;
+ private boolean mCommonStatesWrapperCalled = false;
+ private AdServicesCommonStatesWrapper.CommonStatesResult mCommonStatesResult =
+ new AdServicesCommonStatesWrapper.CommonStatesResult(
+ UserPrivacyStatus.CONTROL_GIVEN_STATUS_CODE,
+ UserPrivacyStatus.CONTROL_GIVEN_STATUS_CODE);
- @Spy
- private Flags mSpyFlags = spy(FlagsFactory.getFlags());
+ private Flags mSpyFlags = new Flags() {
+ @Override public boolean getGlobalKillSwitch() {
+ return false;
+ }
+ };
+
+ private Clock mTestClock = new Clock() {
+ @Override public long elapsedRealtime() {
+ return mClockTime;
+ }
+ @Override public long currentTimeMillis() {
+ return mClockTime;
+ }
+ };
+
+ private AdServicesCommonStatesWrapper mCommonStatesWrapper =
+ new AdServicesCommonStatesWrapper() {
+ @Override public ListenableFuture<CommonStatesResult> getCommonStates() {
+ mCommonStatesWrapperCalled = true;
+ return Futures.immediateFuture(mCommonStatesResult);
+ }
+ };
@Rule
public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
+ .mockStatic(DebugUtils.class)
.mockStatic(FlagsFactory.class)
+ .mockStatic(StatsUtils.class)
.spyStatic(ResetDataJobService.class)
+ .spyStatic(StableFlags.class)
.setStrictness(Strictness.LENIENT)
.build();
@@ -62,9 +113,15 @@
public void setup() throws Exception {
PhFlagsTestUtil.setUpDeviceConfigPermissions();
ExtendedMockito.doReturn(mSpyFlags).when(FlagsFactory::getFlags);
- when(mSpyFlags.getGlobalKillSwitch()).thenReturn(false);
- when(mSpyFlags.getPersonalizationStatusOverrideValue()).thenReturn(false);
- mUserPrivacyStatus = UserPrivacyStatus.getInstance();
+ ExtendedMockito.doNothing().when(() -> StatsUtils.writeServiceRequestMetrics(
+ anyInt(), anyString(), any(), any(), anyInt(), anyLong()));
+ ExtendedMockito.doReturn(false).when(
+ () -> StableFlags.get(KEY_ENABLE_PERSONALIZATION_STATUS_OVERRIDE));
+ ExtendedMockito.doReturn(false).when(
+ () -> StableFlags.get(KEY_PERSONALIZATION_STATUS_OVERRIDE_VALUE));
+ ExtendedMockito.doReturn(CACHE_TIMEOUT_MILLIS).when(
+ () -> StableFlags.get(KEY_USER_CONTROL_CACHE_IN_MILLIS));
+ mUserPrivacyStatus = new UserPrivacyStatus(mCommonStatesWrapper, mTestClock);
doReturn(RESULT_SUCCESS).when(ResetDataJobService::schedule);
}
@@ -111,6 +168,82 @@
assertFalse(mUserPrivacyStatus.isUserControlCacheValid());
}
+ @Test
+ public void testFetchesFromAdServicesOnCacheTimeout() {
+ mUserPrivacyStatus.invalidateUserControlCacheForTesting();
+ assertFalse(mUserPrivacyStatus.isUserControlCacheValid());
+ var unused = mUserPrivacyStatus.isMeasurementEnabled();
+ assertTrue(mCommonStatesWrapperCalled);
+ mCommonStatesWrapperCalled = false;
+ var unused2 = mUserPrivacyStatus.isMeasurementEnabled();
+ assertFalse(mCommonStatesWrapperCalled);
+ mClockTime += 2 * CACHE_TIMEOUT_MILLIS;
+ var unused3 = mUserPrivacyStatus.isMeasurementEnabled();
+ assertTrue(mCommonStatesWrapperCalled);
+ }
+
+ @Test
+ public void testOverrideEnabledOnDeveloperModeOverrideTrue() {
+ mUserPrivacyStatus.updateUserControlCache(
+ UserPrivacyStatus.CONTROL_REVOKED_STATUS_CODE,
+ UserPrivacyStatus.CONTROL_REVOKED_STATUS_CODE);
+ ExtendedMockito.doReturn(true).when(
+ () -> StableFlags.get(KEY_ENABLE_PERSONALIZATION_STATUS_OVERRIDE));
+ ExtendedMockito.doReturn(true).when(
+ () -> StableFlags.get(KEY_PERSONALIZATION_STATUS_OVERRIDE_VALUE));
+ doReturn(true).when(() -> DebugUtils.isDeveloperModeEnabled(any()));
+
+ assertFalse(mUserPrivacyStatus.isProtectedAudienceAndMeasurementBothDisabled());
+ assertTrue(mUserPrivacyStatus.isMeasurementEnabled());
+ assertTrue(mUserPrivacyStatus.isProtectedAudienceEnabled());
+ }
+
+ @Test
+ public void testOverrideEnabledOnDeveloperModeOverrideFalse() {
+ mUserPrivacyStatus.updateUserControlCache(
+ UserPrivacyStatus.CONTROL_GIVEN_STATUS_CODE,
+ UserPrivacyStatus.CONTROL_GIVEN_STATUS_CODE);
+ ExtendedMockito.doReturn(true).when(
+ () -> StableFlags.get(KEY_ENABLE_PERSONALIZATION_STATUS_OVERRIDE));
+ ExtendedMockito.doReturn(false).when(
+ () -> StableFlags.get(KEY_PERSONALIZATION_STATUS_OVERRIDE_VALUE));
+ doReturn(true).when(() -> DebugUtils.isDeveloperModeEnabled(any()));
+
+ assertTrue(mUserPrivacyStatus.isProtectedAudienceAndMeasurementBothDisabled());
+ assertFalse(mUserPrivacyStatus.isMeasurementEnabled());
+ assertFalse(mUserPrivacyStatus.isProtectedAudienceEnabled());
+ }
+
+ @Test
+ public void testOverrideNotAllowedOnNonDeveloperMode() {
+ mUserPrivacyStatus.updateUserControlCache(
+ UserPrivacyStatus.CONTROL_REVOKED_STATUS_CODE,
+ UserPrivacyStatus.CONTROL_REVOKED_STATUS_CODE);
+ ExtendedMockito.doReturn(true).when(
+ () -> StableFlags.get(KEY_ENABLE_PERSONALIZATION_STATUS_OVERRIDE));
+ ExtendedMockito.doReturn(true).when(
+ () -> StableFlags.get(KEY_PERSONALIZATION_STATUS_OVERRIDE_VALUE));
+ doReturn(false).when(() -> DebugUtils.isDeveloperModeEnabled(any()));
+ assertTrue(mUserPrivacyStatus.isProtectedAudienceAndMeasurementBothDisabled());
+ assertFalse(mUserPrivacyStatus.isMeasurementEnabled());
+ assertFalse(mUserPrivacyStatus.isProtectedAudienceEnabled());
+ }
+
+ @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()))
+ .isEqualTo(STATUS_CALLER_NOT_ALLOWED);
+ assertThat(mUserPrivacyStatus.getExceptionStatus(new IllegalArgumentException()))
+ .isEqualTo(STATUS_INTERNAL_ERROR);
+ assertThat(mUserPrivacyStatus.getExceptionStatus(new Exception()))
+ .isEqualTo(STATUS_REMOTE_EXCEPTION);
+ }
+
@After
public void tearDown() {
mUserPrivacyStatus.resetUserControlForTesting();
diff --git a/tests/servicetests/src/com/android/ondevicepersonalization/services/data/vendor/OnDevicePersonalizationVendorDataDaoTest.java b/tests/servicetests/src/com/android/ondevicepersonalization/services/data/vendor/OnDevicePersonalizationVendorDataDaoTest.java
index 0671667..a736a74 100644
--- a/tests/servicetests/src/com/android/ondevicepersonalization/services/data/vendor/OnDevicePersonalizationVendorDataDaoTest.java
+++ b/tests/servicetests/src/com/android/ondevicepersonalization/services/data/vendor/OnDevicePersonalizationVendorDataDaoTest.java
@@ -215,59 +215,15 @@
OnDevicePersonalizationVendorDataDao instance2Owner1 =
OnDevicePersonalizationVendorDataDao.getInstance(mContext, owner1,
TEST_CERT_DIGEST);
- assertEquals(instance1Owner1, instance2Owner1);
ComponentName owner2 = new ComponentName("owner2", "cls2");
OnDevicePersonalizationVendorDataDao instance1Owner2 =
OnDevicePersonalizationVendorDataDao.getInstance(mContext, owner2,
TEST_CERT_DIGEST);
+
+ com.google.common.truth.Truth.assertThat(instance1Owner1).isSameInstanceAs(instance2Owner1);
assertNotEquals(instance1Owner1, instance1Owner2);
}
- @After
- public void cleanup() {
- OnDevicePersonalizationDbHelper dbHelper =
- OnDevicePersonalizationDbHelper.getInstanceForTest(mContext);
- dbHelper.getWritableDatabase().close();
- dbHelper.getReadableDatabase().close();
- dbHelper.close();
-
- File vendorDir = new File(mContext.getFilesDir(), "VendorData");
- File localDir = new File(mContext.getFilesDir(), "LocalData");
- FileUtils.deleteDirectory(vendorDir);
- FileUtils.deleteDirectory(localDir);
- }
-
- private void addTestData(long timestamp) {
- addTestData(timestamp, mDao);
- }
-
- private void addTestData(long timestamp, OnDevicePersonalizationVendorDataDao dao) {
- List<VendorData> dataList = new ArrayList<>();
- dataList.add(new VendorData.Builder().setKey("key").setData(new byte[10]).build());
- dataList.add(new VendorData.Builder().setKey("key2").setData(new byte[10]).build());
- dataList.add(new VendorData.Builder().setKey("large").setData(new byte[111111]).build());
- dataList.add(new VendorData.Builder().setKey("large2").setData(new byte[111111]).build());
- dataList.add(new VendorData.Builder().setKey("xlarge").setData(new byte[5555555]).build());
-
- List<String> retainedKeys = new ArrayList<>();
- retainedKeys.add("key");
- retainedKeys.add("key2");
- retainedKeys.add("large");
- retainedKeys.add("large2");
- retainedKeys.add("xlarge");
- assertTrue(dao.batchUpdateOrInsertVendorDataTransaction(dataList, retainedKeys,
- timestamp));
- }
-
- private void addEventState(ComponentName service) {
- EventState eventState = new EventState.Builder()
- .setTaskIdentifier(TASK_IDENTIFIER)
- .setService(service)
- .setToken(new byte[]{1})
- .build();
- mEventsDao.updateOrInsertEventState(eventState);
- }
-
@Test
public void testDeleteVendorTables() throws Exception {
ExtendedMockito.doReturn(TEST_CERT_DIGEST)
@@ -319,4 +275,49 @@
assertFalse(dir.exists());
assertNull(mEventsDao.getEventState(TASK_IDENTIFIER, TEST_OWNER));
}
+
+ @After
+ public void cleanup() {
+ OnDevicePersonalizationDbHelper dbHelper =
+ OnDevicePersonalizationDbHelper.getInstanceForTest(mContext);
+ dbHelper.getWritableDatabase().close();
+ dbHelper.getReadableDatabase().close();
+ dbHelper.close();
+
+ File vendorDir = new File(mContext.getFilesDir(), "VendorData");
+ File localDir = new File(mContext.getFilesDir(), "LocalData");
+ FileUtils.deleteDirectory(vendorDir);
+ FileUtils.deleteDirectory(localDir);
+ }
+
+ private void addTestData(long timestamp) {
+ addTestData(timestamp, mDao);
+ }
+
+ private static void addTestData(long timestamp, OnDevicePersonalizationVendorDataDao dao) {
+ List<VendorData> dataList = new ArrayList<>();
+ dataList.add(new VendorData.Builder().setKey("key").setData(new byte[10]).build());
+ dataList.add(new VendorData.Builder().setKey("key2").setData(new byte[10]).build());
+ dataList.add(new VendorData.Builder().setKey("large").setData(new byte[111111]).build());
+ dataList.add(new VendorData.Builder().setKey("large2").setData(new byte[111111]).build());
+ dataList.add(new VendorData.Builder().setKey("xlarge").setData(new byte[5555555]).build());
+
+ List<String> retainedKeys = new ArrayList<>();
+ retainedKeys.add("key");
+ retainedKeys.add("key2");
+ retainedKeys.add("large");
+ retainedKeys.add("large2");
+ retainedKeys.add("xlarge");
+ assertTrue(dao.batchUpdateOrInsertVendorDataTransaction(dataList, retainedKeys, timestamp));
+ }
+
+ private void addEventState(ComponentName service) {
+ EventState eventState =
+ new EventState.Builder()
+ .setTaskIdentifier(TASK_IDENTIFIER)
+ .setService(service)
+ .setToken(new byte[] {1})
+ .build();
+ mEventsDao.updateOrInsertEventState(eventState);
+ }
}
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 52539a0..6460c9a 100644
--- a/tests/servicetests/src/com/android/ondevicepersonalization/services/display/OdpWebViewClientTests.java
+++ b/tests/servicetests/src/com/android/ondevicepersonalization/services/display/OdpWebViewClientTests.java
@@ -16,6 +16,8 @@
package com.android.ondevicepersonalization.services.display;
+import static com.android.ondevicepersonalization.services.PhFlags.KEY_SHARED_ISOLATED_PROCESS_FEATURE_ENABLED;
+
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -23,10 +25,8 @@
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
import android.adservices.ondevicepersonalization.EventOutputParcel;
import android.adservices.ondevicepersonalization.RequestLogRecord;
@@ -53,6 +53,7 @@
import com.android.ondevicepersonalization.services.FlagsFactory;
import com.android.ondevicepersonalization.services.OnDevicePersonalizationExecutors;
import com.android.ondevicepersonalization.services.PhFlagsTestUtil;
+import com.android.ondevicepersonalization.services.StableFlags;
import com.android.ondevicepersonalization.services.data.OnDevicePersonalizationDbHelper;
import com.android.ondevicepersonalization.services.data.events.EventUrlHelper;
import com.android.ondevicepersonalization.services.data.events.EventUrlPayload;
@@ -71,7 +72,6 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
-import org.mockito.Spy;
import org.mockito.quality.Strictness;
import java.net.HttpURLConnection;
@@ -120,27 +120,32 @@
);
}
- @Spy
- private Flags mSpyFlags = spy(FlagsFactory.getFlags());
+ private Flags mSpyFlags = new Flags() {
+ int mIsolatedServiceDeadlineSeconds = 30;
+ @Override public int getIsolatedServiceDeadlineSeconds() {
+ return mIsolatedServiceDeadlineSeconds;
+ }
+ };
@Rule
public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
.mockStatic(FlagsFactory.class)
+ .spyStatic(StableFlags.class)
.setStrictness(Strictness.LENIENT)
.build();
@Before
public void setup() throws Exception {
PhFlagsTestUtil.setUpDeviceConfigPermissions();
+ ExtendedMockito.doReturn(mSpyFlags).when(FlagsFactory::getFlags);
+ ExtendedMockito.doReturn(SdkLevel.isAtLeastU() && mIsSipFeatureEnabled).when(
+ () -> StableFlags.get(KEY_SHARED_ISOLATED_PROCESS_FEATURE_ENABLED));
mDbHelper = OnDevicePersonalizationDbHelper.getInstanceForTest(mContext);
mDao = EventsDao.getInstanceForTest(mContext);
// Insert query for FK constraint
mDao.insertQuery(mTestQuery);
mLatch = new CountDownLatch(1);
- ExtendedMockito.doReturn(mSpyFlags).when(FlagsFactory::getFlags);
- when(mSpyFlags.isSharedIsolatedProcessFeatureEnabled())
- .thenReturn(SdkLevel.isAtLeastU() && mIsSipFeatureEnabled);
ShellUtils.runShellCommand("settings put global hidden_api_policy 1");
CountDownLatch latch = new CountDownLatch(1);
diff --git a/tests/servicetests/src/com/android/ondevicepersonalization/services/download/OnDevicePersonalizationDataProcessingAsyncCallableTests.java b/tests/servicetests/src/com/android/ondevicepersonalization/services/download/OnDevicePersonalizationDataProcessingAsyncCallableTests.java
index b1f43ef..5edf262 100644
--- a/tests/servicetests/src/com/android/ondevicepersonalization/services/download/OnDevicePersonalizationDataProcessingAsyncCallableTests.java
+++ b/tests/servicetests/src/com/android/ondevicepersonalization/services/download/OnDevicePersonalizationDataProcessingAsyncCallableTests.java
@@ -16,12 +16,12 @@
package com.android.ondevicepersonalization.services.download;
+import static com.android.ondevicepersonalization.services.PhFlags.KEY_SHARED_ISOLATED_PROCESS_FEATURE_ENABLED;
+
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.when;
import android.adservices.ondevicepersonalization.DownloadCompletedOutputParcel;
import android.content.ComponentName;
@@ -38,6 +38,7 @@
import com.android.ondevicepersonalization.services.Flags;
import com.android.ondevicepersonalization.services.FlagsFactory;
import com.android.ondevicepersonalization.services.PhFlagsTestUtil;
+import com.android.ondevicepersonalization.services.StableFlags;
import com.android.ondevicepersonalization.services.data.OnDevicePersonalizationDbHelper;
import com.android.ondevicepersonalization.services.data.vendor.OnDevicePersonalizationVendorDataDao;
import com.android.ondevicepersonalization.services.data.vendor.VendorData;
@@ -60,7 +61,6 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
-import org.mockito.Spy;
import org.mockito.quality.Strictness;
import java.util.ArrayList;
@@ -109,12 +109,17 @@
);
}
- @Spy
- private Flags mSpyFlags = spy(FlagsFactory.getFlags());
+ private Flags mSpyFlags = new Flags() {
+ int mIsolatedServiceDeadlineSeconds = 30;
+ @Override public int getIsolatedServiceDeadlineSeconds() {
+ return mIsolatedServiceDeadlineSeconds;
+ }
+ };
@Rule
public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
.mockStatic(FlagsFactory.class)
+ .spyStatic(StableFlags.class)
.setStrictness(Strictness.LENIENT)
.build();
@@ -138,8 +143,8 @@
PhFlagsTestUtil.setUpDeviceConfigPermissions();
ExtendedMockito.doReturn(mSpyFlags).when(FlagsFactory::getFlags);
- when(mSpyFlags.isSharedIsolatedProcessFeatureEnabled())
- .thenReturn(SdkLevel.isAtLeastU() && mIsSipFeatureEnabled);
+ ExtendedMockito.doReturn(SdkLevel.isAtLeastU() && mIsSipFeatureEnabled).when(
+ () -> StableFlags.get(KEY_SHARED_ISOLATED_PROCESS_FEATURE_ENABLED));
ShellUtils.runShellCommand("settings put global hidden_api_policy 1");
mLatch = new CountDownLatch(1);
diff --git a/tests/servicetests/src/com/android/ondevicepersonalization/services/download/OnDevicePersonalizationDownloadProcessingJobServiceTests.java b/tests/servicetests/src/com/android/ondevicepersonalization/services/download/OnDevicePersonalizationDownloadProcessingJobServiceTests.java
index ca25a4b..47edfcb 100644
--- a/tests/servicetests/src/com/android/ondevicepersonalization/services/download/OnDevicePersonalizationDownloadProcessingJobServiceTests.java
+++ b/tests/servicetests/src/com/android/ondevicepersonalization/services/download/OnDevicePersonalizationDownloadProcessingJobServiceTests.java
@@ -32,7 +32,6 @@
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
import android.app.job.JobParameters;
import android.app.job.JobScheduler;
@@ -57,7 +56,6 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
-import org.mockito.Spy;
import org.mockito.quality.Strictness;
import java.util.concurrent.CountDownLatch;
@@ -66,8 +64,14 @@
public class OnDevicePersonalizationDownloadProcessingJobServiceTests {
private final Context mContext = ApplicationProvider.getApplicationContext();
- @Spy
- private Flags mSpyFlags = spy(FlagsFactory.getFlags());
+ static class TestFlags implements Flags {
+ boolean mGlobalKillSwitch = false;
+ @Override public boolean getGlobalKillSwitch() {
+ return mGlobalKillSwitch;
+ }
+ }
+
+ private TestFlags mSpyFlags = new TestFlags();
@Rule
public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
@@ -82,7 +86,6 @@
public void setup() throws Exception {
PhFlagsTestUtil.setUpDeviceConfigPermissions();
ExtendedMockito.doReturn(mSpyFlags).when(FlagsFactory::getFlags);
- when(mSpyFlags.getGlobalKillSwitch()).thenReturn(false);
// Use direct executor to keep all work sequential for the tests
ListeningExecutorService executorService = MoreExecutors.newDirectExecutorService();
MobileDataDownloadFactory.getMdd(mContext, executorService, executorService);
@@ -125,7 +128,7 @@
@Test
public void onStartJobTestKillSwitchEnabled() {
- when(mSpyFlags.getGlobalKillSwitch()).thenReturn(true);
+ mSpyFlags.mGlobalKillSwitch = true;
doNothing().when(mSpyService).jobFinished(any(), anyBoolean());
boolean result = mSpyService.onStartJob(mock(JobParameters.class));
assertTrue(result);
diff --git a/tests/servicetests/src/com/android/ondevicepersonalization/services/download/mdd/MddJobServiceTest.java b/tests/servicetests/src/com/android/ondevicepersonalization/services/download/mdd/MddJobServiceTest.java
index b447841..fcd2201 100644
--- a/tests/servicetests/src/com/android/ondevicepersonalization/services/download/mdd/MddJobServiceTest.java
+++ b/tests/servicetests/src/com/android/ondevicepersonalization/services/download/mdd/MddJobServiceTest.java
@@ -48,9 +48,7 @@
import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.android.modules.utils.testing.ExtendedMockitoRule;
import com.android.ondevicepersonalization.services.Flags;
-import com.android.ondevicepersonalization.services.FlagsFactory;
import com.android.ondevicepersonalization.services.OnDevicePersonalizationExecutors;
-import com.android.ondevicepersonalization.services.PhFlagsTestUtil;
import com.android.ondevicepersonalization.services.data.user.UserPrivacyStatus;
import com.google.common.util.concurrent.ListeningExecutorService;
@@ -61,7 +59,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
-import org.mockito.Spy;
+import org.mockito.Mock;
import org.mockito.quality.Strictness;
@RunWith(JUnit4.class)
@@ -72,27 +70,36 @@
private MddJobService mSpyService;
private UserPrivacyStatus mUserPrivacyStatus;
- @Spy
- private Flags mSpyFlags = spy(FlagsFactory.getFlags());
+ @Mock
+ private Flags mMockFlags;
@Rule
public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
- .mockStatic(FlagsFactory.class)
.spyStatic(UserPrivacyStatus.class)
.spyStatic(OnDevicePersonalizationExecutors.class)
.setStrictness(Strictness.LENIENT)
.build();
+ private class TestInjector extends MddJobService.Injector {
+ @Override
+ ListeningExecutorService getBackgroundExecutor() {
+ return MoreExecutors.newDirectExecutorService();
+ }
+
+ @Override
+ Flags getFlags() {
+ return mMockFlags;
+ }
+ }
+
@Before
public void setup() throws Exception {
- PhFlagsTestUtil.setUpDeviceConfigPermissions();
- ExtendedMockito.doReturn(mSpyFlags).when(FlagsFactory::getFlags);
- when(mSpyFlags.getGlobalKillSwitch()).thenReturn(false);
+ when(mMockFlags.getGlobalKillSwitch()).thenReturn(false);
mUserPrivacyStatus = spy(UserPrivacyStatus.getInstance());
ListeningExecutorService executorService = MoreExecutors.newDirectExecutorService();
MobileDataDownloadFactory.getMdd(mContext, executorService, executorService);
- mSpyService = spy(new MddJobService());
+ mSpyService = spy(new MddJobService(new TestInjector()));
mMockJobScheduler = mock(JobScheduler.class);
doNothing().when(mSpyService).jobFinished(any(), anyBoolean());
doReturn(mMockJobScheduler).when(mSpyService).getSystemService(JobScheduler.class);
@@ -108,14 +115,12 @@
}
@Test
- public void onStartJobTest() {
+ public void onStartJobTest() throws Exception {
ExtendedMockito.doReturn(MoreExecutors.newDirectExecutorService()).when(
OnDevicePersonalizationExecutors::getBackgroundExecutor);
- ExtendedMockito.doReturn(MoreExecutors.newDirectExecutorService()).when(
- OnDevicePersonalizationExecutors::getLightweightExecutor);
ExtendedMockito.doReturn(mUserPrivacyStatus).when(UserPrivacyStatus::getInstance);
- ExtendedMockito.doReturn(true).when(mUserPrivacyStatus).isProtectedAudienceEnabled();
- ExtendedMockito.doReturn(true).when(mUserPrivacyStatus).isMeasurementEnabled();
+ ExtendedMockito.doReturn(false).when(mUserPrivacyStatus)
+ .isProtectedAudienceAndMeasurementBothDisabled();
JobParameters jobParameters = mock(JobParameters.class);
PersistableBundle extras = new PersistableBundle();
@@ -124,13 +129,14 @@
boolean result = mSpyService.onStartJob(jobParameters);
assertTrue(result);
+ Thread.sleep(5000);
verify(mSpyService, times(1)).jobFinished(any(), eq(false));
verify(mMockJobScheduler, times(1)).schedule(any());
}
@Test
public void onStartJobTestKillSwitchEnabled() {
- when(mSpyFlags.getGlobalKillSwitch()).thenReturn(true);
+ when(mMockFlags.getGlobalKillSwitch()).thenReturn(true);
JobScheduler mJobScheduler = mContext.getSystemService(JobScheduler.class);
PersistableBundle extras = new PersistableBundle();
extras.putString(MDD_TASK_TAG_KEY, WIFI_CHARGING_PERIODIC_TASK);
@@ -159,10 +165,10 @@
}
@Test
- public void onStartJobTestUserControlRevoked() {
+ public void onStartJobTestUserControlRevoked() throws Exception {
ExtendedMockito.doReturn(mUserPrivacyStatus).when(UserPrivacyStatus::getInstance);
- ExtendedMockito.doReturn(false).when(mUserPrivacyStatus).isProtectedAudienceEnabled();
- ExtendedMockito.doReturn(false).when(mUserPrivacyStatus).isMeasurementEnabled();
+ ExtendedMockito.doReturn(true).when(mUserPrivacyStatus)
+ .isProtectedAudienceAndMeasurementBothDisabled();
JobScheduler mJobScheduler = mContext.getSystemService(JobScheduler.class);
PersistableBundle extras = new PersistableBundle();
extras.putString(MDD_TASK_TAG_KEY, WIFI_CHARGING_PERIODIC_TASK);
@@ -185,16 +191,13 @@
doReturn(extras).when(jobParameters).getExtras();
boolean result = mSpyService.onStartJob(jobParameters);
assertTrue(result);
+ Thread.sleep(2000);
verify(mSpyService, times(1)).jobFinished(any(), eq(false));
verify(mMockJobScheduler, times(0)).schedule(any());
}
@Test
public void onStartJobNoTaskTagTest() {
- ExtendedMockito.doReturn(MoreExecutors.newDirectExecutorService()).when(
- OnDevicePersonalizationExecutors::getBackgroundExecutor);
- ExtendedMockito.doReturn(MoreExecutors.newDirectExecutorService()).when(
- OnDevicePersonalizationExecutors::getLightweightExecutor);
assertThrows(IllegalArgumentException.class,
() -> mSpyService.onStartJob(mock(JobParameters.class)));
@@ -203,14 +206,10 @@
}
@Test
- public void onStartJobFailHandleTaskTest() {
- ExtendedMockito.doReturn(MoreExecutors.newDirectExecutorService()).when(
- OnDevicePersonalizationExecutors::getBackgroundExecutor);
- ExtendedMockito.doReturn(MoreExecutors.newDirectExecutorService()).when(
- OnDevicePersonalizationExecutors::getLightweightExecutor);
+ public void onStartJobFailHandleTaskTest() throws Exception {
ExtendedMockito.doReturn(mUserPrivacyStatus).when(UserPrivacyStatus::getInstance);
- ExtendedMockito.doReturn(true).when(mUserPrivacyStatus).isProtectedAudienceEnabled();
- ExtendedMockito.doReturn(true).when(mUserPrivacyStatus).isMeasurementEnabled();
+ ExtendedMockito.doReturn(false).when(mUserPrivacyStatus)
+ .isProtectedAudienceAndMeasurementBothDisabled();
JobParameters jobParameters = mock(JobParameters.class);
PersistableBundle extras = new PersistableBundle();
@@ -219,6 +218,7 @@
boolean result = mSpyService.onStartJob(jobParameters);
assertTrue(result);
+ Thread.sleep(2000);
verify(mSpyService, times(1)).jobFinished(any(), eq(false));
verify(mMockJobScheduler, times(0)).schedule(any());
}
diff --git a/tests/servicetests/src/com/android/ondevicepersonalization/services/federatedcompute/FederatedComputeServiceImplTest.java b/tests/servicetests/src/com/android/ondevicepersonalization/services/federatedcompute/FederatedComputeServiceImplTest.java
index 4f46648..682892c 100644
--- a/tests/servicetests/src/com/android/ondevicepersonalization/services/federatedcompute/FederatedComputeServiceImplTest.java
+++ b/tests/servicetests/src/com/android/ondevicepersonalization/services/federatedcompute/FederatedComputeServiceImplTest.java
@@ -35,7 +35,6 @@
import android.federatedcompute.common.TrainingInterval;
import android.federatedcompute.common.TrainingOptions;
import android.os.OutcomeReceiver;
-import android.provider.DeviceConfig;
import androidx.test.core.app.ApplicationProvider;
@@ -70,12 +69,24 @@
@RunWith(JUnit4.class)
public class FederatedComputeServiceImplTest {
private static final String FC_SERVER_URL = "https://google.com";
+ private static final String TEST_POPULATION_NAME = "population";
+ private static final TrainingInterval TEST_INTERVAL =
+ new TrainingInterval.Builder()
+ .setMinimumIntervalMillis(100)
+ .setSchedulingMode(1)
+ .build();
+ private static final TrainingOptions TEST_OPTIONS =
+ new TrainingOptions.Builder()
+ .setPopulationName(TEST_POPULATION_NAME)
+ .setTrainingInterval(TEST_INTERVAL)
+ .build();
+
private static final String SERVICE_CLASS = "com.test.TestPersonalizationService";
private final Context mApplicationContext = ApplicationProvider.getApplicationContext();
ArgumentCaptor<OutcomeReceiver<Object, Exception>> mCallbackCapture;
ArgumentCaptor<ScheduleFederatedComputeRequest> mRequestCapture;
- private TestInjector mInjector = new TestInjector();
- private CountDownLatch mLatch = new CountDownLatch(1);
+ private final TestInjector mInjector = new TestInjector();
+ private final CountDownLatch mLatch = new CountDownLatch(1);
private int mErrorCode = 0;
private boolean mOnSuccessCalled = false;
private boolean mOnErrorCalled = false;
@@ -96,7 +107,7 @@
@Before
public void setup() throws Exception {
mIsolatedService = new ComponentName(mApplicationContext.getPackageName(), SERVICE_CLASS);
- mInjector = new TestInjector();
+
mMockManager = Mockito.mock(FederatedComputeManager.class);
mCallbackCapture = ArgumentCaptor.forClass(OutcomeReceiver.class);
mRequestCapture = ArgumentCaptor.forClass(ScheduleFederatedComputeRequest.class);
@@ -123,22 +134,13 @@
@Test
public void testSchedule() throws Exception {
- TrainingInterval interval =
- new TrainingInterval.Builder()
- .setMinimumIntervalMillis(100)
- .setSchedulingMode(1)
- .build();
- TrainingOptions options =
- new TrainingOptions.Builder()
- .setPopulationName("population")
- .setTrainingInterval(interval)
- .build();
- mServiceProxy.schedule(options, new TestCallback());
+ mServiceProxy.schedule(TEST_OPTIONS, new TestCallback());
mCallbackCapture.getValue().onResult(null);
var request = mRequestCapture.getValue();
mLatch.await(1000, TimeUnit.MILLISECONDS);
+
assertEquals(FC_SERVER_URL, request.getTrainingOptions().getServerAddress());
- assertEquals("population", request.getTrainingOptions().getPopulationName());
+ assertEquals(TEST_POPULATION_NAME, request.getTrainingOptions().getPopulationName());
assertTrue(mOnSuccessCalled);
}
@@ -147,18 +149,10 @@
ExtendedMockito.doReturn(mUserPrivacyStatus).when(UserPrivacyStatus::getInstance);
ExtendedMockito.doReturn(false)
.when(mUserPrivacyStatus).isMeasurementEnabled();
- TrainingInterval interval =
- new TrainingInterval.Builder()
- .setMinimumIntervalMillis(100)
- .setSchedulingMode(1)
- .build();
- TrainingOptions options =
- new TrainingOptions.Builder()
- .setPopulationName("population")
- .setTrainingInterval(interval)
- .build();
- mServiceProxy.schedule(options, new TestCallback());
+
+ mServiceProxy.schedule(TEST_OPTIONS, new TestCallback());
mLatch.await(1000, TimeUnit.MILLISECONDS);
+
assertFalse(mOnSuccessCalled);
}
@@ -170,70 +164,23 @@
String overrideUrl = "https://android.com";
ShellUtils.runShellCommand(
"setprop debug.ondevicepersonalization.override_fc_server_url " + overrideUrl);
- TrainingInterval interval =
- new TrainingInterval.Builder()
- .setMinimumIntervalMillis(100)
- .setSchedulingMode(1)
- .build();
- TrainingOptions options =
- new TrainingOptions.Builder()
- .setPopulationName("population")
- .setTrainingInterval(interval)
- .build();
- mServiceProxy.schedule(options, new TestCallback());
- mCallbackCapture.getValue().onResult(null);
- var request = mRequestCapture.getValue();
- mLatch.await(1000, TimeUnit.MILLISECONDS);
- assertEquals(overrideUrl, request.getTrainingOptions().getServerAddress());
- assertEquals("population", request.getTrainingOptions().getPopulationName());
- assertTrue(mOnSuccessCalled);
- }
- @Test
- public void testScheduleUrlDeviceConfigOverride() throws Exception {
- ShellUtils.runShellCommand(
- "setprop debug.ondevicepersonalization.override_fc_server_url_package "
- + mApplicationContext.getPackageName());
- String overrideUrl = "https://cs.android.com";
- DeviceConfig.setProperty(
- DeviceConfig.NAMESPACE_ON_DEVICE_PERSONALIZATION,
- "debug.ondevicepersonalization.override_fc_server_url",
- overrideUrl,
- /* makeDefault= */ false);
- TrainingInterval interval =
- new TrainingInterval.Builder()
- .setMinimumIntervalMillis(100)
- .setSchedulingMode(1)
- .build();
- TrainingOptions options =
- new TrainingOptions.Builder()
- .setPopulationName("population")
- .setTrainingInterval(interval)
- .build();
- mServiceProxy.schedule(options, new TestCallback());
+ mServiceProxy.schedule(TEST_OPTIONS, new TestCallback());
mCallbackCapture.getValue().onResult(null);
var request = mRequestCapture.getValue();
mLatch.await(1000, TimeUnit.MILLISECONDS);
+
assertEquals(overrideUrl, request.getTrainingOptions().getServerAddress());
- assertEquals("population", request.getTrainingOptions().getPopulationName());
+ assertEquals(TEST_POPULATION_NAME, request.getTrainingOptions().getPopulationName());
assertTrue(mOnSuccessCalled);
}
@Test
public void testScheduleErr() throws Exception {
- TrainingInterval interval =
- new TrainingInterval.Builder()
- .setMinimumIntervalMillis(100)
- .setSchedulingMode(1)
- .build();
- TrainingOptions options =
- new TrainingOptions.Builder()
- .setPopulationName("population")
- .setTrainingInterval(interval)
- .build();
- mServiceProxy.schedule(options, new TestCallback());
+ mServiceProxy.schedule(TEST_OPTIONS, new TestCallback());
mCallbackCapture.getValue().onError(new Exception());
mLatch.await(1000, TimeUnit.MILLISECONDS);
+
assertTrue(mOnErrorCalled);
assertEquals(ClientConstants.STATUS_INTERNAL_ERROR, mErrorCode);
}
@@ -244,19 +191,22 @@
.updateOrInsertEventState(
new EventState.Builder()
.setService(mIsolatedService)
- .setTaskIdentifier("population")
+ .setTaskIdentifier(TEST_POPULATION_NAME)
.setToken(new byte[] {})
.build());
- mServiceProxy.cancel("population", new TestCallback());
+
+ mServiceProxy.cancel(TEST_POPULATION_NAME, new TestCallback());
mCallbackCapture.getValue().onResult(null);
mLatch.await(1000, TimeUnit.MILLISECONDS);
+
assertTrue(mOnSuccessCalled);
}
@Test
public void testCancelNoPopulation() throws Exception {
- mServiceProxy.cancel("population", new TestCallback());
+ mServiceProxy.cancel(TEST_POPULATION_NAME, new TestCallback());
mLatch.await(1000, TimeUnit.MILLISECONDS);
+
verify(mMockManager, times(0)).cancel(any(), any(), any(), any());
assertTrue(mOnSuccessCalled);
}
@@ -267,12 +217,14 @@
.updateOrInsertEventState(
new EventState.Builder()
.setService(mIsolatedService)
- .setTaskIdentifier("population")
+ .setTaskIdentifier(TEST_POPULATION_NAME)
.setToken(new byte[] {})
.build());
- mServiceProxy.cancel("population", new TestCallback());
+
+ mServiceProxy.cancel(TEST_POPULATION_NAME, new TestCallback());
mCallbackCapture.getValue().onError(new Exception());
mLatch.await(1000, TimeUnit.MILLISECONDS);
+
assertTrue(mOnErrorCalled);
assertEquals(ClientConstants.STATUS_INTERNAL_ERROR, mErrorCode);
}
@@ -283,6 +235,7 @@
"setprop debug.ondevicepersonalization.override_fc_server_url_package \"\"");
ShellUtils.runShellCommand(
"setprop debug.ondevicepersonalization.override_fc_server_url \"\"");
+
OnDevicePersonalizationDbHelper dbHelper =
OnDevicePersonalizationDbHelper.getInstanceForTest(mApplicationContext);
dbHelper.getWritableDatabase().close();
@@ -290,7 +243,7 @@
dbHelper.close();
}
- class TestCallback extends IFederatedComputeCallback.Stub {
+ private class TestCallback extends IFederatedComputeCallback.Stub {
@Override
public void onSuccess() {
mOnSuccessCalled = true;
@@ -305,7 +258,7 @@
}
}
- class TestInjector extends FederatedComputeServiceImpl.Injector {
+ private class TestInjector extends FederatedComputeServiceImpl.Injector {
ListeningExecutorService getExecutor() {
return MoreExecutors.newDirectExecutorService();
diff --git a/tests/servicetests/src/com/android/ondevicepersonalization/services/federatedcompute/OdpExampleStoreServiceTests.java b/tests/servicetests/src/com/android/ondevicepersonalization/services/federatedcompute/OdpExampleStoreServiceTests.java
index 5bdcd2e..086d560 100644
--- a/tests/servicetests/src/com/android/ondevicepersonalization/services/federatedcompute/OdpExampleStoreServiceTests.java
+++ b/tests/servicetests/src/com/android/ondevicepersonalization/services/federatedcompute/OdpExampleStoreServiceTests.java
@@ -25,6 +25,7 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.when;
@@ -54,10 +55,10 @@
import com.android.ondevicepersonalization.services.data.events.EventState;
import com.android.ondevicepersonalization.services.data.events.EventsDao;
import com.android.ondevicepersonalization.services.data.user.UserPrivacyStatus;
+import com.android.ondevicepersonalization.testing.utils.DeviceSupportHelper;
import org.junit.After;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -108,6 +109,7 @@
@Before
public void setUp() throws Exception {
+ assumeTrue(DeviceSupportHelper.isDeviceSupported());
initMocks(this);
when(mMockContext.getApplicationContext()).thenReturn(mContext);
ExtendedMockito.doReturn(mUserPrivacyStatus).when(UserPrivacyStatus::getInstance);
@@ -191,7 +193,6 @@
}
@Test
- @Ignore("TODO: b/342475912 - temporary disable failing tests.")
public void testWithStartQuery() throws Exception {
mEventsDao.updateOrInsertEventState(
new EventState.Builder()
@@ -335,6 +336,40 @@
assertFalse(mQueryCallbackOnFailureCalled);
}
+ @Test
+ public void testStartQuery_isolatedServiceThrowsException() throws Exception {
+ mEventsDao.updateOrInsertEventState(
+ new EventState.Builder()
+ .setTaskIdentifier("throw_exception")
+ .setService(mIsolatedService)
+ .setToken()
+ .build());
+ mService.onCreate();
+ Intent intent = new Intent();
+ intent.setAction(EXAMPLE_STORE_ACTION).setPackage(mContext.getPackageName());
+ IExampleStoreService binder =
+ IExampleStoreService.Stub.asInterface(mService.onBind(intent));
+ assertNotNull(binder);
+ TestQueryCallback callback = new TestQueryCallback();
+ Bundle input = new Bundle();
+ ContextData contextData =
+ new ContextData(mIsolatedService.getPackageName(), mIsolatedService.getClassName());
+ input.putByteArray(
+ ClientConstants.EXTRA_CONTEXT_DATA, ContextData.toByteArray(contextData));
+ input.putString(ClientConstants.EXTRA_POPULATION_NAME, "throw_exception");
+ input.putString(ClientConstants.EXTRA_TASK_ID, "TaskName");
+ input.putString(ClientConstants.EXTRA_COLLECTION_URI, "CollectionUri");
+ input.putInt(ClientConstants.EXTRA_ELIGIBILITY_MIN_EXAMPLE, 4);
+
+ binder.startQuery(input, callback);
+ assertTrue(
+ "timeout reached while waiting for countdownlatch!",
+ mLatch.await(5000, TimeUnit.MILLISECONDS));
+
+ assertFalse(mQueryCallbackOnSuccessCalled);
+ assertTrue(mQueryCallbackOnFailureCalled);
+ }
+
public class TestIteratorCallback implements IExampleStoreIteratorCallback {
byte[] mExpectedExample;
byte[] mExpectedResumptionToken;
diff --git a/tests/servicetests/src/com/android/ondevicepersonalization/services/inference/IsolatedModelServiceImplTest.java b/tests/servicetests/src/com/android/ondevicepersonalization/services/inference/IsolatedModelServiceImplTest.java
index 377c090..9868331 100644
--- a/tests/servicetests/src/com/android/ondevicepersonalization/services/inference/IsolatedModelServiceImplTest.java
+++ b/tests/servicetests/src/com/android/ondevicepersonalization/services/inference/IsolatedModelServiceImplTest.java
@@ -30,6 +30,7 @@
import android.adservices.ondevicepersonalization.InferenceOutput;
import android.adservices.ondevicepersonalization.InferenceOutputParcel;
import android.adservices.ondevicepersonalization.ModelId;
+import android.adservices.ondevicepersonalization.OnDevicePersonalizationException;
import android.adservices.ondevicepersonalization.RemoteDataImpl;
import android.adservices.ondevicepersonalization.aidl.IDataAccessService;
import android.adservices.ondevicepersonalization.aidl.IDataAccessServiceCallback;
@@ -53,6 +54,7 @@
import java.util.Map;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
public class IsolatedModelServiceImplTest {
private static final LoggerFactory.Logger sLogger = LoggerFactory.getLogger();
@@ -89,11 +91,8 @@
IsolatedModelServiceImpl modelService = new IsolatedModelServiceImpl();
var callback = new TestServiceCallback();
modelService.runInference(bundle, callback);
- callback.mLatch.await();
- assertFalse(callback.mError);
- InferenceOutputParcel result =
- mCallbackResult.getParcelable(Constants.EXTRA_RESULT, InferenceOutputParcel.class);
+ InferenceOutputParcel result = verifyAndGetCallbackResult(callback);
Map<Integer, Object> outputs = result.getData();
float[] output1 = (float[]) outputs.get(0);
assertThat(output1.length).isEqualTo(1);
@@ -119,11 +118,8 @@
IsolatedModelServiceImpl modelService = new IsolatedModelServiceImpl();
var callback = new TestServiceCallback();
modelService.runInference(bundle, callback);
- callback.mLatch.await();
- assertFalse(callback.mError);
- InferenceOutputParcel result =
- mCallbackResult.getParcelable(Constants.EXTRA_RESULT, InferenceOutputParcel.class);
+ InferenceOutputParcel result = verifyAndGetCallbackResult(callback);
Map<Integer, Object> outputs = result.getData();
float[] output1 = (float[]) outputs.get(0);
assertThat(output1.length).isEqualTo(numExample);
@@ -148,11 +144,8 @@
IsolatedModelServiceImpl modelService = new IsolatedModelServiceImpl();
var callback = new TestServiceCallback();
modelService.runInference(bundle, callback);
- callback.mLatch.await();
- assertFalse(callback.mError);
- InferenceOutputParcel result =
- mCallbackResult.getParcelable(Constants.EXTRA_RESULT, InferenceOutputParcel.class);
+ InferenceOutputParcel result = verifyAndGetCallbackResult(callback);
Map<Integer, Object> outputs = result.getData();
float[] output1 = (float[]) outputs.get(0);
assertThat(output1.length).isEqualTo(numExample);
@@ -176,11 +169,8 @@
IsolatedModelServiceImpl modelService = new IsolatedModelServiceImpl();
var callback = new TestServiceCallback();
modelService.runInference(bundle, callback);
- callback.mLatch.await();
- assertFalse(callback.mError);
- InferenceOutputParcel result =
- mCallbackResult.getParcelable(Constants.EXTRA_RESULT, InferenceOutputParcel.class);
+ InferenceOutputParcel result = verifyAndGetCallbackResult(callback);
Map<Integer, Object> outputs = result.getData();
float[] output1 = (float[]) outputs.get(0);
assertThat(output1.length).isEqualTo(numExample);
@@ -205,10 +195,8 @@
IsolatedModelServiceImpl modelService = new IsolatedModelServiceImpl();
var callback = new TestServiceCallback();
modelService.runInference(bundle, callback);
- callback.mLatch.await();
- assertTrue(callback.mError);
- assertThat(callback.mErrorCode).isEqualTo(Constants.STATUS_INTERNAL_ERROR);
+ verifyCallBackError(callback, OnDevicePersonalizationException.ERROR_INFERENCE_FAILED);
}
@Test
@@ -262,9 +250,30 @@
var callback = new TestServiceCallback();
modelService.runInference(bundle, callback);
- callback.mLatch.await();
- assertTrue(callback.mError);
- assertThat(callback.mErrorCode).isEqualTo(Constants.STATUS_INTERNAL_ERROR);
+ verifyCallBackError(callback, OnDevicePersonalizationException.ERROR_INFERENCE_FAILED);
+ }
+
+ @Test
+ public void runModelInference_modelNotExist() throws Exception {
+ InferenceInput.Params params =
+ new InferenceInput.Params.Builder(mRemoteData, "nonexist").build();
+ InferenceInput inferenceInput =
+ // Not set output structure in InferenceOutput.
+ new InferenceInput.Builder(
+ params, generateInferenceInput(1), generateInferenceOutput(1))
+ .build();
+
+ Bundle bundle = new Bundle();
+ bundle.putBinder(Constants.EXTRA_DATA_ACCESS_SERVICE_BINDER, new TestDataAccessService());
+ bundle.putParcelable(
+ Constants.EXTRA_INFERENCE_INPUT, new InferenceInputParcel(inferenceInput));
+
+ IsolatedModelServiceImpl modelService = new IsolatedModelServiceImpl();
+ var callback = new TestServiceCallback();
+ modelService.runInference(bundle, callback);
+
+ verifyCallBackError(
+ callback, OnDevicePersonalizationException.ERROR_INFERENCE_MODEL_NOT_FOUND);
}
@Test
@@ -285,6 +294,23 @@
() -> modelService.runInference(bundle, new TestServiceCallback()));
}
+ private void verifyCallBackError(TestServiceCallback callback, int errorCode) throws Exception {
+ assertTrue(
+ "Timeout when run ModelService.runInference complete",
+ callback.mLatch.await(5000, TimeUnit.SECONDS));
+ assertTrue(callback.mError);
+ assertThat(callback.mErrorCode).isEqualTo(errorCode);
+ }
+
+ private InferenceOutputParcel verifyAndGetCallbackResult(TestServiceCallback callback)
+ throws Exception {
+ assertTrue(
+ "Timeout when run ModelService.runInference complete",
+ callback.mLatch.await(5000, TimeUnit.SECONDS));
+ assertFalse(callback.mError);
+ return mCallbackResult.getParcelable(Constants.EXTRA_RESULT, InferenceOutputParcel.class);
+ }
+
private Object[] generateInferenceInput(int numExample) {
float[][] input0 = new float[numExample][100];
for (int i = 0; i < numExample; i++) {
@@ -329,6 +355,10 @@
TAG
+ " TestDataAccessService onRequest model id %s"
+ modelId.getKey());
+ if (modelId.getKey().equals("nonexist")) {
+ callback.onSuccess(Bundle.EMPTY);
+ return;
+ }
assertThat(modelId.getKey()).isEqualTo(MODEL_KEY);
assertThat(modelId.getTableId()).isEqualTo(ModelId.TABLE_ID_REMOTE_DATA);
Context context = ApplicationProvider.getApplicationContext();
@@ -351,6 +381,7 @@
}
}
}
+
@Override
public void logApiCallStats(int apiName, long latencyMillis, int responseCode) {}
}
diff --git a/tests/servicetests/src/com/android/ondevicepersonalization/services/maintenance/OnDevicePersonalizationMaintenanceJobServiceTest.java b/tests/servicetests/src/com/android/ondevicepersonalization/services/maintenance/OnDevicePersonalizationMaintenanceJobServiceTest.java
index 1a4865f..86717e6 100644
--- a/tests/servicetests/src/com/android/ondevicepersonalization/services/maintenance/OnDevicePersonalizationMaintenanceJobServiceTest.java
+++ b/tests/servicetests/src/com/android/ondevicepersonalization/services/maintenance/OnDevicePersonalizationMaintenanceJobServiceTest.java
@@ -33,7 +33,6 @@
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
import android.app.job.JobParameters;
import android.app.job.JobScheduler;
@@ -53,6 +52,7 @@
import com.android.ondevicepersonalization.services.PhFlagsTestUtil;
import com.android.ondevicepersonalization.services.data.DbUtils;
import com.android.ondevicepersonalization.services.data.OnDevicePersonalizationDbHelper;
+import com.android.ondevicepersonalization.services.data.errors.AggregatedErrorCodesLogger;
import com.android.ondevicepersonalization.services.data.events.Event;
import com.android.ondevicepersonalization.services.data.events.EventState;
import com.android.ondevicepersonalization.services.data.events.EventsDao;
@@ -64,13 +64,13 @@
import com.android.ondevicepersonalization.services.data.vendor.VendorData;
import com.android.ondevicepersonalization.services.sharedlibrary.spe.OdpJobScheduler;
+import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
-import org.mockito.Spy;
import org.mockito.quality.Strictness;
import java.io.File;
@@ -94,8 +94,29 @@
private EventsDao mEventsDao;
private OnDevicePersonalizationMaintenanceJobService mSpyService;
- @Spy
- private Flags mSpyFlags = spy(FlagsFactory.getFlags());
+ class TestFlags implements Flags {
+ boolean mGetGlobalKillSwitch = false;
+ boolean mSpePilotJobEnabled = false;
+ boolean mGetAggregatedErrorReportingEnabled = false;
+ String mIsolatedServiceAllowList = "*";
+
+ @Override public boolean getGlobalKillSwitch() {
+ return mGetGlobalKillSwitch;
+ }
+ @Override public boolean getSpePilotJobEnabled() {
+ return mSpePilotJobEnabled;
+ }
+ @Override public String getIsolatedServiceAllowList() {
+ return mIsolatedServiceAllowList;
+ }
+
+ @Override
+ public boolean getAggregatedErrorReportingEnabled() {
+ return mGetAggregatedErrorReportingEnabled;
+ }
+ }
+
+ private TestFlags mSpyFlags = new TestFlags();
@Rule
public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
@@ -104,59 +125,10 @@
.setStrictness(Strictness.LENIENT)
.build();
- private static void addTestData(long timestamp, OnDevicePersonalizationVendorDataDao dao) {
- // Add vendor data
- List<VendorData> dataList = new ArrayList<>();
- dataList.add(new VendorData.Builder().setKey("key").setData(new byte[10]).build());
- dataList.add(new VendorData.Builder().setKey("key2").setData(new byte[10]).build());
- dataList.add(new VendorData.Builder().setKey("large").setData(new byte[111111]).build());
- dataList.add(new VendorData.Builder().setKey("large2").setData(new byte[111111]).build());
- List<String> retainedKeys = new ArrayList<>();
- retainedKeys.add("key");
- retainedKeys.add("key2");
- retainedKeys.add("large");
- retainedKeys.add("large2");
- assertTrue(dao.batchUpdateOrInsertVendorDataTransaction(dataList, retainedKeys,
- timestamp));
- }
-
- private void addEventData(ComponentName service, long timestamp) {
- Query query = new Query.Builder(
- timestamp,
- "com.app",
- service,
- TEST_CERT_DIGEST,
- "query".getBytes(StandardCharsets.UTF_8))
- .build();
- long queryId = mEventsDao.insertQuery(query);
-
- Event event = new Event.Builder()
- .setType(1)
- .setEventData("event".getBytes(StandardCharsets.UTF_8))
- .setService(service)
- .setQueryId(queryId)
- .setTimeMillis(timestamp)
- .setRowIndex(0)
- .build();
- mEventsDao.insertEvent(event);
-
- EventState eventState = new EventState.Builder()
- .setTaskIdentifier(TASK_IDENTIFIER)
- .setService(service)
- .setToken(new byte[]{1})
- .build();
- mEventsDao.updateOrInsertEventState(eventState);
- }
-
@Before
public void setup() throws Exception {
PhFlagsTestUtil.setUpDeviceConfigPermissions();
ExtendedMockito.doReturn(mSpyFlags).when(FlagsFactory::getFlags);
- when(mSpyFlags.getGlobalKillSwitch()).thenReturn(false);
- when(mSpyFlags.getPersonalizationStatusOverrideValue()).thenReturn(false);
-
- // By default, disable SPE.
- when(mSpyFlags.getSpePilotJobEnabled()).thenReturn(false);
// Clean data up directories
File vendorDir = new File(mContext.getFilesDir(), "VendorData");
@@ -200,7 +172,7 @@
@Test
public void onStartJobTestKillSwitchEnabled() {
- when(mSpyFlags.getGlobalKillSwitch()).thenReturn(true);
+ mSpyFlags.mGetGlobalKillSwitch = true;
doReturn(mJobScheduler).when(mSpyService).getSystemService(JobScheduler.class);
mSpyService.schedule(mContext, /* forceSchedule= */ false);
assertTrue(
@@ -219,7 +191,7 @@
@MockStatic(OdpJobScheduler.class)
public void onStartJobSpeEnabled() {
// Enable SPE.
- when(mSpyFlags.getSpePilotJobEnabled()).thenReturn(true);
+ mSpyFlags.mSpePilotJobEnabled = true;
// Mock OdpJobScheduler to not actually schedule the job.
OdpJobScheduler mockedScheduler = mock(OdpJobScheduler.class);
doReturn(mockedScheduler).when(() -> OdpJobScheduler.getInstance(any()));
@@ -236,7 +208,28 @@
}
@Test
+ public void testVendorDataCleanup_clearAggregatedErrorData() throws Exception {
+ // when(mSpyFlags.getAggregatedErrorReportingEnabled()).thenReturn(true);
+ mSpyFlags.mGetAggregatedErrorReportingEnabled = true;
+ doReturn(MoreExecutors.newDirectExecutorService())
+ .when(OnDevicePersonalizationExecutors::getBackgroundExecutor);
+ var originalIsolatedServiceAllowList =
+ FlagsFactory.getFlags().getIsolatedServiceAllowList();
+ mSpyFlags.mIsolatedServiceAllowList = mContext.getPackageName();
+ addErrorCodeData(mService, mContext);
+
+ // Mark all the services un-enrolled
+ mSpyFlags.mIsolatedServiceAllowList = "";
+ OnDevicePersonalizationMaintenanceJobService.cleanupVendorData(mContext);
+
+ assertEquals(0, AggregatedErrorCodesLogger.getErrorDataTableCount(mContext));
+ // Reset original allow list and test cleanup successful
+ mSpyFlags.mIsolatedServiceAllowList = originalIsolatedServiceAllowList;
+ }
+
+ @Test
public void testVendorDataCleanup() throws Exception {
+ // Add data
long timestamp = System.currentTimeMillis();
addTestData(timestamp, mTestDao);
addTestData(timestamp, mDao);
@@ -244,11 +237,11 @@
addEventData(mService, 100L);
addEventData(TEST_OWNER, timestamp);
- var originalIsolatedServiceAllowList =
- FlagsFactory.getFlags().getIsolatedServiceAllowList();
- when(mSpyFlags.getIsolatedServiceAllowList()).thenReturn(mContext.getPackageName());
+ // Save original allow list and enable aggregate error reporting flag
+ var originalIsolatedServiceAllowList = mSpyFlags.getIsolatedServiceAllowList();
+ mSpyFlags.mIsolatedServiceAllowList = mContext.getPackageName();
OnDevicePersonalizationMaintenanceJobService.cleanupVendorData(mContext);
- when(mSpyFlags.getIsolatedServiceAllowList()).thenReturn(originalIsolatedServiceAllowList);
+ mSpyFlags.mIsolatedServiceAllowList = originalIsolatedServiceAllowList;
File dir = new File(OnDevicePersonalizationVendorDataDao.getFileDir(
OnDevicePersonalizationVendorDataDao.getTableName(
mService,
@@ -275,11 +268,11 @@
assertEquals(6, dir.listFiles().length);
originalIsolatedServiceAllowList =
- FlagsFactory.getFlags().getIsolatedServiceAllowList();
- when(mSpyFlags.getIsolatedServiceAllowList()).thenReturn(
- "com.android.ondevicepersonalization.servicetests");
+ mSpyFlags.getIsolatedServiceAllowList();
+ mSpyFlags.mIsolatedServiceAllowList =
+ "com.android.ondevicepersonalization.servicetests";
OnDevicePersonalizationMaintenanceJobService.cleanupVendorData(mContext);
- when(mSpyFlags.getIsolatedServiceAllowList()).thenReturn(originalIsolatedServiceAllowList);
+ mSpyFlags.mIsolatedServiceAllowList = originalIsolatedServiceAllowList;
assertEquals(2, dir.listFiles().length);
assertTrue(new File(dir, "large_" + (timestamp + 20)).exists());
assertTrue(new File(dir, "large2_" + (timestamp + 20)).exists());
@@ -333,11 +326,11 @@
assertEquals(3, localDir.listFiles().length);
var originalIsolatedServiceAllowList =
- FlagsFactory.getFlags().getIsolatedServiceAllowList();
- when(mSpyFlags.getIsolatedServiceAllowList()).thenReturn(
- "com.android.ondevicepersonalization.servicetests");
+ mSpyFlags.getIsolatedServiceAllowList();
+ mSpyFlags.mIsolatedServiceAllowList =
+ "com.android.ondevicepersonalization.servicetests";
OnDevicePersonalizationMaintenanceJobService.cleanupVendorData(mContext);
- when(mSpyFlags.getIsolatedServiceAllowList()).thenReturn(originalIsolatedServiceAllowList);
+ mSpyFlags.mIsolatedServiceAllowList = originalIsolatedServiceAllowList;
assertEquals(1, vendorDir.listFiles().length);
assertEquals(1, localDir.listFiles().length);
}
@@ -355,4 +348,58 @@
FileUtils.deleteDirectory(vendorDir);
FileUtils.deleteDirectory(localDir);
}
+
+ private void addEventData(ComponentName service, long timestamp) {
+ Query query =
+ new Query.Builder(
+ timestamp,
+ "com.app",
+ service,
+ TEST_CERT_DIGEST,
+ "query".getBytes(StandardCharsets.UTF_8))
+ .build();
+ long queryId = mEventsDao.insertQuery(query);
+
+ Event event =
+ new Event.Builder()
+ .setType(1)
+ .setEventData("event".getBytes(StandardCharsets.UTF_8))
+ .setService(service)
+ .setQueryId(queryId)
+ .setTimeMillis(timestamp)
+ .setRowIndex(0)
+ .build();
+ mEventsDao.insertEvent(event);
+
+ EventState eventState =
+ new EventState.Builder()
+ .setTaskIdentifier(TASK_IDENTIFIER)
+ .setService(service)
+ .setToken(new byte[] {1})
+ .build();
+ mEventsDao.updateOrInsertEventState(eventState);
+ }
+
+ private static void addTestData(long timestamp, OnDevicePersonalizationVendorDataDao dao) {
+ // Add vendor data
+ List<VendorData> dataList = new ArrayList<>();
+ dataList.add(new VendorData.Builder().setKey("key").setData(new byte[10]).build());
+ dataList.add(new VendorData.Builder().setKey("key2").setData(new byte[10]).build());
+ dataList.add(new VendorData.Builder().setKey("large").setData(new byte[111111]).build());
+ dataList.add(new VendorData.Builder().setKey("large2").setData(new byte[111111]).build());
+ List<String> retainedKeys = new ArrayList<>();
+ retainedKeys.add("key");
+ retainedKeys.add("key2");
+ retainedKeys.add("large");
+ retainedKeys.add("large2");
+ assertTrue(dao.batchUpdateOrInsertVendorDataTransaction(dataList, retainedKeys, timestamp));
+ }
+
+ private static void addErrorCodeData(ComponentName service, Context context) {
+ // Add a single error code entry
+ ListenableFuture<?> loggingFuture =
+ AggregatedErrorCodesLogger.logIsolatedServiceErrorCode(1, service, context);
+ assertTrue(loggingFuture.isDone());
+ assertEquals(1, AggregatedErrorCodesLogger.getErrorDataTableCount(context));
+ }
}
diff --git a/tests/servicetests/src/com/android/ondevicepersonalization/services/process/SharedIsolatedProcessRunnerTest.java b/tests/servicetests/src/com/android/ondevicepersonalization/services/process/SharedIsolatedProcessRunnerTest.java
index 7134cf1..0621f75 100644
--- a/tests/servicetests/src/com/android/ondevicepersonalization/services/process/SharedIsolatedProcessRunnerTest.java
+++ b/tests/servicetests/src/com/android/ondevicepersonalization/services/process/SharedIsolatedProcessRunnerTest.java
@@ -23,15 +23,37 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+
+import android.adservices.ondevicepersonalization.Constants;
+import android.adservices.ondevicepersonalization.IsolatedServiceException;
+import android.adservices.ondevicepersonalization.aidl.IIsolatedService;
+import android.adservices.ondevicepersonalization.aidl.IIsolatedServiceCallback;
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.Bundle;
+
+import androidx.test.core.app.ApplicationProvider;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.federatedcompute.internal.util.AbstractServiceBinder;
import com.android.modules.utils.testing.ExtendedMockitoRule;
import com.android.ondevicepersonalization.services.Flags;
import com.android.ondevicepersonalization.services.FlagsFactory;
+import com.android.ondevicepersonalization.services.OdpServiceException;
import com.android.ondevicepersonalization.services.PhFlagsTestUtil;
+import com.android.ondevicepersonalization.services.StableFlags;
+
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -39,6 +61,11 @@
import org.mockito.Mock;
import org.mockito.quality.Strictness;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
@RunWith(JUnit4.class)
public class SharedIsolatedProcessRunnerTest {
@@ -46,44 +73,204 @@
SharedIsolatedProcessRunner.getInstance();
private static final String TRUSTED_APP_NAME = "trusted_app_name";
+ private static final int CALLBACK_TIMEOUT_SECONDS = 60;
@Mock
private Flags mFlags;
+ @Mock private IsolatedServiceInfo mIsolatedServiceInfo = null;
+ @Mock private AbstractServiceBinder mAbstractServiceBinder = null;
+
+ private final Context mContext = ApplicationProvider.getApplicationContext();
+
@Rule
public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
.spyStatic(FlagsFactory.class)
+ .spyStatic(StableFlags.class)
.setStrictness(Strictness.LENIENT)
.build();
+ private final SharedIsolatedProcessRunner.Injector mTestInjector =
+ new SharedIsolatedProcessRunner.Injector();
+
+ private SharedIsolatedProcessRunner mInstanceUnderTest;
+ private final CountDownLatch mCountDownLatch = new CountDownLatch(1);
+ private final FutureCallback<Object> mTestCallback =
+ new FutureCallback<Object>() {
+ @Override
+ public void onSuccess(Object result) {
+ mCountDownLatch.countDown();
+ }
+
+ @Override
+ public void onFailure(Throwable t) {
+ mCountDownLatch.countDown();
+ }
+ };
+
@Before
public void setup() throws Exception {
PhFlagsTestUtil.setUpDeviceConfigPermissions();
ExtendedMockito.doReturn(mFlags).when(FlagsFactory::getFlags);
- doReturn(TRUSTED_APP_NAME).when(mFlags).getStableFlag(KEY_TRUSTED_PARTNER_APPS_LIST);
+ ExtendedMockito.doReturn(TRUSTED_APP_NAME).when(
+ () -> StableFlags.get(KEY_TRUSTED_PARTNER_APPS_LIST));
+
+ mInstanceUnderTest =
+ new SharedIsolatedProcessRunner(
+ ApplicationProvider.getApplicationContext(), mTestInjector);
}
@Test
public void testGetSipInstanceName_artImageLoadingOptimizationEnabled() {
- doReturn(true).when(mFlags)
- .getStableFlag(KEY_IS_ART_IMAGE_LOADING_OPTIMIZATION_ENABLED);
+ ExtendedMockito.doReturn(true).when(
+ () -> StableFlags.get(KEY_IS_ART_IMAGE_LOADING_OPTIMIZATION_ENABLED));
assertThat(sSipRunner.getSipInstanceName(TRUSTED_APP_NAME))
.isEqualTo(TRUSTED_PARTNER_APPS_SIP + "_disable_art_image_");
}
@Test
public void testGetSipInstanceName_trustedApp() {
- doReturn(false).when(mFlags)
- .getStableFlag(KEY_IS_ART_IMAGE_LOADING_OPTIMIZATION_ENABLED);
+ ExtendedMockito.doReturn(false).when(
+ () -> StableFlags.get(KEY_IS_ART_IMAGE_LOADING_OPTIMIZATION_ENABLED));
assertThat(sSipRunner.getSipInstanceName(TRUSTED_APP_NAME))
.isEqualTo(TRUSTED_PARTNER_APPS_SIP);
}
@Test
public void testGetSipInstanceName_unknownApp() {
- doReturn(false).when(mFlags)
- .getStableFlag(KEY_IS_ART_IMAGE_LOADING_OPTIMIZATION_ENABLED);
+ ExtendedMockito.doReturn(false).when(
+ () -> StableFlags.get(KEY_IS_ART_IMAGE_LOADING_OPTIMIZATION_ENABLED));
assertThat(sSipRunner.getSipInstanceName("unknown_app_name"))
.isEqualTo(UNKNOWN_APPS_SIP);
}
+
+ @Test
+ @Ignore("TODO: b/342672147 - temporary disable failing tests.")
+ public void testLoadIsolatedService_packageManagerNameNotFoundException_failedFuture()
+ throws Exception {
+ // When the package is not found during loading IsolatedService, returned future fails
+ // with appropriate OdpServiceException.
+ ListenableFuture<IsolatedServiceInfo> resultFuture =
+ mInstanceUnderTest.loadIsolatedService(
+ "AppRequestTask",
+ new ComponentName(mContext.getPackageName(), "nonExistService"));
+ Futures.addCallback(resultFuture, mTestCallback, mTestInjector.getExecutor());
+
+ mCountDownLatch.await(CALLBACK_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+ assertThat(resultFuture.isDone()).isTrue();
+ ExecutionException outException = assertThrows(ExecutionException.class, resultFuture::get);
+ assertThat(outException.getCause()).isInstanceOf(OdpServiceException.class);
+ assertThat(((OdpServiceException) outException.getCause()).getErrorCode())
+ .isEqualTo(Constants.STATUS_ISOLATED_SERVICE_LOADING_FAILED);
+ }
+
+ @Test
+ public void testRunIsolatedService_serviceBinderException_failedFutureOdpServiceException()
+ throws Exception {
+ // When the getting the IsolatedServiceBinder throws an exception the returned future fails
+ // with the loading service failed error code.
+ doThrow(new RuntimeException("Unexpected exception in binder!"))
+ .when(mIsolatedServiceInfo)
+ .getIsolatedServiceBinder();
+
+ ListenableFuture<Bundle> resultFuture =
+ mInstanceUnderTest.runIsolatedService(
+ mIsolatedServiceInfo, Constants.API_NAME_SERVICE_ON_EXECUTE, new Bundle());
+ Futures.addCallback(resultFuture, mTestCallback, mTestInjector.getExecutor());
+
+ mCountDownLatch.await(CALLBACK_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+ assertThat(resultFuture.isDone()).isTrue();
+ ExecutionException outException = assertThrows(ExecutionException.class, resultFuture::get);
+ assertThat(outException.getCause()).isInstanceOf(OdpServiceException.class);
+ assertThat(((OdpServiceException) outException.getCause()).getErrorCode())
+ .isEqualTo(Constants.STATUS_ISOLATED_SERVICE_LOADING_FAILED);
+ }
+
+ @Test
+ public void testRunIsolatedService_serviceBinderError_failedFutureOdpServiceException()
+ throws Exception {
+ // When the service binder returns an isolatedServiceError code the returned future
+ // fails with appropriate IsolatedServiceException
+ int isolatedServiceErrorCode = 6;
+ doReturn(mAbstractServiceBinder).when(mIsolatedServiceInfo).getIsolatedServiceBinder();
+ doReturn(new TestServiceBinder(Constants.STATUS_SERVICE_FAILED, isolatedServiceErrorCode))
+ .when(mAbstractServiceBinder)
+ .getService(any());
+
+ ListenableFuture<Bundle> resultFuture =
+ mInstanceUnderTest.runIsolatedService(
+ mIsolatedServiceInfo, Constants.API_NAME_SERVICE_ON_EXECUTE, new Bundle());
+ Futures.addCallback(resultFuture, mTestCallback, mTestInjector.getExecutor());
+
+ mCountDownLatch.await(CALLBACK_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+ assertThat(resultFuture.isDone()).isTrue();
+ ExecutionException outException = assertThrows(ExecutionException.class, resultFuture::get);
+ assertThat(outException.getCause()).isInstanceOf(OdpServiceException.class);
+ OdpServiceException odpServiceException = (OdpServiceException) outException.getCause();
+ assertThat(odpServiceException.getErrorCode()).isEqualTo(Constants.STATUS_SERVICE_FAILED);
+ assertThat(odpServiceException.getCause()).isInstanceOf(IsolatedServiceException.class);
+ assertThat(((IsolatedServiceException) odpServiceException.getCause()).getErrorCode())
+ .isEqualTo(isolatedServiceErrorCode);
+ }
+
+ @Test
+ public void testRunIsolatedService_serviceBinderTimeout_failedFutureTimeoutException()
+ throws Exception {
+ // When the service binder times out without responding the future fails with a timeout
+ // exception.
+ doReturn(mAbstractServiceBinder).when(mIsolatedServiceInfo).getIsolatedServiceBinder();
+ doReturn(new FakeTimeoutServiceBinder()).when(mAbstractServiceBinder).getService(any());
+
+ ListenableFuture<Bundle> resultFuture =
+ mInstanceUnderTest.runIsolatedService(
+ mIsolatedServiceInfo, Constants.API_NAME_SERVICE_ON_EXECUTE, new Bundle());
+ Futures.addCallback(resultFuture, mTestCallback, mTestInjector.getExecutor());
+ // For a GC to cause the callbackToFutureAdapter to throw FutureGarbageCollectedException
+ forceGc();
+
+ mCountDownLatch.await(CALLBACK_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+ assertThat(resultFuture.isDone()).isTrue();
+ ExecutionException outException = assertThrows(ExecutionException.class, resultFuture::get);
+ assertThat(outException.getCause()).isInstanceOf(TimeoutException.class);
+ }
+
+ private static void forceGc() {
+ System.gc();
+ System.runFinalization();
+ System.gc();
+ }
+
+ private static final class TestServiceBinder extends IIsolatedService.Stub {
+ private final int mErrorCode;
+ private final int mIsolatedServiceErrorCode;
+
+ private TestServiceBinder(int errorCode, int isolatedServiceErrorCode) {
+ mErrorCode = errorCode;
+ mIsolatedServiceErrorCode = isolatedServiceErrorCode;
+ }
+
+ @Override
+ public void onRequest(
+ int operationCode,
+ @NonNull Bundle params,
+ @NonNull IIsolatedServiceCallback resultCallback) {
+ try {
+ resultCallback.onError(mErrorCode, mIsolatedServiceErrorCode, null);
+ } catch (Exception e) {
+
+ }
+ }
+ }
+
+ private static final class FakeTimeoutServiceBinder extends IIsolatedService.Stub {
+ private FakeTimeoutServiceBinder() {}
+
+ @Override
+ public void onRequest(
+ int operationCode,
+ @NonNull Bundle params,
+ @NonNull IIsolatedServiceCallback resultCallback) {
+ // Does nothing no-op
+ }
+ }
}
diff --git a/tests/servicetests/src/com/android/ondevicepersonalization/services/serviceflow/AppRequestFlowTest.java b/tests/servicetests/src/com/android/ondevicepersonalization/services/serviceflow/AppRequestFlowTest.java
index aa6b7ba..20463f2 100644
--- a/tests/servicetests/src/com/android/ondevicepersonalization/services/serviceflow/AppRequestFlowTest.java
+++ b/tests/servicetests/src/com/android/ondevicepersonalization/services/serviceflow/AppRequestFlowTest.java
@@ -16,18 +16,23 @@
package com.android.ondevicepersonalization.services.serviceflow;
+import static com.android.ondevicepersonalization.services.PhFlags.KEY_SHARED_ISOLATED_PROCESS_FEATURE_ENABLED;
-import static org.junit.Assert.assertArrayEquals;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import android.adservices.ondevicepersonalization.CalleeMetadata;
import android.adservices.ondevicepersonalization.Constants;
+import android.adservices.ondevicepersonalization.ExecuteInIsolatedServiceRequest;
+import android.adservices.ondevicepersonalization.ExecuteOptionsParcel;
import android.adservices.ondevicepersonalization.aidl.IExecuteCallback;
import android.content.ComponentName;
import android.content.ContentValues;
@@ -39,13 +44,14 @@
import com.android.compatibility.common.util.ShellUtils;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.modules.utils.build.SdkLevel;
import com.android.modules.utils.testing.ExtendedMockitoRule;
import com.android.odp.module.common.PackageUtils;
import com.android.ondevicepersonalization.internal.util.ByteArrayParceledSlice;
+import com.android.ondevicepersonalization.internal.util.LoggerFactory;
import com.android.ondevicepersonalization.internal.util.PersistableBundleUtils;
import com.android.ondevicepersonalization.services.Flags;
-import com.android.ondevicepersonalization.services.FlagsFactory;
-import com.android.ondevicepersonalization.services.PhFlagsTestUtil;
+import com.android.ondevicepersonalization.services.StableFlags;
import com.android.ondevicepersonalization.services.data.DbUtils;
import com.android.ondevicepersonalization.services.data.OnDevicePersonalizationDbHelper;
import com.android.ondevicepersonalization.services.data.events.EventsContract;
@@ -55,61 +61,106 @@
import com.android.ondevicepersonalization.services.data.user.UserPrivacyStatus;
import com.android.ondevicepersonalization.services.data.vendor.OnDevicePersonalizationVendorDataDao;
import com.android.ondevicepersonalization.services.data.vendor.VendorData;
+import com.android.ondevicepersonalization.services.request.AppRequestFlow;
+import com.android.ondevicepersonalization.services.util.NoiseUtil;
import com.android.ondevicepersonalization.services.util.OnDevicePersonalizationFlatbufferUtils;
+import com.test.TestPersonalizationHandler;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
+import org.junit.runners.Parameterized;
import org.mockito.Mock;
-import org.mockito.Spy;
import org.mockito.quality.Strictness;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
import java.util.List;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
-@RunWith(JUnit4.class)
+@RunWith(Parameterized.class)
public class AppRequestFlowTest {
+ private static final LoggerFactory.Logger sLogger = LoggerFactory.getLogger();
+ private static final String TAG = AppRequestFlowTest.class.getSimpleName();
+ private static final String TEST_SERVICE_CLASS = "com.test.TestPersonalizationService";
+ private static final int TEST_TIMEOUT_SECONDS = 10;
private final Context mContext = spy(ApplicationProvider.getApplicationContext());
+ private final ComponentName mTestServiceComponentName =
+ new ComponentName(mContext.getPackageName(), TEST_SERVICE_CLASS);
private final CountDownLatch mLatch = new CountDownLatch(1);
private final OnDevicePersonalizationDbHelper mDbHelper =
OnDevicePersonalizationDbHelper.getInstanceForTest(mContext);
- private boolean mCallbackSuccess;
- private boolean mCallbackError;
- private int mCallbackErrorCode;
+ private volatile boolean mCallbackSuccess;
+ private volatile boolean mCallbackError;
+ private volatile int mCallbackErrorCode;
private int mIsolatedServiceErrorCode;
- private String mErrorMessage;
- private Bundle mExecuteCallback;
+ private byte[] mSerializedException;
+ private volatile Bundle mExecuteCallback;
private ServiceFlowOrchestrator mSfo;
@Mock
UserPrivacyStatus mUserPrivacyStatus;
+ @Mock private NoiseUtil mMockNoiseUtil;
+ @Parameterized.Parameter(0)
+ public boolean mIsSipFeatureEnabled;
- @Spy
- private Flags mSpyFlags = spy(FlagsFactory.getFlags());
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(
+ new Object[][] {
+ {true}, {false}
+ }
+ );
+ }
+
+ class TestFlags implements Flags {
+ int mIsolatedServiceDeadlineSeconds = 30;
+ String mOutputDataAllowList = "*;*";
+ String mPlatformDataAllowList = "";
+
+ @Override public boolean getGlobalKillSwitch() {
+ return false;
+ }
+ @Override public int getIsolatedServiceDeadlineSeconds() {
+ return mIsolatedServiceDeadlineSeconds;
+ }
+ @Override public String getOutputDataAllowList() {
+ return mOutputDataAllowList;
+ }
+
+ @Override
+ public String getDefaultPlatformDataForExecuteAllowlist() {
+ return mPlatformDataAllowList;
+ }
+ }
+
+ private TestFlags mSpyFlags = new TestFlags();
@Rule
- public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
- .mockStatic(FlagsFactory.class)
- .spyStatic(UserPrivacyStatus.class)
- .setStrictness(Strictness.LENIENT)
- .build();
+ public final ExtendedMockitoRule mExtendedMockitoRule =
+ new ExtendedMockitoRule.Builder(this)
+ .spyStatic(StableFlags.class)
+ .spyStatic(UserPrivacyStatus.class)
+ .setStrictness(Strictness.LENIENT)
+ .build();
@Before
public void setup() throws Exception {
- PhFlagsTestUtil.setUpDeviceConfigPermissions();
- ExtendedMockito.doReturn(mSpyFlags).when(FlagsFactory::getFlags);
- when(mSpyFlags.getGlobalKillSwitch()).thenReturn(false);
ShellUtils.runShellCommand("settings put global hidden_api_policy 1");
ExtendedMockito.doReturn(mUserPrivacyStatus).when(UserPrivacyStatus::getInstance);
+ ExtendedMockito.doReturn(SdkLevel.isAtLeastU() && mIsSipFeatureEnabled).when(
+ () -> StableFlags.get(KEY_SHARED_ISOLATED_PROCESS_FEATURE_ENABLED));
doReturn(true).when(mUserPrivacyStatus).isMeasurementEnabled();
doReturn(true).when(mUserPrivacyStatus).isProtectedAudienceEnabled();
+ when(mMockNoiseUtil.applyNoiseToBestValue(anyInt(), anyInt(), any())).thenReturn(3);
setUpTestData();
@@ -124,15 +175,65 @@
}
@Test
+ public void testAppRequestFlow_InvalidService_ErrorManifestMisconfigured()
+ throws InterruptedException {
+ mSfo.scheduleForTest(
+ ServiceFlowType.APP_REQUEST_FLOW,
+ mContext.getPackageName(),
+ new ComponentName(mContext.getPackageName(), "com.test.BadService"),
+ createWrappedAppParams(),
+ new TestExecuteCallback(),
+ mContext,
+ 100L,
+ 110L,
+ ExecuteOptionsParcel.DEFAULT,
+ new AppTestInjector());
+
+ mLatch.await();
+
+ assertTrue(mCallbackError);
+ assertEquals(Constants.STATUS_MANIFEST_MISCONFIGURED, mCallbackErrorCode);
+ }
+
+ @Test
+ public void testAppRequestFlow_InvalidPackage_ErrorParsingFailed() throws InterruptedException {
+ mSfo.scheduleForTest(
+ ServiceFlowType.APP_REQUEST_FLOW,
+ mContext.getPackageName(),
+ new ComponentName("badPackageName", TEST_SERVICE_CLASS),
+ createWrappedAppParams(),
+ new TestExecuteCallback(),
+ mContext,
+ 100L,
+ 110L,
+ ExecuteOptionsParcel.DEFAULT,
+ new AppTestInjector());
+
+ mLatch.await();
+
+ assertTrue(mCallbackError);
+ assertEquals(Constants.STATUS_MANIFEST_PARSING_FAILED, mCallbackErrorCode);
+ }
+
+ @Test
public void testAppRequestFlow_MeasurementControlRevoked() throws InterruptedException {
int originalQueriesCount = getDbTableSize(QueriesContract.QueriesEntry.TABLE_NAME);
int originalEventsCount = getDbTableSize(EventsContract.EventsEntry.TABLE_NAME);
doReturn(false).when(mUserPrivacyStatus).isMeasurementEnabled();
- mSfo.schedule(ServiceFlowType.APP_REQUEST_FLOW, mContext.getPackageName(),
- new ComponentName(mContext.getPackageName(), "com.test.TestPersonalizationService"),
- createWrappedAppParams(), new TestExecuteCallback(), mContext, 100L, 110L);
+ mSfo.scheduleForTest(
+ ServiceFlowType.APP_REQUEST_FLOW,
+ mContext.getPackageName(),
+ mTestServiceComponentName,
+ createWrappedAppParams(),
+ new TestExecuteCallback(),
+ mContext,
+ 100L,
+ 110L,
+ ExecuteOptionsParcel.DEFAULT,
+ new AppTestInjector());
mLatch.await();
+
assertTrue(mCallbackSuccess);
assertFalse(mExecuteCallback.isEmpty());
// make sure no request or event records are written to the database
@@ -144,48 +245,169 @@
public void testAppRequestFlow_TargetingControlRevoked() throws InterruptedException {
doReturn(false).when(mUserPrivacyStatus).isProtectedAudienceEnabled();
- mSfo.schedule(ServiceFlowType.APP_REQUEST_FLOW, mContext.getPackageName(),
- new ComponentName(mContext.getPackageName(), "com.test.TestPersonalizationService"),
- createWrappedAppParams(), new TestExecuteCallback(), mContext, 100L, 110L);
+ mSfo.scheduleForTest(
+ ServiceFlowType.APP_REQUEST_FLOW,
+ mContext.getPackageName(),
+ mTestServiceComponentName,
+ createWrappedAppParams(),
+ new TestExecuteCallback(),
+ mContext,
+ 100L,
+ 110L,
+ ExecuteOptionsParcel.DEFAULT,
+ new AppTestInjector());
mLatch.await();
+
assertTrue(mCallbackSuccess);
assertTrue(mExecuteCallback.isEmpty());
}
@Test
- public void testAppRequestFlow_OutputDataBlocked() throws InterruptedException {
- when(mSpyFlags.getOutputDataAllowList()).thenReturn("");
+ public void testAppRequestFlow_notInOutputDataAllowlist_blocked() throws InterruptedException {
+ mSpyFlags.mOutputDataAllowList = "";
+ ExecuteOptionsParcel options =
+ new ExecuteOptionsParcel(
+ ExecuteInIsolatedServiceRequest.OutputSpec.OUTPUT_TYPE_BEST_VALUE, 10);
- mSfo.schedule(ServiceFlowType.APP_REQUEST_FLOW, mContext.getPackageName(),
- new ComponentName(mContext.getPackageName(), "com.test.TestPersonalizationService"),
- createWrappedAppParams(), new TestExecuteCallback(), mContext, 100L, 110L);
+ mSfo.scheduleForTest(
+ ServiceFlowType.APP_REQUEST_FLOW,
+ mContext.getPackageName(),
+ mTestServiceComponentName,
+ createWrappedAppParams(),
+ new TestExecuteCallback(),
+ mContext,
+ 100L,
+ 110L,
+ options,
+ new AppTestInjector());
mLatch.await();
assertTrue(mCallbackSuccess);
- assertNull(mExecuteCallback.getByteArray(Constants.EXTRA_OUTPUT_DATA));
+ assertThat(mExecuteCallback.getInt(Constants.EXTRA_OUTPUT_BEST_VALUE)).isEqualTo(-1);
assertEquals(2, getDbTableSize(QueriesContract.QueriesEntry.TABLE_NAME));
assertEquals(1, getDbTableSize(EventsContract.EventsEntry.TABLE_NAME));
}
@Test
- public void testAppRequestFlow_OutputDataAllowed() throws InterruptedException {
+ public void testAppRequestFlow_notInPlatformDataAllowlist_blocked()
+ throws InterruptedException {
String contextPackageName = mContext.getPackageName();
- when(mSpyFlags.getOutputDataAllowList()).thenReturn(
- contextPackageName + ";" + contextPackageName);
+ mSpyFlags.mOutputDataAllowList = contextPackageName + ";" + contextPackageName;
+ mSpyFlags.mPlatformDataAllowList = "";
+ ExecuteOptionsParcel options =
+ new ExecuteOptionsParcel(
+ ExecuteInIsolatedServiceRequest.OutputSpec.OUTPUT_TYPE_BEST_VALUE, 10);
- mSfo.schedule(ServiceFlowType.APP_REQUEST_FLOW, mContext.getPackageName(),
- new ComponentName(mContext.getPackageName(), "com.test.TestPersonalizationService"),
- createWrappedAppParams(), new TestExecuteCallback(), mContext, 100L, 110L);
+ mSfo.scheduleForTest(
+ ServiceFlowType.APP_REQUEST_FLOW,
+ mContext.getPackageName(),
+ mTestServiceComponentName,
+ createWrappedAppParams(),
+ new TestExecuteCallback(),
+ mContext,
+ 100L,
+ 110L,
+ options,
+ new AppTestInjector());
mLatch.await();
assertTrue(mCallbackSuccess);
- assertArrayEquals(
- mExecuteCallback.getByteArray(Constants.EXTRA_OUTPUT_DATA),
- new byte[] {1, 2, 3});
+ assertThat(mExecuteCallback.getInt(Constants.EXTRA_OUTPUT_BEST_VALUE)).isEqualTo(-1);
assertEquals(2, getDbTableSize(QueriesContract.QueriesEntry.TABLE_NAME));
assertEquals(1, getDbTableSize(EventsContract.EventsEntry.TABLE_NAME));
}
+ @Test
+ public void testAppRequestFlow_getBestValue() throws Exception {
+ String contextPackageName = mContext.getPackageName();
+ mSpyFlags.mOutputDataAllowList =
+ contextPackageName + ";" + contextPackageName;
+ mSpyFlags.mPlatformDataAllowList =
+ contextPackageName
+ + ":"
+ + PackageUtils.getCertDigest(mContext, mContext.getPackageName());
+ ExecuteOptionsParcel options =
+ new ExecuteOptionsParcel(
+ ExecuteInIsolatedServiceRequest.OutputSpec.OUTPUT_TYPE_BEST_VALUE, 10);
+
+ mSfo.scheduleForTest(
+ ServiceFlowType.APP_REQUEST_FLOW,
+ mContext.getPackageName(),
+ mTestServiceComponentName,
+ createWrappedAppParams(),
+ new TestExecuteCallback(),
+ mContext,
+ 100L,
+ 110L,
+ options,
+ new AppTestInjector());
+ mLatch.await();
+
+ assertTrue(mCallbackSuccess);
+ assertThat(mExecuteCallback.getInt(Constants.EXTRA_OUTPUT_BEST_VALUE)).isEqualTo(3);
+ assertEquals(2, getDbTableSize(QueriesContract.QueriesEntry.TABLE_NAME));
+ assertEquals(1, getDbTableSize(EventsContract.EventsEntry.TABLE_NAME));
+ }
+
+ @Test
+ public void testAppRequestFlow_getServiceFlowFuture_timeoutExceptionReturned()
+ throws InterruptedException {
+ // When the request fails due to the test service timing out, the callback should fail
+ // with the service timeout error code.
+ String contextPackageName = mContext.getPackageName();
+ mSpyFlags.mOutputDataAllowList =
+ contextPackageName + ";" + contextPackageName;
+ mSpyFlags.mIsolatedServiceDeadlineSeconds = TEST_TIMEOUT_SECONDS;
+
+ mSfo.scheduleForTest(
+ ServiceFlowType.APP_REQUEST_FLOW,
+ mContext.getPackageName(),
+ mTestServiceComponentName,
+ createWrappedAppParams(/* timeout= */ true),
+ new TestExecuteCallback(),
+ mContext,
+ 100L,
+ 110L,
+ ExecuteOptionsParcel.DEFAULT,
+ new AppTestInjector());
+ boolean countedDown = mLatch.await(3 * TEST_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+
+ assertTrue(countedDown);
+ assertFalse(mCallbackSuccess);
+ assertTrue(mCallbackError);
+ assertEquals(Constants.STATUS_ISOLATED_SERVICE_TIMEOUT, mCallbackErrorCode);
+ }
+
+ @Test
+ public void testAppRequestFlow_getServiceFlowFuture_outputValidationExceptionReturned()
+ throws Exception {
+ // When the request fails due to output validation check failing, the callback should fail
+ // with the service failed error code. Clear vendor data to cause output
+ // validation check to fail.
+ String contextPackageName = mContext.getPackageName();
+ mSpyFlags.mOutputDataAllowList =
+ contextPackageName + ";" + contextPackageName;
+ clearVendorDataDao();
+
+ mSfo.scheduleForTest(
+ ServiceFlowType.APP_REQUEST_FLOW,
+ mContext.getPackageName(),
+ mTestServiceComponentName,
+ createWrappedAppParams(),
+ new TestExecuteCallback(),
+ mContext,
+ 100L,
+ 110L,
+ ExecuteOptionsParcel.DEFAULT,
+ new AppTestInjector());
+ boolean countedDown = mLatch.await(TEST_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+
+ assertTrue(countedDown);
+ assertFalse(mCallbackSuccess);
+ assertTrue(mCallbackError);
+ assertEquals(Constants.STATUS_SERVICE_FAILED, mCallbackErrorCode);
+ }
+
private int getDbTableSize(String tableName) {
return mDbHelper.getReadableDatabase().query(tableName, null,
null, null, null, null, null).getCount();
@@ -199,8 +421,7 @@
ContentValues row2 = new ContentValues();
row2.put("b", 2);
rows.add(row2);
- ComponentName service = new ComponentName(
- mContext.getPackageName(), "com.test.TestPersonalizationService");
+ ComponentName service = new ComponentName(mContext.getPackageName(), TEST_SERVICE_CLASS);
byte[] queryDataBytes = OnDevicePersonalizationFlatbufferUtils.createQueryData(
DbUtils.toTableValue(service), "AABBCCDD", rows);
EventsDao.getInstanceForTest(mContext).insertQuery(
@@ -213,10 +434,10 @@
.build());
EventsDao.getInstanceForTest(mContext);
- OnDevicePersonalizationVendorDataDao testVendorDao = OnDevicePersonalizationVendorDataDao
- .getInstanceForTest(mContext,
- new ComponentName(mContext.getPackageName(),
- "com.test.TestPersonalizationService"),
+ OnDevicePersonalizationVendorDataDao testVendorDao =
+ OnDevicePersonalizationVendorDataDao.getInstanceForTest(
+ mContext,
+ new ComponentName(mContext.getPackageName(), TEST_SERVICE_CLASS),
PackageUtils.getCertDigest(mContext, mContext.getPackageName()));
VendorData vendorData = new VendorData.Builder().setData(new byte[5]).setKey(
"bid1").build();
@@ -227,11 +448,36 @@
);
}
- private Bundle createWrappedAppParams() {
+ private void clearVendorDataDao() throws Exception {
+ OnDevicePersonalizationVendorDataDao testVendorDao =
+ OnDevicePersonalizationVendorDataDao.getInstanceForTest(
+ mContext,
+ new ComponentName(mContext.getPackageName(), TEST_SERVICE_CLASS),
+ PackageUtils.getCertDigest(mContext, mContext.getPackageName()));
+ testVendorDao.deleteVendorData(
+ mContext,
+ new ComponentName(mContext.getPackageName(), TEST_SERVICE_CLASS),
+ PackageUtils.getCertDigest(mContext, mContext.getPackageName()));
+ }
+
+ private static Bundle createWrappedAppParams() {
+ return createWrappedAppParams(/* timeout= */ false);
+ }
+
+ /**
+ * Creates Bundle with app params for the test, including optional boolean for mimicking timeout
+ * in the {@code TestPersonalizationService}.
+ */
+ private static Bundle createWrappedAppParams(boolean timeout) {
try {
Bundle wrappedParams = new Bundle();
- ByteArrayParceledSlice buffer = new ByteArrayParceledSlice(
- PersistableBundleUtils.toByteArray(PersistableBundle.EMPTY));
+ PersistableBundle handlerBundle = PersistableBundle.EMPTY;
+ if (timeout) {
+ handlerBundle = new PersistableBundle();
+ handlerBundle.putBoolean(TestPersonalizationHandler.TIMEOUT_KEY, timeout);
+ }
+ ByteArrayParceledSlice buffer =
+ new ByteArrayParceledSlice(PersistableBundleUtils.toByteArray(handlerBundle));
wrappedParams.putParcelable(Constants.EXTRA_APP_PARAMS_SERIALIZED, buffer);
return wrappedParams;
} catch (Exception e) {
@@ -239,21 +485,41 @@
}
}
+ class AppTestInjector extends AppRequestFlow.Injector {
+ @Override
+ public Flags getFlags() {
+ return mSpyFlags;
+ }
+
+ @Override
+ public boolean shouldValidateExecuteOutput() {
+ return true;
+ }
+
+ @Override
+ public NoiseUtil getNoiseUtil() {
+ return mMockNoiseUtil;
+ }
+ }
+
class TestExecuteCallback extends IExecuteCallback.Stub {
@Override
public void onSuccess(Bundle bundle, CalleeMetadata calleeMetadata) {
+ sLogger.d(TAG + " : onSuccess callback.");
mCallbackSuccess = true;
mExecuteCallback = bundle;
mLatch.countDown();
}
@Override
- public void onError(int errorCode, int isolatedServiceErrorCode, String message,
+ public void onError(int errorCode, int isolatedServiceErrorCode,
+ byte[] serializedException,
CalleeMetadata calleeMetadata) {
+ sLogger.d(TAG + " : onError callback.");
mCallbackError = true;
mCallbackErrorCode = errorCode;
mIsolatedServiceErrorCode = isolatedServiceErrorCode;
- mErrorMessage = message;
+ mSerializedException = serializedException;
mLatch.countDown();
}
}
diff --git a/tests/servicetests/src/com/android/ondevicepersonalization/services/serviceflow/RenderFlowTest.java b/tests/servicetests/src/com/android/ondevicepersonalization/services/serviceflow/RenderFlowTest.java
index 02ed35f..69bb408 100644
--- a/tests/servicetests/src/com/android/ondevicepersonalization/services/serviceflow/RenderFlowTest.java
+++ b/tests/servicetests/src/com/android/ondevicepersonalization/services/serviceflow/RenderFlowTest.java
@@ -16,11 +16,11 @@
package com.android.ondevicepersonalization.services.serviceflow;
+import static com.android.ondevicepersonalization.services.PhFlags.KEY_SHARED_ISOLATED_PROCESS_FEATURE_ENABLED;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.when;
import android.adservices.ondevicepersonalization.CalleeMetadata;
import android.adservices.ondevicepersonalization.Constants;
@@ -38,10 +38,12 @@
import com.android.compatibility.common.util.ShellUtils;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.modules.utils.build.SdkLevel;
import com.android.modules.utils.testing.ExtendedMockitoRule;
import com.android.ondevicepersonalization.services.Flags;
import com.android.ondevicepersonalization.services.FlagsFactory;
import com.android.ondevicepersonalization.services.PhFlagsTestUtil;
+import com.android.ondevicepersonalization.services.StableFlags;
import com.android.ondevicepersonalization.services.data.DbUtils;
import com.android.ondevicepersonalization.services.data.OnDevicePersonalizationDbHelper;
import com.android.ondevicepersonalization.services.data.events.EventsDao;
@@ -58,7 +60,6 @@
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.mockito.Mock;
-import org.mockito.Spy;
import org.mockito.quality.Strictness;
import java.util.ArrayList;
@@ -79,7 +80,7 @@
private boolean mCallbackError;
private int mCallbackErrorCode;
private int mIsolatedServiceErrorCode;
- private String mErrorMessage;
+ private byte[] mSerializedException;
private Bundle mCallbackResult;
private ServiceFlowOrchestrator mSfo;
@@ -98,12 +99,17 @@
);
}
- @Spy
- private Flags mSpyFlags = spy(FlagsFactory.getFlags());
+ private Flags mSpyFlags = new Flags() {
+ int mIsolatedServiceDeadlineSeconds = 30;
+ @Override public int getIsolatedServiceDeadlineSeconds() {
+ return mIsolatedServiceDeadlineSeconds;
+ }
+ };
@Rule
public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
.mockStatic(FlagsFactory.class)
+ .spyStatic(StableFlags.class)
.spyStatic(UserPrivacyStatus.class)
.spyStatic(CryptUtils.class)
.setStrictness(Strictness.LENIENT)
@@ -114,7 +120,8 @@
PhFlagsTestUtil.setUpDeviceConfigPermissions();
ShellUtils.runShellCommand("settings put global hidden_api_policy 1");
ExtendedMockito.doReturn(mSpyFlags).when(FlagsFactory::getFlags);
- when(mSpyFlags.isSharedIsolatedProcessFeatureEnabled()).thenReturn(mIsSipFeatureEnabled);
+ ExtendedMockito.doReturn(SdkLevel.isAtLeastU() && mIsSipFeatureEnabled).when(
+ () -> StableFlags.get(KEY_SHARED_ISOLATED_PROCESS_FEATURE_ENABLED));
setUpTestDate();
@@ -198,12 +205,13 @@
mLatch.countDown();
}
@Override public void onError(
- int errorCode, int isolatedServiceErrorCode, String message,
+ int errorCode, int isolatedServiceErrorCode,
+ byte[] serializedException,
CalleeMetadata calleeMetadata) {
mCallbackError = true;
mCallbackErrorCode = errorCode;
mIsolatedServiceErrorCode = isolatedServiceErrorCode;
- mErrorMessage = message;
+ mSerializedException = serializedException;
mLatch.countDown();
}
}
diff --git a/tests/servicetests/src/com/android/ondevicepersonalization/services/serviceflow/ServiceFlowFactoryTest.java b/tests/servicetests/src/com/android/ondevicepersonalization/services/serviceflow/ServiceFlowFactoryTest.java
index b6597dc..b8178ae 100644
--- a/tests/servicetests/src/com/android/ondevicepersonalization/services/serviceflow/ServiceFlowFactoryTest.java
+++ b/tests/servicetests/src/com/android/ondevicepersonalization/services/serviceflow/ServiceFlowFactoryTest.java
@@ -20,6 +20,7 @@
import android.adservices.ondevicepersonalization.CalleeMetadata;
import android.adservices.ondevicepersonalization.Constants;
+import android.adservices.ondevicepersonalization.ExecuteOptionsParcel;
import android.adservices.ondevicepersonalization.MeasurementWebTriggerEventParamsParcel;
import android.adservices.ondevicepersonalization.aidl.IExecuteCallback;
import android.adservices.ondevicepersonalization.aidl.IRegisterMeasurementEventCallback;
@@ -55,10 +56,17 @@
@Test
public void testCreateAppRequestFlowInstance() throws Exception {
- ServiceFlow serviceFlow = ServiceFlowFactory.createInstance(
- ServiceFlowType.APP_REQUEST_FLOW, "testCallingPackage",
- new ComponentName("testPackage", "testClass"), new Bundle(),
- new TestExecuteCallback(), mContext, 0L, 100L);
+ ServiceFlow serviceFlow =
+ ServiceFlowFactory.createInstance(
+ ServiceFlowType.APP_REQUEST_FLOW,
+ "testCallingPackage",
+ new ComponentName("testPackage", "testClass"),
+ new Bundle(),
+ new TestExecuteCallback(),
+ mContext,
+ 0L,
+ 100L,
+ ExecuteOptionsParcel.DEFAULT);
assertThat(serviceFlow).isNotNull();
assertThat(serviceFlow).isInstanceOf(AppRequestFlow.class);
@@ -66,9 +74,19 @@
@Test
public void testCreateRenderFlowInstance() throws Exception {
- ServiceFlow serviceFlow = ServiceFlowFactory.createInstance(
- ServiceFlowType.RENDER_FLOW, "testToken", new Binder(), 0,
- 100, 50, new TestRenderFlowCallback(), mContext, 0L, 100L);
+ ServiceFlow serviceFlow =
+ ServiceFlowFactory.createInstance(
+ ServiceFlowType.RENDER_FLOW,
+ "testToken",
+ new Binder(),
+ 0,
+ 100,
+ 50,
+ new TestRenderFlowCallback(),
+ mContext,
+ 0L,
+ 100L,
+ ExecuteOptionsParcel.DEFAULT);
assertThat(serviceFlow).isNotNull();
assertThat(serviceFlow).isInstanceOf(RenderFlow.class);
@@ -76,9 +94,18 @@
@Test(expected = ClassCastException.class)
public void testCreateAppRequestFlowInstance_IllegalInputClass() throws Exception {
- ServiceFlow serviceFlow = ServiceFlowFactory.createInstance(
- ServiceFlowType.APP_REQUEST_FLOW, "testToken", new Binder(), 0,
- 100, 50, new TestRenderFlowCallback(), mContext, 0L);
+ ServiceFlow serviceFlow =
+ ServiceFlowFactory.createInstance(
+ ServiceFlowType.APP_REQUEST_FLOW,
+ "testToken",
+ new Binder(),
+ 0,
+ 100,
+ 50,
+ new TestRenderFlowCallback(),
+ mContext,
+ 0L,
+ ExecuteOptionsParcel.DEFAULT);
}
@Test(expected = ArrayIndexOutOfBoundsException.class)
@@ -89,9 +116,15 @@
@Test
public void testCreateWebTriggerFlowInstance() throws Exception {
- ServiceFlow serviceFlow = ServiceFlowFactory.createInstance(
- ServiceFlowType.WEB_TRIGGER_FLOW, getWebTriggerParams(), mContext,
- new TestWebCallback(), 0L, 100L);
+ ServiceFlow serviceFlow =
+ ServiceFlowFactory.createInstance(
+ ServiceFlowType.WEB_TRIGGER_FLOW,
+ getWebTriggerParams(),
+ mContext,
+ new TestWebCallback(),
+ 0L,
+ 100L,
+ ExecuteOptionsParcel.DEFAULT);
assertThat(serviceFlow).isNotNull();
assertThat(serviceFlow).isInstanceOf(WebTriggerFlow.class);
@@ -101,15 +134,15 @@
@Override
public void onSuccess(Bundle bundle, CalleeMetadata calleeMetadata) {}
@Override
- public void onError(int errorCode, int isolatedServiceErrorCode, String message,
- CalleeMetadata calleeMetadata) {}
+ public void onError(int errorCode, int isolatedServiceErrorCode,
+ byte[] serializedException, CalleeMetadata calleeMetadata) {}
}
class TestRenderFlowCallback extends IRequestSurfacePackageCallback.Stub {
@Override public void onSuccess(SurfaceControlViewHost.SurfacePackage surfacePackage,
CalleeMetadata calleeMetadata) {}
- @Override public void onError(int errorCode, int isolatedServiceErrorCode, String message,
- CalleeMetadata calleeMetadata) {}
+ @Override public void onError(int errorCode, int isolatedServiceErrorCode,
+ byte[] serializedException, CalleeMetadata calleeMetadata) {}
}
class TestWebCallback extends IRegisterMeasurementEventCallback.Stub {
diff --git a/tests/servicetests/src/com/android/ondevicepersonalization/services/serviceflow/ServiceFlowTypeTest.java b/tests/servicetests/src/com/android/ondevicepersonalization/services/serviceflow/ServiceFlowTypeTest.java
index 4541f6d..b5e9001 100644
--- a/tests/servicetests/src/com/android/ondevicepersonalization/services/serviceflow/ServiceFlowTypeTest.java
+++ b/tests/servicetests/src/com/android/ondevicepersonalization/services/serviceflow/ServiceFlowTypeTest.java
@@ -25,7 +25,6 @@
import static com.google.common.truth.Truth.assertThat;
-import com.android.ondevicepersonalization.services.FlagsFactory;
import com.android.ondevicepersonalization.services.PhFlagsTestUtil;
import org.junit.Before;
@@ -52,7 +51,7 @@
assertThat(ServiceFlowType.RENDER_FLOW.getTaskName()).isEqualTo("Render");
assertThat(ServiceFlowType.WEB_TRIGGER_FLOW.getTaskName()).isEqualTo("WebTrigger");
assertThat(ServiceFlowType.WEB_VIEW_FLOW.getTaskName())
- .isEqualTo("ComputeEventMetrics");
+ .isEqualTo("WebView");
assertThat(ServiceFlowType.EXAMPLE_STORE_FLOW.getTaskName())
.isEqualTo("ExampleStore");
assertThat(ServiceFlowType.DOWNLOAD_FLOW.getTaskName())
@@ -86,18 +85,4 @@
assertThat(ServiceFlowType.DOWNLOAD_FLOW.getPriority())
.isEqualTo(ServiceFlowType.Priority.LOW);
}
-
- @Test
- public void executionTimeoutTest() {
- assertThat(ServiceFlowType.APP_REQUEST_FLOW.getExecutionTimeout())
- .isEqualTo(FlagsFactory.getFlags().getAppRequestFlowDeadlineSeconds());
- assertThat(ServiceFlowType.RENDER_FLOW.getExecutionTimeout())
- .isEqualTo(FlagsFactory.getFlags().getRenderFlowDeadlineSeconds());
- assertThat(ServiceFlowType.WEB_TRIGGER_FLOW.getExecutionTimeout())
- .isEqualTo(FlagsFactory.getFlags().getWebTriggerFlowDeadlineSeconds());
- assertThat(ServiceFlowType.EXAMPLE_STORE_FLOW.getExecutionTimeout())
- .isEqualTo(FlagsFactory.getFlags().getExampleStoreFlowDeadlineSeconds());
- assertThat(ServiceFlowType.DOWNLOAD_FLOW.getExecutionTimeout())
- .isEqualTo(FlagsFactory.getFlags().getDownloadFlowDeadlineSeconds());
- }
}
diff --git a/tests/servicetests/src/com/android/ondevicepersonalization/services/serviceflow/WebTriggerFlowTest.java b/tests/servicetests/src/com/android/ondevicepersonalization/services/serviceflow/WebTriggerFlowTest.java
index 5ff81af..408ea6b 100644
--- a/tests/servicetests/src/com/android/ondevicepersonalization/services/serviceflow/WebTriggerFlowTest.java
+++ b/tests/servicetests/src/com/android/ondevicepersonalization/services/serviceflow/WebTriggerFlowTest.java
@@ -16,11 +16,11 @@
package com.android.ondevicepersonalization.services.serviceflow;
+import static com.android.ondevicepersonalization.services.PhFlags.KEY_SHARED_ISOLATED_PROCESS_FEATURE_ENABLED;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.when;
import android.adservices.ondevicepersonalization.CalleeMetadata;
import android.adservices.ondevicepersonalization.Constants;
@@ -36,10 +36,12 @@
import com.android.compatibility.common.util.ShellUtils;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.modules.utils.build.SdkLevel;
import com.android.modules.utils.testing.ExtendedMockitoRule;
import com.android.ondevicepersonalization.services.Flags;
import com.android.ondevicepersonalization.services.FlagsFactory;
import com.android.ondevicepersonalization.services.PhFlagsTestUtil;
+import com.android.ondevicepersonalization.services.StableFlags;
import com.android.ondevicepersonalization.services.data.DbUtils;
import com.android.ondevicepersonalization.services.data.OnDevicePersonalizationDbHelper;
import com.android.ondevicepersonalization.services.data.events.EventsDao;
@@ -52,15 +54,16 @@
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
+import org.junit.runners.Parameterized;
import org.mockito.Mock;
-import org.mockito.Spy;
import org.mockito.quality.Strictness;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
import java.util.concurrent.CountDownLatch;
-@RunWith(JUnit4.class)
+@RunWith(Parameterized.class)
public class WebTriggerFlowTest {
private final Context mContext = ApplicationProvider.getApplicationContext();
@@ -75,12 +78,36 @@
@Mock UserPrivacyStatus mUserPrivacyStatus;
- @Spy
- private Flags mSpyFlags = spy(FlagsFactory.getFlags());
+ @Parameterized.Parameter(0)
+ public boolean mIsSipFeatureEnabled;
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(
+ new Object[][] {
+ {true}, {false}
+ }
+ );
+ }
+
+ static class TestFlags implements Flags {
+ int mIsolatedServiceDeadlineSeconds = 30;
+ boolean mGlobalKillSwitch = false;
+ @Override
+ public boolean getGlobalKillSwitch() {
+ return mGlobalKillSwitch;
+ }
+ @Override public int getIsolatedServiceDeadlineSeconds() {
+ return mIsolatedServiceDeadlineSeconds;
+ }
+ };
+
+ private TestFlags mSpyFlags = new TestFlags();
@Rule
public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
.mockStatic(FlagsFactory.class)
+ .spyStatic(StableFlags.class)
.spyStatic(UserPrivacyStatus.class)
.setStrictness(Strictness.LENIENT)
.build();
@@ -90,7 +117,8 @@
PhFlagsTestUtil.setUpDeviceConfigPermissions();
ShellUtils.runShellCommand("settings put global hidden_api_policy 1");
ExtendedMockito.doReturn(mSpyFlags).when(FlagsFactory::getFlags);
- when(mSpyFlags.getGlobalKillSwitch()).thenReturn(false);
+ ExtendedMockito.doReturn(SdkLevel.isAtLeastU() && mIsSipFeatureEnabled).when(
+ () -> StableFlags.get(KEY_SHARED_ISOLATED_PROCESS_FEATURE_ENABLED));
ExtendedMockito.doReturn(mUserPrivacyStatus).when(UserPrivacyStatus::getInstance);
doReturn(true).when(mUserPrivacyStatus).isMeasurementEnabled();
@@ -109,7 +137,7 @@
@Test
public void testWebTriggerFlow_GlobalKillswitchOn() throws Exception {
- when(mSpyFlags.getGlobalKillSwitch()).thenReturn(true);
+ mSpyFlags.mGlobalKillSwitch = true;
mSfo.schedule(ServiceFlowType.WEB_TRIGGER_FLOW, getWebTriggerParams(), mContext,
new TestWebCallback(), 100L, 110L);
diff --git a/tests/servicetests/src/com/android/ondevicepersonalization/services/serviceflow/WebViewFlowTest.java b/tests/servicetests/src/com/android/ondevicepersonalization/services/serviceflow/WebViewFlowTest.java
index 663eef2..7804bb2 100644
--- a/tests/servicetests/src/com/android/ondevicepersonalization/services/serviceflow/WebViewFlowTest.java
+++ b/tests/servicetests/src/com/android/ondevicepersonalization/services/serviceflow/WebViewFlowTest.java
@@ -16,6 +16,8 @@
package com.android.ondevicepersonalization.services.serviceflow;
+import static com.android.ondevicepersonalization.services.PhFlags.KEY_SHARED_ISOLATED_PROCESS_FEATURE_ENABLED;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.spy;
@@ -32,8 +34,14 @@
import androidx.test.core.app.ApplicationProvider;
import com.android.compatibility.common.util.ShellUtils;
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.modules.utils.build.SdkLevel;
+import com.android.modules.utils.testing.ExtendedMockitoRule;
+import com.android.ondevicepersonalization.services.Flags;
+import com.android.ondevicepersonalization.services.FlagsFactory;
import com.android.ondevicepersonalization.services.OnDevicePersonalizationExecutors;
import com.android.ondevicepersonalization.services.PhFlagsTestUtil;
+import com.android.ondevicepersonalization.services.StableFlags;
import com.android.ondevicepersonalization.services.data.OnDevicePersonalizationDbHelper;
import com.android.ondevicepersonalization.services.data.events.EventUrlPayload;
import com.android.ondevicepersonalization.services.data.events.EventsContract;
@@ -45,15 +53,18 @@
import org.jetbrains.annotations.NotNull;
import org.junit.After;
import org.junit.Before;
-import org.junit.Ignore;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
+import org.junit.runners.Parameterized;
+import org.mockito.quality.Strictness;
import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Collection;
import java.util.concurrent.CountDownLatch;
-@RunWith(JUnit4.class)
+@RunWith(Parameterized.class)
public class WebViewFlowTest {
private static final String SERVICE_CLASS = "com.test.TestPersonalizationService";
@@ -66,10 +77,38 @@
private FlowCallback mCallback;
private static final ServiceFlowOrchestrator sSfo = ServiceFlowOrchestrator.getInstance();
+ @Parameterized.Parameter(0)
+ public boolean mIsSipFeatureEnabled;
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(
+ new Object[][] {
+ {true}, {false}
+ }
+ );
+ }
+
+ private Flags mSpyFlags = new Flags() {
+ int mIsolatedServiceDeadlineSeconds = 30;
+ @Override public int getIsolatedServiceDeadlineSeconds() {
+ return mIsolatedServiceDeadlineSeconds;
+ }
+ };
+
+ @Rule
+ public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
+ .mockStatic(FlagsFactory.class)
+ .spyStatic(StableFlags.class)
+ .setStrictness(Strictness.LENIENT)
+ .build();
@Before
public void setup() throws Exception {
PhFlagsTestUtil.setUpDeviceConfigPermissions();
ShellUtils.runShellCommand("settings put global hidden_api_policy 1");
+ ExtendedMockito.doReturn(mSpyFlags).when(FlagsFactory::getFlags);
+ ExtendedMockito.doReturn(SdkLevel.isAtLeastU() && mIsSipFeatureEnabled).when(
+ () -> StableFlags.get(KEY_SHARED_ISOLATED_PROCESS_FEATURE_ENABLED));
mDao = EventsDao.getInstanceForTest(mContext);
Query mTestQuery = new Query.Builder(
@@ -122,7 +161,6 @@
}
@Test
- @Ignore("TODO: b/342475912 - temporary disable failing tests.")
public void testDedupMultiplePayloads() throws Exception {
mTestEventPayload =
new EventUrlPayload(createEventParameters(), null, null);
diff --git a/tests/servicetests/src/com/android/ondevicepersonalization/services/util/AllowListUtilsTest.java b/tests/servicetests/src/com/android/ondevicepersonalization/services/util/AllowListUtilsTest.java
index 21b132b..9bc75be 100644
--- a/tests/servicetests/src/com/android/ondevicepersonalization/services/util/AllowListUtilsTest.java
+++ b/tests/servicetests/src/com/android/ondevicepersonalization/services/util/AllowListUtilsTest.java
@@ -137,5 +137,40 @@
"com.test.app2",
"EFGH",
"com.test.app1:ABCD;com.test.app2:EFGH;com.test.app3:PQRS"));
+ // Match - Wildcard left side.
+ assertTrue(AllowListUtils.isPairAllowListed(
+ "com.test.app1",
+ "ABCD",
+ "com.test.app2",
+ "EFGH",
+ "*;com.test.app2"));
+ // Match - Wildcard right side.
+ assertTrue(AllowListUtils.isPairAllowListed(
+ "com.test.app1",
+ "ABCD",
+ "com.test.app2",
+ "EFGH",
+ "com.test.app1;*"));
+ // Match - Wildcard both sides.
+ assertTrue(AllowListUtils.isPairAllowListed(
+ "com.test.app1",
+ "ABCD",
+ "com.test.app2",
+ "EFGH",
+ "*;*"));
+ // No match - Wildcard left side, right side mismatch
+ assertFalse(AllowListUtils.isPairAllowListed(
+ "com.test.app1",
+ "ABCD",
+ "com.test.app2",
+ "EFGH",
+ "*;com.test.app3"));
+ // No Match - Left side mismatch, Wildcard right side.
+ assertFalse(AllowListUtils.isPairAllowListed(
+ "com.test.app1",
+ "ABCD",
+ "com.test.app2",
+ "EFGH",
+ "com.test.app3;*"));
}
}
diff --git a/tests/servicetests/src/com/android/ondevicepersonalization/services/util/DebugUtilsTest.java b/tests/servicetests/src/com/android/ondevicepersonalization/services/util/DebugUtilsTest.java
index 028bada..5a7823f 100644
--- a/tests/servicetests/src/com/android/ondevicepersonalization/services/util/DebugUtilsTest.java
+++ b/tests/servicetests/src/com/android/ondevicepersonalization/services/util/DebugUtilsTest.java
@@ -162,11 +162,13 @@
.thenReturn(0);
}
- class TestFlags implements Flags {
- public boolean mIsolatedServiceDebuggingEnabled;
+ private static final class TestFlags implements Flags {
+ private final boolean mIsolatedServiceDebuggingEnabled;
+
TestFlags(boolean value) {
mIsolatedServiceDebuggingEnabled = value;
}
+
@Override public boolean isIsolatedServiceDebuggingEnabled() {
return mIsolatedServiceDebuggingEnabled;
}
diff --git a/tests/servicetests/src/com/android/ondevicepersonalization/services/util/NoiseUtilTest.java b/tests/servicetests/src/com/android/ondevicepersonalization/services/util/NoiseUtilTest.java
new file mode 100644
index 0000000..c5f13a0
--- /dev/null
+++ b/tests/servicetests/src/com/android/ondevicepersonalization/services/util/NoiseUtilTest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ondevicepersonalization.services.util;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.when;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.concurrent.ThreadLocalRandom;
+
+public class NoiseUtilTest {
+ @Mock ThreadLocalRandom mMockRandom;
+ private NoiseUtil mNoiseUtil;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mNoiseUtil = new NoiseUtil();
+ }
+
+ @Test
+ public void applyNoise_ToBestValue_returnActualValue() {
+ when(mMockRandom.nextDouble()).thenReturn(0.2);
+ int output = mNoiseUtil.applyNoiseToBestValue(5, 10, mMockRandom);
+ assertThat(output).isEqualTo(5);
+ }
+
+ @Test
+ public void applyNoise_ToBestValue_returnFakeValue() {
+ when(mMockRandom.nextDouble()).thenReturn(0.02);
+ when(mMockRandom.nextInt(anyInt())).thenReturn(6);
+ int output = mNoiseUtil.applyNoiseToBestValue(5, 10, mMockRandom);
+ assertThat(output).isEqualTo(6);
+ }
+
+ @Test
+ public void invalidActualValue() {
+ int output = mNoiseUtil.applyNoiseToBestValue(11, 10, mMockRandom);
+ assertThat(output).isEqualTo(-1);
+ }
+
+ @Test
+ public void invalidNegativeValue() {
+ int output = mNoiseUtil.applyNoiseToBestValue(-2, 10, mMockRandom);
+ assertThat(output).isEqualTo(-1);
+ }
+
+ @Test
+ public void applyNoise_ToBestValue_returnNotActualFakeValue() {
+ when(mMockRandom.nextDouble()).thenReturn(0.02);
+ when(mMockRandom.nextInt(anyInt())).thenReturn(5, 7);
+ int output = mNoiseUtil.applyNoiseToBestValue(5, 10, mMockRandom);
+ assertThat(output).isEqualTo(7);
+ }
+}
diff --git a/tests/servicetests/src/com/test/TestPersonalizationHandler.java b/tests/servicetests/src/com/test/TestPersonalizationHandler.java
index 0644429..60a8b5f 100644
--- a/tests/servicetests/src/com/test/TestPersonalizationHandler.java
+++ b/tests/servicetests/src/com/test/TestPersonalizationHandler.java
@@ -38,6 +38,7 @@
import android.annotation.NonNull;
import android.content.ContentValues;
import android.os.OutcomeReceiver;
+import android.os.PersistableBundle;
import android.util.Log;
import java.util.ArrayList;
@@ -48,7 +49,11 @@
// TODO(b/249345663) Move this class and related manifest to separate APK for more realistic testing
public class TestPersonalizationHandler implements IsolatedWorker {
- public final String TAG = "TestPersonalizationHandler";
+ public static final String TAG = "TestPersonalizationHandler";
+
+ /** Bundle key that mimics a timeout in {@link #onExecute}. */
+ public static final String TIMEOUT_KEY = "timeout_key";
+
private final KeyValueStore mRemoteData;
TestPersonalizationHandler(KeyValueStore remoteData) {
@@ -82,6 +87,12 @@
@NonNull ExecuteInput input,
@NonNull OutcomeReceiver<ExecuteOutput, IsolatedServiceException> receiver) {
Log.d(TAG, "onExecute() started.");
+ PersistableBundle inputBundle = input.getAppParams();
+ if (inputBundle != null && inputBundle.getBoolean("timeout_key", false)) {
+ Log.d(TAG, "onExecute() skipped.");
+ return;
+ }
+ Log.d(TAG, "onExecute() continuing.");
ContentValues logData = new ContentValues();
logData.put("id", "bid1");
logData.put("pr", 5.0);
diff --git a/tests/systemserviceapitests/AndroidManifest.xml b/tests/systemserviceapitests/AndroidManifest.xml
index 27f721e..144bc29 100644
--- a/tests/systemserviceapitests/AndroidManifest.xml
+++ b/tests/systemserviceapitests/AndroidManifest.xml
@@ -21,7 +21,7 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
- <application android:label="OnDevicePersonalizationSystemServiceApiTests">
+ <application android:debuggable="true" android:label="OnDevicePersonalizationSystemServiceApiTests">
<uses-library android:name="android.test.runner"/>
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/tests/systemserviceimpltests/Android.bp b/tests/systemserviceimpltests/Android.bp
index 3d382b5..916c5cf 100644
--- a/tests/systemserviceimpltests/Android.bp
+++ b/tests/systemserviceimpltests/Android.bp
@@ -41,6 +41,7 @@
],
sdk_version: "module_current",
min_sdk_version: "Tiramisu",
+ compile_multilib: "both",
test_suites: [
"general-tests",
"mts-ondevicepersonalization",
diff --git a/tests/testutils/src/com/android/ondevicepersonalization/testing/utils/ResultReceiver.java b/tests/testutils/src/com/android/ondevicepersonalization/testing/utils/ResultReceiver.java
index 7010d89..aa5dafe 100644
--- a/tests/testutils/src/com/android/ondevicepersonalization/testing/utils/ResultReceiver.java
+++ b/tests/testutils/src/com/android/ondevicepersonalization/testing/utils/ResultReceiver.java
@@ -15,21 +15,44 @@
*/
package com.android.ondevicepersonalization.testing.utils;
+import static org.junit.Assert.assertTrue;
+
import android.os.OutcomeReceiver;
+import java.time.Duration;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
/**
* A synchronous wrapper around OutcomeReceiver for testing.
*/
public class ResultReceiver<T> implements OutcomeReceiver<T, Exception> {
private final CountDownLatch mLatch = new CountDownLatch(1);
+ private final Duration mDeadline;
private T mResult = null;
private Exception mException = null;
private boolean mSuccess = false;
private boolean mError = false;
private boolean mCalled = false;
+ /** Creates a ResultReceiver. */
+ public ResultReceiver() {
+ this(Duration.ofSeconds(30));
+ }
+
+ /** Creates a ResultReceiver with a deadline. */
+ public ResultReceiver(Duration deadline) {
+ mDeadline = deadline;
+ }
+
+ private void await() throws InterruptedException {
+ if (mDeadline != null) {
+ assertTrue(mLatch.await(mDeadline.toMillis(), TimeUnit.MILLISECONDS));
+ } else {
+ mLatch.await();
+ }
+ }
+
@Override public void onResult(T result) {
mCalled = true;
mSuccess = true;
@@ -46,37 +69,37 @@
/** Returns the result passed to the OutcomeReceiver. */
public T getResult() throws InterruptedException {
- mLatch.await();
+ await();
return mResult;
}
/** Returns the exception passed to the OutcomeReceiver. */
public Exception getException() throws InterruptedException {
- mLatch.await();
+ await();
return mException;
}
/** Returns true if onResult() was called. */
public boolean isSuccess() throws InterruptedException {
- mLatch.await();
+ await();
return mSuccess;
}
/** Returns true if onError() was called. */
public boolean isError() throws InterruptedException {
- mLatch.await();
+ await();
return mError;
}
/** Returns true if onResult() or onError() was called. */
public boolean isCalled() throws InterruptedException {
- mLatch.await();
+ await();
return mCalled;
}
/** Returns the exception message. */
public String getErrorMessage() throws InterruptedException {
- mLatch.await();
+ await();
if (mException != null) {
return mException.getClass().getSimpleName()
+ ": " + mException.getMessage();