Add sources for API 35
Downloaded from https://dl.google.com/android/repository/source-35_r01.zip
using SdkManager in Studio
Test: None
Change-Id: I83f78aa820b66edfdc9f8594d17bc7b6cacccec1
diff --git a/android-35/android/adservices/AdServicesFrameworkInitializer.java b/android-35/android/adservices/AdServicesFrameworkInitializer.java
new file mode 100644
index 0000000..ef61e13
--- /dev/null
+++ b/android-35/android/adservices/AdServicesFrameworkInitializer.java
@@ -0,0 +1,137 @@
+/*
+ * 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;
+
+import static android.adservices.adid.AdIdManager.ADID_SERVICE;
+import static android.adservices.adselection.AdSelectionManager.AD_SELECTION_SERVICE;
+import static android.adservices.appsetid.AppSetIdManager.APPSETID_SERVICE;
+import static android.adservices.common.AdServicesCommonManager.AD_SERVICES_COMMON_SERVICE;
+import static android.adservices.customaudience.CustomAudienceManager.CUSTOM_AUDIENCE_SERVICE;
+import static android.adservices.measurement.MeasurementManager.MEASUREMENT_SERVICE;
+import static android.adservices.signals.ProtectedSignalsManager.PROTECTED_SIGNALS_SERVICE;
+import static android.adservices.topics.TopicsManager.TOPICS_SERVICE;
+
+import android.adservices.adid.AdIdManager;
+import android.adservices.adselection.AdSelectionManager;
+import android.adservices.appsetid.AppSetIdManager;
+import android.adservices.common.AdServicesCommonManager;
+import android.adservices.customaudience.CustomAudienceManager;
+import android.adservices.measurement.MeasurementManager;
+import android.adservices.signals.ProtectedSignalsManager;
+import android.adservices.topics.TopicsManager;
+import android.annotation.SystemApi;
+import android.app.SystemServiceRegistry;
+import android.app.sdksandbox.SdkSandboxSystemServiceRegistry;
+import android.content.Context;
+import android.os.Build;
+
+import androidx.annotation.RequiresApi;
+
+import com.android.adservices.LogUtil;
+
+/**
+ * Class holding initialization code for the AdServices module.
+ *
+ * @hide
+ */
+// TODO(b/269798827): Enable for R.
+@RequiresApi(Build.VERSION_CODES.S)
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+public class AdServicesFrameworkInitializer {
+ private AdServicesFrameworkInitializer() {
+ }
+
+ /**
+ * Called by {@link SystemServiceRegistry}'s static initializer and registers all
+ * AdServices services to {@link Context}, so that
+ * {@link Context#getSystemService} can return them.
+ *
+ * @throws IllegalStateException if this is called from anywhere besides
+ * {@link SystemServiceRegistry}
+ */
+ public static void registerServiceWrappers() {
+ LogUtil.d("Registering AdServices's TopicsManager.");
+ SystemServiceRegistry.registerContextAwareService(
+ TOPICS_SERVICE, TopicsManager.class,
+ (c) -> new TopicsManager(c));
+ // TODO(b/242889021): don't use this workaround on devices that have proper fix
+ SdkSandboxSystemServiceRegistry.getInstance()
+ .registerServiceMutator(
+ TOPICS_SERVICE,
+ (service, ctx) -> ((TopicsManager) service).initialize(ctx));
+
+ LogUtil.d("Registering AdServices's CustomAudienceManager.");
+ SystemServiceRegistry.registerContextAwareService(
+ CUSTOM_AUDIENCE_SERVICE, CustomAudienceManager.class, CustomAudienceManager::new);
+ // TODO(b/242889021): don't use this workaround on devices that have proper fix
+ SdkSandboxSystemServiceRegistry.getInstance()
+ .registerServiceMutator(
+ CUSTOM_AUDIENCE_SERVICE,
+ (service, ctx) -> ((CustomAudienceManager) service).initialize(ctx));
+
+ LogUtil.d("Registering AdServices's AdSelectionManager.");
+ SystemServiceRegistry.registerContextAwareService(
+ AD_SELECTION_SERVICE, AdSelectionManager.class, AdSelectionManager::new);
+ // TODO(b/242889021): don't use this workaround on devices that have proper fix
+ SdkSandboxSystemServiceRegistry.getInstance()
+ .registerServiceMutator(
+ AD_SELECTION_SERVICE,
+ (service, ctx) -> ((AdSelectionManager) service).initialize(ctx));
+
+ LogUtil.d("Registering AdServices's ProtectedSignalsManager.");
+ SystemServiceRegistry.registerContextAwareService(
+ PROTECTED_SIGNALS_SERVICE,
+ ProtectedSignalsManager.class,
+ ProtectedSignalsManager::new);
+ // TODO(b/242889021): don't use this workaround on devices that have proper fix
+ SdkSandboxSystemServiceRegistry.getInstance()
+ .registerServiceMutator(
+ PROTECTED_SIGNALS_SERVICE,
+ (service, ctx) -> ((ProtectedSignalsManager) service).initialize(ctx));
+
+ LogUtil.d("Registering AdServices's MeasurementManager.");
+ SystemServiceRegistry.registerContextAwareService(
+ MEASUREMENT_SERVICE, MeasurementManager.class, MeasurementManager::new);
+ // TODO(b/242889021): don't use this workaround on devices that have proper fix
+ SdkSandboxSystemServiceRegistry.getInstance()
+ .registerServiceMutator(
+ MEASUREMENT_SERVICE,
+ (service, ctx) -> ((MeasurementManager) service).initialize(ctx));
+
+ LogUtil.d("Registering AdServices's AdIdManager.");
+ SystemServiceRegistry.registerContextAwareService(
+ ADID_SERVICE, AdIdManager.class, (c) -> new AdIdManager(c));
+ // TODO(b/242889021): don't use this workaround on devices that have proper fix
+ SdkSandboxSystemServiceRegistry.getInstance()
+ .registerServiceMutator(
+ ADID_SERVICE, (service, ctx) -> ((AdIdManager) service).initialize(ctx));
+
+ LogUtil.d("Registering AdServices's AppSetIdManager.");
+ SystemServiceRegistry.registerContextAwareService(
+ APPSETID_SERVICE, AppSetIdManager.class, (c) -> new AppSetIdManager(c));
+ // TODO(b/242889021): don't use this workaround on devices that have proper fix
+ SdkSandboxSystemServiceRegistry.getInstance()
+ .registerServiceMutator(
+ APPSETID_SERVICE,
+ (service, ctx) -> ((AppSetIdManager) service).initialize(ctx));
+
+ LogUtil.d("Registering AdServices's AdServicesCommonManager.");
+ SystemServiceRegistry.registerContextAwareService(AD_SERVICES_COMMON_SERVICE,
+ AdServicesCommonManager.class,
+ (c) -> new AdServicesCommonManager(c));
+ }
+}
diff --git a/android-35/android/adservices/AdServicesState.java b/android-35/android/adservices/AdServicesState.java
new file mode 100644
index 0000000..42d2c51
--- /dev/null
+++ b/android-35/android/adservices/AdServicesState.java
@@ -0,0 +1,33 @@
+/*
+ * 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;
+
+/** This class specifies the state of the APIs exposed by AdServicesApi apk. */
+public class AdServicesState {
+
+ private AdServicesState() {}
+
+ /**
+ * Returns current state of the {@code AdServicesApi}. The state of AdServicesApi may change
+ * only upon reboot, so this value can be cached, but not persisted, i.e., the value should be
+ * rechecked after a reboot.
+ */
+ public static boolean isAdServicesStateEnabled() {
+ return true;
+ }
+}
+
diff --git a/android-35/android/adservices/AdServicesVersion.java b/android-35/android/adservices/AdServicesVersion.java
new file mode 100644
index 0000000..b059b6e
--- /dev/null
+++ b/android-35/android/adservices/AdServicesVersion.java
@@ -0,0 +1,45 @@
+/*
+ * 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;
+
+import android.annotation.SuppressLint;
+
+/**
+ * This class specifies the current version of the AdServices API.
+ *
+ * @removed
+ */
+public class AdServicesVersion {
+
+ /**
+ * @hide
+ */
+ public AdServicesVersion() {}
+
+ /**
+ * The API version of this AdServices API.
+ */
+ @SuppressLint("CompileTimeConstant")
+ public static final int API_VERSION;
+
+ // This variable needs to be initialized in static {} , otherwise javac
+ // would inline these constants and they won't be updatable.
+ static {
+ API_VERSION = 2;
+ }
+}
+
diff --git a/android-35/android/adservices/adid/AdId.java b/android-35/android/adservices/adid/AdId.java
new file mode 100644
index 0000000..5efd2ba
--- /dev/null
+++ b/android-35/android/adservices/adid/AdId.java
@@ -0,0 +1,105 @@
+/*
+ * 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.adid;
+
+import android.annotation.NonNull;
+
+import java.util.Objects;
+
+/**
+ * A unique, user-resettable, device-wide, per-profile ID for advertising.
+ *
+ * <p>Ad networks may use {@code AdId} to monetize for Interest Based Advertising (IBA), i.e.
+ * targeting and remarketing ads. The user may limit availability of this identifier.
+ *
+ * @see AdIdManager#getAdId(Executor, OutcomeReceiver)
+ */
+public class AdId {
+ @NonNull private final String mAdId;
+ private final boolean mLimitAdTrackingEnabled;
+
+ /**
+ * A zeroed-out {@link #getAdId ad id} that is returned when the user has {@link
+ * #isLimitAdTrackingEnabled limited ad tracking}.
+ */
+ public static final String ZERO_OUT = "00000000-0000-0000-0000-000000000000";
+
+ /**
+ * Creates an instance of {@link AdId}
+ *
+ * @param adId obtained from the provider service.
+ * @param limitAdTrackingEnabled value from the provider service which determines the value of
+ * adId.
+ */
+ public AdId(@NonNull String adId, boolean limitAdTrackingEnabled) {
+ mAdId = adId;
+ mLimitAdTrackingEnabled = limitAdTrackingEnabled;
+ }
+
+ /**
+ * The advertising ID.
+ *
+ * <p>The value of advertising Id depends on a combination of {@link
+ * #isLimitAdTrackingEnabled()} and {@link
+ * android.adservices.common.AdServicesPermissions#ACCESS_ADSERVICES_AD_ID}.
+ *
+ * <p>When the user is {@link #isLimitAdTrackingEnabled limiting ad tracking}, the API returns
+ * {@link #ZERO_OUT}. This disallows a caller to track the user for monetization purposes.
+ *
+ * <p>Otherwise, a string unique to the device and user is returned, which can be used to track
+ * users for advertising.
+ */
+ public @NonNull String getAdId() {
+ return mAdId;
+ }
+
+ /**
+ * Retrieves the limit ad tracking enabled setting.
+ *
+ * <p>This value is true if user has limit ad tracking enabled, {@code false} otherwise.
+ */
+ public boolean isLimitAdTrackingEnabled() {
+ return mLimitAdTrackingEnabled;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof AdId)) {
+ return false;
+ }
+ AdId that = (AdId) o;
+ return mAdId.equals(that.mAdId)
+ && (mLimitAdTrackingEnabled == that.mLimitAdTrackingEnabled);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mAdId, mLimitAdTrackingEnabled);
+ }
+
+ @Override
+ public String toString() {
+ return "AdId{"
+ + "mAdId="
+ + mAdId
+ + ", mLimitAdTrackingEnabled='"
+ + mLimitAdTrackingEnabled
+ + '}';
+ }
+}
diff --git a/android-35/android/adservices/adid/AdIdCompatibleManager.java b/android-35/android/adservices/adid/AdIdCompatibleManager.java
new file mode 100644
index 0000000..0d278f3
--- /dev/null
+++ b/android-35/android/adservices/adid/AdIdCompatibleManager.java
@@ -0,0 +1,196 @@
+/*
+ * 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.adid;
+
+import static android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_AD_ID;
+
+import android.adservices.common.AdServicesOutcomeReceiver;
+import android.adservices.common.AdServicesStatusUtils;
+import android.adservices.common.CallerMetadata;
+import android.adservices.common.SandboxedSdkContextUtils;
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.app.sdksandbox.SandboxedSdkContext;
+import android.content.Context;
+import android.os.RemoteException;
+import android.os.SystemClock;
+
+import com.android.adservices.AdServicesCommon;
+import com.android.adservices.LogUtil;
+import com.android.adservices.ServiceBinder;
+import com.android.adservices.shared.common.ServiceUnavailableException;
+
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/**
+ * AdId Manager provides APIs for app and ad-SDKs to access advertising ID. The advertising ID is a
+ * unique, per-device, user-resettable ID for advertising. It gives users better controls and
+ * provides developers with a simple, standard system to continue to monetize their apps via
+ * personalized ads (formerly known as interest-based ads).
+ *
+ * @hide
+ */
+public class AdIdCompatibleManager {
+ private Context mContext;
+ private ServiceBinder<IAdIdService> mServiceBinder;
+
+ /**
+ * Create AdIdCompatibleManager
+ *
+ * @hide
+ */
+ public AdIdCompatibleManager(@NonNull Context context) {
+ Objects.requireNonNull(context);
+
+ // In case the AdIdManager is initiated from inside a sdk_sandbox process the fields
+ // will be immediately rewritten by the initialize method below.
+ initialize(context);
+ }
+
+ /**
+ * Initializes {@link AdIdCompatibleManager} with the given {@code context}.
+ *
+ * <p>This method is called by the {@link SandboxedSdkContext} to propagate the correct context.
+ * For more information check the javadoc on the {@link
+ * android.app.sdksandbox.SdkSandboxSystemServiceRegistry}.
+ *
+ * @hide
+ * @see android.app.sdksandbox.SdkSandboxSystemServiceRegistry
+ */
+ void initialize(Context context) {
+ mContext = context;
+ mServiceBinder =
+ ServiceBinder.getServiceBinder(
+ context,
+ AdServicesCommon.ACTION_ADID_SERVICE,
+ IAdIdService.Stub::asInterface);
+ }
+
+ @NonNull
+ private IAdIdService getService(
+ @CallbackExecutor Executor executor,
+ AdServicesOutcomeReceiver<AdId, Exception> callback) {
+ IAdIdService service = null;
+ try {
+ service = mServiceBinder.getService();
+
+ // Throw ServiceUnavailableException and set it to the callback.
+ if (service == null) {
+ throw new ServiceUnavailableException();
+ }
+ } catch (RuntimeException e) {
+ LogUtil.e(e, "Failed binding to AdId service");
+ executor.execute(() -> callback.onError(e));
+ }
+
+ return service;
+ }
+
+ @NonNull
+ private Context getContext() {
+ return mContext;
+ }
+
+ /**
+ * Return the AdId. For use on Android R or lower.
+ *
+ * @param executor The executor to run callback.
+ * @param callback The callback that's called after adid are available or an error occurs.
+ * @hide
+ */
+ @RequiresPermission(ACCESS_ADSERVICES_AD_ID)
+ @NonNull
+ public void getAdId(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull AdServicesOutcomeReceiver<AdId, Exception> callback) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+ CallerMetadata callerMetadata =
+ new CallerMetadata.Builder()
+ .setBinderElapsedTimestamp(SystemClock.elapsedRealtime())
+ .build();
+
+ String appPackageName = "";
+ String sdkPackageName = "";
+ // First check if context is SandboxedSdkContext or not
+ Context getAdIdRequestContext = getContext();
+ SandboxedSdkContext requestContext =
+ SandboxedSdkContextUtils.getAsSandboxedSdkContext(getAdIdRequestContext);
+ if (requestContext != null) {
+ sdkPackageName = requestContext.getSdkPackageName();
+ appPackageName = requestContext.getClientPackageName();
+ } else { // This is the case without the Sandbox.
+ appPackageName = getAdIdRequestContext.getPackageName();
+ }
+
+ try {
+ IAdIdService service = getService(executor, callback);
+ if (service == null) {
+ LogUtil.d("Unable to find AdId service");
+ return;
+ }
+
+ service.getAdId(
+ new GetAdIdParam.Builder()
+ .setAppPackageName(appPackageName)
+ .setSdkPackageName(sdkPackageName)
+ .build(),
+ callerMetadata,
+ new IGetAdIdCallback.Stub() {
+ @Override
+ public void onResult(GetAdIdResult resultParcel) {
+ executor.execute(
+ () -> {
+ if (resultParcel.isSuccess()) {
+ callback.onResult(
+ new AdId(
+ resultParcel.getAdId(),
+ resultParcel.isLatEnabled()));
+ } else {
+ callback.onError(
+ AdServicesStatusUtils.asException(
+ resultParcel));
+ }
+ });
+ }
+
+ @Override
+ public void onError(int resultCode) {
+ executor.execute(
+ () ->
+ callback.onError(
+ AdServicesStatusUtils.asException(resultCode)));
+ }
+ });
+ } catch (RemoteException e) {
+ LogUtil.e(e, "RemoteException");
+ callback.onError(e);
+ }
+ }
+
+ /**
+ * If the service is in an APK (as opposed to the system service), unbind it from the service to
+ * allow the APK process to die.
+ *
+ * @hide
+ */
+ // TODO: change to @VisibleForTesting
+ public void unbindFromService() {
+ mServiceBinder.unbindFromService();
+ }
+}
diff --git a/android-35/android/adservices/adid/AdIdManager.java b/android-35/android/adservices/adid/AdIdManager.java
new file mode 100644
index 0000000..4cec751
--- /dev/null
+++ b/android-35/android/adservices/adid/AdIdManager.java
@@ -0,0 +1,136 @@
+/*
+ * 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.adid;
+
+import static android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_AD_ID;
+
+import android.adservices.common.AdServicesOutcomeReceiver;
+import android.adservices.common.OutcomeReceiverConverter;
+import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.app.sdksandbox.SandboxedSdkContext;
+import android.content.Context;
+import android.os.Build;
+import android.os.OutcomeReceiver;
+
+import androidx.annotation.RequiresApi;
+
+import com.android.adservices.flags.Flags;
+
+import java.util.concurrent.Executor;
+
+/**
+ * AdId Manager provides APIs for app and ad-SDKs to access advertising ID. The advertising ID is a
+ * unique, per-device, user-resettable ID for advertising. It gives users better controls and
+ * provides developers with a simple, standard system to continue to monetize their apps via
+ * personalized ads (formerly known as interest-based ads).
+ */
+public class AdIdManager {
+ /**
+ * Service used for registering AdIdManager in the system service registry.
+ *
+ * @hide
+ */
+ public static final String ADID_SERVICE = "adid_service";
+
+ // When an app calls the AdId API directly, it sets the SDK name to empty string.
+ static final String EMPTY_SDK = "";
+
+ private final AdIdCompatibleManager mImpl;
+
+ /**
+ * Factory method for creating an instance of AdIdManager.
+ *
+ * @param context The {@link Context} to use
+ * @return A {@link AdIdManager} instance
+ */
+ @NonNull
+ public static AdIdManager get(@NonNull Context context) {
+ // On T+, context.getSystemService() does more than just call constructor.
+ return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
+ ? context.getSystemService(AdIdManager.class)
+ : new AdIdManager(context);
+ }
+
+ /**
+ * Create AdIdManager
+ *
+ * @hide
+ */
+ public AdIdManager(Context context) {
+ // In case the AdIdManager is initiated from inside a sdk_sandbox process the fields
+ // will be immediately rewritten by the initialize method below.
+ mImpl = new AdIdCompatibleManager(context);
+ }
+
+ /**
+ * Initializes {@link AdIdManager} with the given {@code context}.
+ *
+ * <p>This method is called by the {@link SandboxedSdkContext} to propagate the correct context.
+ * For more information check the javadoc on the {@link
+ * android.app.sdksandbox.SdkSandboxSystemServiceRegistry}.
+ *
+ * @hide
+ * @see android.app.sdksandbox.SdkSandboxSystemServiceRegistry
+ */
+ public AdIdManager initialize(Context context) {
+ mImpl.initialize(context);
+ return this;
+ }
+
+ /**
+ * Return the AdId.
+ *
+ * @param executor The executor to run callback.
+ * @param callback The callback that's called after adid are available or an error occurs.
+ */
+ @RequiresApi(Build.VERSION_CODES.S)
+ @RequiresPermission(ACCESS_ADSERVICES_AD_ID)
+ @NonNull
+ public void getAdId(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OutcomeReceiver<AdId, Exception> callback) {
+ mImpl.getAdId(executor, OutcomeReceiverConverter.toAdServicesOutcomeReceiver(callback));
+ }
+
+ /**
+ * Return the AdId. For use on Android R or lower.
+ *
+ * @param executor The executor to run callback.
+ * @param callback The callback that's called after adid are available or an error occurs.
+ */
+ @FlaggedApi(Flags.FLAG_ADSERVICES_OUTCOMERECEIVER_R_API_ENABLED)
+ @RequiresPermission(ACCESS_ADSERVICES_AD_ID)
+ @NonNull
+ public void getAdId(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull AdServicesOutcomeReceiver<AdId, Exception> callback) {
+ mImpl.getAdId(executor, callback);
+ }
+
+ /**
+ * If the service is in an APK (as opposed to the system service), unbind it from the service to
+ * allow the APK process to die.
+ *
+ * @hide
+ */
+ // TODO: change to @VisibleForTesting
+ public void unbindFromService() {
+ mImpl.unbindFromService();
+ }
+}
diff --git a/android-35/android/adservices/adid/AdIdProviderService.java b/android-35/android/adservices/adid/AdIdProviderService.java
new file mode 100644
index 0000000..e9781a8
--- /dev/null
+++ b/android-35/android/adservices/adid/AdIdProviderService.java
@@ -0,0 +1,87 @@
+/*
+ * 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.adid;
+
+import static android.adservices.common.AdServicesStatusUtils.STATUS_SUCCESS;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SdkConstant;
+import android.annotation.SystemApi;
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import java.io.IOException;
+
+/**
+ * Abstract Base class for provider service to implement generation of Advertising Id and
+ * limitAdTracking value.
+ *
+ * <p>The implementor of this service needs to override the onGetAdId method and provide a
+ * device-level unique advertising Id and limitAdTracking value on that device.
+ *
+ * @hide
+ */
+@SystemApi
+public abstract class AdIdProviderService extends Service {
+
+ /** The intent that the service must respond to. Add it to the intent filter of the service. */
+ @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+ public static final String SERVICE_INTERFACE = "android.adservices.adid.AdIdProviderService";
+
+ /**
+ * Abstract method which will be overridden by provider to provide the adId. For multi-user,
+ * multi-profiles on-device scenarios, separate instance of service per user is expected to
+ * implement this method.
+ */
+ @NonNull
+ public abstract AdId onGetAdId(int clientUid, @NonNull String clientPackageName)
+ throws IOException;
+
+ private final android.adservices.adid.IAdIdProviderService mInterface =
+ new android.adservices.adid.IAdIdProviderService.Stub() {
+ @Override
+ public void getAdIdProvider(
+ int appUID,
+ @NonNull String packageName,
+ @NonNull IGetAdIdProviderCallback resultCallback)
+ throws RemoteException {
+ try {
+ AdId adId = onGetAdId(appUID, packageName);
+ GetAdIdResult adIdInternal =
+ new GetAdIdResult.Builder()
+ .setStatusCode(STATUS_SUCCESS)
+ .setErrorMessage("")
+ .setAdId(adId.getAdId())
+ .setLatEnabled(adId.isLimitAdTrackingEnabled())
+ .build();
+
+ resultCallback.onResult(adIdInternal);
+ } catch (Throwable e) {
+ resultCallback.onError(e.getMessage());
+ }
+ }
+ };
+
+ @Nullable
+ @Override
+ public final IBinder onBind(@Nullable Intent intent) {
+ return mInterface.asBinder();
+ }
+}
diff --git a/android-35/android/adservices/adid/GetAdIdParam.java b/android-35/android/adservices/adid/GetAdIdParam.java
new file mode 100644
index 0000000..50bf5de
--- /dev/null
+++ b/android-35/android/adservices/adid/GetAdIdParam.java
@@ -0,0 +1,119 @@
+/*
+ * 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.adid;
+
+import static android.adservices.adid.AdIdManager.EMPTY_SDK;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Represent input params to the getAdId API.
+ *
+ * @hide
+ */
+public final class GetAdIdParam implements Parcelable {
+ private final String mSdkPackageName;
+ private final String mAppPackageName;
+
+ private GetAdIdParam(@Nullable String sdkPackageName, @NonNull String appPackageName) {
+ mSdkPackageName = sdkPackageName;
+ mAppPackageName = appPackageName;
+ }
+
+ private GetAdIdParam(@NonNull Parcel in) {
+ mSdkPackageName = in.readString();
+ mAppPackageName = in.readString();
+ }
+
+ public static final @NonNull Creator<GetAdIdParam> CREATOR =
+ new Parcelable.Creator<GetAdIdParam>() {
+ @Override
+ public GetAdIdParam createFromParcel(Parcel in) {
+ return new GetAdIdParam(in);
+ }
+
+ @Override
+ public GetAdIdParam[] newArray(int size) {
+ return new GetAdIdParam[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeString(mSdkPackageName);
+ out.writeString(mAppPackageName);
+ }
+
+ /** Get the Sdk Package Name. This is the package name in the Manifest. */
+ @NonNull
+ public String getSdkPackageName() {
+ return mSdkPackageName;
+ }
+
+ /** Get the App PackageName. */
+ @NonNull
+ public String getAppPackageName() {
+ return mAppPackageName;
+ }
+
+ /** Builder for {@link GetAdIdParam} objects. */
+ public static final class Builder {
+ private String mSdkPackageName;
+ private String mAppPackageName;
+
+ public Builder() {}
+
+ /**
+ * Set the Sdk Package Name. When the app calls the AdId API directly without using an SDK,
+ * don't set this field.
+ */
+ public @NonNull Builder setSdkPackageName(@NonNull String sdkPackageName) {
+ mSdkPackageName = sdkPackageName;
+ return this;
+ }
+
+ /** Set the App PackageName. */
+ public @NonNull Builder setAppPackageName(@NonNull String appPackageName) {
+ mAppPackageName = appPackageName;
+ return this;
+ }
+
+ /** Builds a {@link GetAdIdParam} instance. */
+ public @NonNull GetAdIdParam build() {
+ if (mSdkPackageName == null) {
+ // When Sdk package name is not set, we assume the App calls the AdId API
+ // directly.
+ // We set the Sdk package name to empty to mark this.
+ mSdkPackageName = EMPTY_SDK;
+ }
+
+ if (mAppPackageName == null || mAppPackageName.isEmpty()) {
+ throw new IllegalArgumentException("App PackageName must not be empty or null");
+ }
+
+ return new GetAdIdParam(mSdkPackageName, mAppPackageName);
+ }
+ }
+}
diff --git a/android-35/android/adservices/adid/GetAdIdResult.java b/android-35/android/adservices/adid/GetAdIdResult.java
new file mode 100644
index 0000000..bfbd191
--- /dev/null
+++ b/android-35/android/adservices/adid/GetAdIdResult.java
@@ -0,0 +1,190 @@
+/*
+ * 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.adid;
+
+import android.adservices.common.AdServicesResponse;
+import android.adservices.common.AdServicesStatusUtils;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Represent the result from the getAdId API.
+ *
+ * @hide
+ */
+public final class GetAdIdResult extends AdServicesResponse {
+ @NonNull private final String mAdId;
+ private final boolean mLimitAdTrackingEnabled;
+
+ private GetAdIdResult(
+ @AdServicesStatusUtils.StatusCode int resultCode,
+ @Nullable String errorMessage,
+ @NonNull String adId,
+ boolean isLimitAdTrackingEnabled) {
+ super(resultCode, errorMessage);
+ mAdId = adId;
+ mLimitAdTrackingEnabled = isLimitAdTrackingEnabled;
+ }
+
+ private GetAdIdResult(@NonNull Parcel in) {
+ super(in);
+ Objects.requireNonNull(in);
+
+ mAdId = in.readString();
+ mLimitAdTrackingEnabled = in.readBoolean();
+ }
+
+ public static final @NonNull Creator<GetAdIdResult> CREATOR =
+ new Parcelable.Creator<GetAdIdResult>() {
+ @Override
+ public GetAdIdResult createFromParcel(@NonNull Parcel in) {
+ Objects.requireNonNull(in);
+ return new GetAdIdResult(in);
+ }
+
+ @Override
+ public GetAdIdResult[] newArray(int size) {
+ return new GetAdIdResult[size];
+ }
+ };
+
+ /** @hide */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** @hide */
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeInt(mStatusCode);
+ out.writeString(mErrorMessage);
+ out.writeString(mAdId);
+ out.writeBoolean(mLimitAdTrackingEnabled);
+ }
+
+ /**
+ * Returns the error message associated with this result.
+ *
+ * <p>If {@link #isSuccess} is {@code true}, the error message is always {@code null}. The error
+ * message may be {@code null} even if {@link #isSuccess} is {@code false}.
+ */
+ @Nullable
+ public String getErrorMessage() {
+ return mErrorMessage;
+ }
+
+ /** Returns the advertising ID associated with this result. */
+ @NonNull
+ public String getAdId() {
+ return mAdId;
+ }
+
+ /** Returns the Limited adtracking field associated with this result. */
+ public boolean isLatEnabled() {
+ return mLimitAdTrackingEnabled;
+ }
+
+ @Override
+ public String toString() {
+ return "GetAdIdResult{"
+ + "mResultCode="
+ + mStatusCode
+ + ", mErrorMessage='"
+ + mErrorMessage
+ + '\''
+ + ", mAdId="
+ + mAdId
+ + ", mLimitAdTrackingEnabled="
+ + mLimitAdTrackingEnabled
+ + '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+
+ if (!(o instanceof GetAdIdResult)) {
+ return false;
+ }
+
+ GetAdIdResult that = (GetAdIdResult) o;
+
+ return mStatusCode == that.mStatusCode
+ && Objects.equals(mErrorMessage, that.mErrorMessage)
+ && Objects.equals(mAdId, that.mAdId)
+ && (mLimitAdTrackingEnabled == that.mLimitAdTrackingEnabled);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mStatusCode, mErrorMessage, mAdId, mLimitAdTrackingEnabled);
+ }
+
+ /**
+ * Builder for {@link GetAdIdResult} objects.
+ *
+ * @hide
+ */
+ public static final class Builder {
+ private @AdServicesStatusUtils.StatusCode int mStatusCode;
+ @Nullable private String mErrorMessage;
+ @NonNull private String mAdId;
+ private boolean mLimitAdTrackingEnabled;
+
+ public Builder() {}
+
+ /** Set the Result Code. */
+ public @NonNull Builder setStatusCode(@AdServicesStatusUtils.StatusCode int statusCode) {
+ mStatusCode = statusCode;
+ return this;
+ }
+
+ /** Set the Error Message. */
+ public @NonNull Builder setErrorMessage(@Nullable String errorMessage) {
+ mErrorMessage = errorMessage;
+ return this;
+ }
+
+ /** Set the adid. */
+ public @NonNull Builder setAdId(@NonNull String adId) {
+ mAdId = adId;
+ return this;
+ }
+
+ /** Set the Limited AdTracking enabled field. */
+ public @NonNull Builder setLatEnabled(boolean isLimitAdTrackingEnabled) {
+ mLimitAdTrackingEnabled = isLimitAdTrackingEnabled;
+ return this;
+ }
+
+ /** Builds a {@link GetAdIdResult} instance. */
+ public @NonNull GetAdIdResult build() {
+ if (mAdId == null) {
+ throw new IllegalArgumentException("adId is null");
+ }
+
+ return new GetAdIdResult(mStatusCode, mErrorMessage, mAdId, mLimitAdTrackingEnabled);
+ }
+ }
+}
diff --git a/android-35/android/adservices/adselection/AdSelectionConfig.java b/android-35/android/adservices/adselection/AdSelectionConfig.java
new file mode 100644
index 0000000..3aec741
--- /dev/null
+++ b/android-35/android/adservices/adselection/AdSelectionConfig.java
@@ -0,0 +1,457 @@
+/*
+ * 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.adselection;
+
+import android.adservices.common.AdSelectionSignals;
+import android.adservices.common.AdTechIdentifier;
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.adservices.AdServicesParcelableUtil;
+import com.android.adservices.flags.Flags;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Contains the configuration of the ad selection process.
+ *
+ * <p>Instances of this class are created by SDKs to be provided as arguments to the {@link
+ * AdSelectionManager#selectAds} and {@link AdSelectionManager#reportImpression} methods in {@link
+ * AdSelectionManager}.
+ */
+// TODO(b/233280314): investigate on adSelectionConfig optimization by merging mCustomAudienceBuyers
+// and mPerBuyerSignals.
+public final class AdSelectionConfig implements Parcelable {
+ /**
+ * {@link AdSelectionConfig} with empty values for each field.
+ *
+ * @hide
+ */
+ @NonNull public static final AdSelectionConfig EMPTY = new AdSelectionConfig();
+
+ @NonNull private final AdTechIdentifier mSeller;
+ @NonNull private final Uri mDecisionLogicUri;
+ @NonNull private final List<AdTechIdentifier> mCustomAudienceBuyers;
+ @NonNull private final AdSelectionSignals mAdSelectionSignals;
+ @NonNull private final AdSelectionSignals mSellerSignals;
+ @NonNull private final Map<AdTechIdentifier, AdSelectionSignals> mPerBuyerSignals;
+ @NonNull private final Map<AdTechIdentifier, SignedContextualAds> mBuyerSignedContextualAds;
+ @NonNull private final Uri mTrustedScoringSignalsUri;
+
+ @NonNull
+ public static final Creator<AdSelectionConfig> CREATOR =
+ new Creator<AdSelectionConfig>() {
+ @Override
+ public AdSelectionConfig createFromParcel(@NonNull Parcel in) {
+ Objects.requireNonNull(in);
+ return new AdSelectionConfig(in);
+ }
+
+ @Override
+ public AdSelectionConfig[] newArray(int size) {
+ return new AdSelectionConfig[size];
+ }
+ };
+
+ private AdSelectionConfig() {
+ this.mSeller = AdTechIdentifier.fromString("");
+ this.mDecisionLogicUri = Uri.EMPTY;
+ this.mCustomAudienceBuyers = Collections.emptyList();
+ this.mAdSelectionSignals = AdSelectionSignals.EMPTY;
+ this.mSellerSignals = AdSelectionSignals.EMPTY;
+ this.mPerBuyerSignals = Collections.emptyMap();
+ this.mBuyerSignedContextualAds = Collections.emptyMap();
+ this.mTrustedScoringSignalsUri = Uri.EMPTY;
+ }
+
+ private AdSelectionConfig(
+ @NonNull AdTechIdentifier seller,
+ @NonNull Uri decisionLogicUri,
+ @NonNull List<AdTechIdentifier> customAudienceBuyers,
+ @NonNull AdSelectionSignals adSelectionSignals,
+ @NonNull AdSelectionSignals sellerSignals,
+ @NonNull Map<AdTechIdentifier, AdSelectionSignals> perBuyerSignals,
+ @NonNull Map<AdTechIdentifier, SignedContextualAds> perBuyerSignedContextualAds,
+ @NonNull Uri trustedScoringSignalsUri) {
+ this.mSeller = seller;
+ this.mDecisionLogicUri = decisionLogicUri;
+ this.mCustomAudienceBuyers = customAudienceBuyers;
+ this.mAdSelectionSignals = adSelectionSignals;
+ this.mSellerSignals = sellerSignals;
+ this.mPerBuyerSignals = perBuyerSignals;
+ this.mBuyerSignedContextualAds = perBuyerSignedContextualAds;
+ this.mTrustedScoringSignalsUri = trustedScoringSignalsUri;
+ }
+
+ private AdSelectionConfig(@NonNull Parcel in) {
+ Objects.requireNonNull(in);
+ mSeller = AdTechIdentifier.CREATOR.createFromParcel(in);
+ mDecisionLogicUri = Uri.CREATOR.createFromParcel(in);
+ mCustomAudienceBuyers = in.createTypedArrayList(AdTechIdentifier.CREATOR);
+ mAdSelectionSignals = AdSelectionSignals.CREATOR.createFromParcel(in);
+ mSellerSignals = AdSelectionSignals.CREATOR.createFromParcel(in);
+ mPerBuyerSignals =
+ AdServicesParcelableUtil.readMapFromParcel(
+ in, AdTechIdentifier::fromString, AdSelectionSignals.class);
+ mBuyerSignedContextualAds =
+ AdServicesParcelableUtil.readMapFromParcel(
+ in, AdTechIdentifier::fromString, SignedContextualAds.class);
+ mTrustedScoringSignalsUri = Uri.CREATOR.createFromParcel(in);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ Objects.requireNonNull(dest);
+
+ mSeller.writeToParcel(dest, flags);
+ mDecisionLogicUri.writeToParcel(dest, flags);
+ dest.writeTypedList(mCustomAudienceBuyers);
+ mAdSelectionSignals.writeToParcel(dest, flags);
+ mSellerSignals.writeToParcel(dest, flags);
+ AdServicesParcelableUtil.writeMapToParcel(dest, mPerBuyerSignals);
+ AdServicesParcelableUtil.writeMapToParcel(dest, mBuyerSignedContextualAds);
+ mTrustedScoringSignalsUri.writeToParcel(dest, flags);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof AdSelectionConfig)) return false;
+ AdSelectionConfig that = (AdSelectionConfig) o;
+ return Objects.equals(mSeller, that.mSeller)
+ && Objects.equals(mDecisionLogicUri, that.mDecisionLogicUri)
+ && Objects.equals(mCustomAudienceBuyers, that.mCustomAudienceBuyers)
+ && Objects.equals(mAdSelectionSignals, that.mAdSelectionSignals)
+ && Objects.equals(mSellerSignals, that.mSellerSignals)
+ && Objects.equals(mPerBuyerSignals, that.mPerBuyerSignals)
+ && Objects.equals(mBuyerSignedContextualAds, that.mBuyerSignedContextualAds)
+ && Objects.equals(mTrustedScoringSignalsUri, that.mTrustedScoringSignalsUri);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(
+ mSeller,
+ mDecisionLogicUri,
+ mCustomAudienceBuyers,
+ mAdSelectionSignals,
+ mSellerSignals,
+ mPerBuyerSignals,
+ mBuyerSignedContextualAds,
+ mTrustedScoringSignalsUri);
+ }
+
+ /**
+ * @return a new builder instance created from this object's cloned data
+ * @hide
+ */
+ @NonNull
+ public AdSelectionConfig.Builder cloneToBuilder() {
+ return new AdSelectionConfig.Builder()
+ .setSeller(this.getSeller())
+ .setPerBuyerSignedContextualAds(this.getPerBuyerSignedContextualAds())
+ .setAdSelectionSignals(this.getAdSelectionSignals())
+ .setCustomAudienceBuyers(this.getCustomAudienceBuyers())
+ .setDecisionLogicUri(this.getDecisionLogicUri())
+ .setPerBuyerSignals(this.getPerBuyerSignals())
+ .setSellerSignals(this.getSellerSignals())
+ .setTrustedScoringSignalsUri(this.getTrustedScoringSignalsUri());
+ }
+
+ /** @return a AdTechIdentifier of the seller, for example "www.example-ssp.com" */
+ @NonNull
+ public AdTechIdentifier getSeller() {
+ return mSeller;
+ }
+
+ /**
+ * @return the URI used to retrieve the JS code containing the seller/SSP scoreAd function used
+ * during the ad selection and reporting processes
+ */
+ @NonNull
+ public Uri getDecisionLogicUri() {
+ return mDecisionLogicUri;
+ }
+
+ /**
+ * @return a list of custom audience buyers allowed by the SSP to participate in the ad
+ * selection process
+ */
+ @NonNull
+ public List<AdTechIdentifier> getCustomAudienceBuyers() {
+ return new ArrayList<>(mCustomAudienceBuyers);
+ }
+
+
+ /**
+ * @return JSON in an AdSelectionSignals object, fetched from the AdSelectionConfig and consumed
+ * by the JS logic fetched from the DSP, represents signals given to the participating
+ * buyers in the ad selection and reporting processes.
+ */
+ @NonNull
+ public AdSelectionSignals getAdSelectionSignals() {
+ return mAdSelectionSignals;
+ }
+
+ /**
+ * @return JSON in an AdSelectionSignals object, provided by the SSP and consumed by the JS
+ * logic fetched from the SSP, represents any information that the SSP used in the ad
+ * scoring process to tweak the results of the ad selection process (e.g. brand safety
+ * checks, excluded contextual ads).
+ */
+ @NonNull
+ public AdSelectionSignals getSellerSignals() {
+ return mSellerSignals;
+ }
+
+ /**
+ * @return a Map of buyers and AdSelectionSignals, fetched from the AdSelectionConfig and
+ * consumed by the JS logic fetched from the DSP, representing any information that each
+ * buyer would provide during ad selection to participants (such as bid floor, ad selection
+ * type, etc.)
+ */
+ @NonNull
+ public Map<AdTechIdentifier, AdSelectionSignals> getPerBuyerSignals() {
+ return new HashMap<>(mPerBuyerSignals);
+ }
+
+
+ /**
+ * @return a Map of buyers and corresponding Contextual Ads, these ads are expected to be
+ * pre-downloaded from the contextual path and injected into Ad Selection.
+ */
+ @FlaggedApi(Flags.FLAG_FLEDGE_AD_SELECTION_FILTERING_ENABLED)
+ @NonNull
+ public Map<AdTechIdentifier, SignedContextualAds> getPerBuyerSignedContextualAds() {
+ return new HashMap<>(mBuyerSignedContextualAds);
+ }
+
+ /**
+ * @return URI endpoint of sell-side trusted signal from which creative specific realtime
+ * information can be fetched from.
+ */
+ @NonNull
+ public Uri getTrustedScoringSignalsUri() {
+ return mTrustedScoringSignalsUri;
+ }
+
+ /** Builder for {@link AdSelectionConfig} object. */
+ public static final class Builder {
+ private AdTechIdentifier mSeller;
+ private Uri mDecisionLogicUri;
+ private List<AdTechIdentifier> mCustomAudienceBuyers;
+ private AdSelectionSignals mAdSelectionSignals = AdSelectionSignals.EMPTY;
+ private AdSelectionSignals mSellerSignals = AdSelectionSignals.EMPTY;
+ private Map<AdTechIdentifier, AdSelectionSignals> mPerBuyerSignals = Collections.emptyMap();
+ private Map<AdTechIdentifier, SignedContextualAds> mBuyerSignedContextualAds =
+ Collections.emptyMap();
+ private Uri mTrustedScoringSignalsUri;
+
+ public Builder() {}
+
+ /**
+ * Sets the seller identifier.
+ *
+ * <p>See {@link #getSeller()} for more details.
+ */
+ @NonNull
+ public AdSelectionConfig.Builder setSeller(@NonNull AdTechIdentifier seller) {
+ Objects.requireNonNull(seller);
+
+ this.mSeller = seller;
+ return this;
+ }
+
+ /**
+ * Sets the URI used to fetch decision logic for use in the ad selection process. Decision
+ * URI could be either of the two schemas:
+ *
+ * <ul>
+ * <li><b>HTTPS:</b> HTTPS URIs have to be absolute URIs where the host matches the {@code
+ * seller}
+ * <li><b>Ad Selection Prebuilt:</b> Ad Selection Service URIs follow {@code
+ * ad-selection-prebuilt://ad-selection/<name>?<script-generation-parameters>} format.
+ * FLEDGE generates the appropriate JS script without the need for a network call.
+ * <p>Available prebuilt scripts:
+ * <ul>
+ * <li><b>{@code highest-bid-wins} for {@code scoreAds} and {@code
+ * reportResult}:</b> This JS picks the ad with the highest bid for scoring. For
+ * reporting, the given URI is parameterized with {@code render_uri} and {@code
+ * bid}. Below parameter(s) are required to use this prebuilt:
+ * <ul>
+ * <li><b>{@code reportingUrl}:</b> Base reporting uri that will be
+ * parameterized later with {@code render_uri} and {@code bid}
+ * </ul>
+ * <p>Ex. If your base reporting URL is "https://www.ssp.com" then, {@code
+ * ad-selection-prebuilt://ad-selection/highest-bid-wins/?reportingUrl=https://www.ssp.com}
+ * </ul>
+ * </ul>
+ *
+ * <p>See {@link #getDecisionLogicUri()} for more details.
+ */
+ @NonNull
+ public AdSelectionConfig.Builder setDecisionLogicUri(@NonNull Uri decisionLogicUri) {
+ Objects.requireNonNull(decisionLogicUri);
+
+ this.mDecisionLogicUri = decisionLogicUri;
+ return this;
+ }
+
+ /**
+ * Sets the list of allowed buyers.
+ *
+ * <p>See {@link #getCustomAudienceBuyers()} for more details.
+ */
+ @NonNull
+ public AdSelectionConfig.Builder setCustomAudienceBuyers(
+ @NonNull List<AdTechIdentifier> customAudienceBuyers) {
+ Objects.requireNonNull(customAudienceBuyers);
+
+ this.mCustomAudienceBuyers = customAudienceBuyers;
+ return this;
+ }
+
+ /**
+ * Sets the signals provided to buyers during ad selection bid generation.
+ *
+ * <p>If not set, defaults to the empty JSON.
+ *
+ * <p>See {@link #getAdSelectionSignals()} for more details.
+ */
+ @NonNull
+ public AdSelectionConfig.Builder setAdSelectionSignals(
+ @NonNull AdSelectionSignals adSelectionSignals) {
+ Objects.requireNonNull(adSelectionSignals);
+
+ this.mAdSelectionSignals = adSelectionSignals;
+ return this;
+ }
+
+ /**
+ * Set the signals used to modify ad selection results.
+ *
+ * <p>If not set, defaults to the empty JSON.
+ *
+ * <p>See {@link #getSellerSignals()} for more details.
+ */
+ @NonNull
+ public AdSelectionConfig.Builder setSellerSignals(
+ @NonNull AdSelectionSignals sellerSignals) {
+ Objects.requireNonNull(sellerSignals);
+
+ this.mSellerSignals = sellerSignals;
+ return this;
+ }
+
+ /**
+ * Sets the signals provided by each buyer during ad selection.
+ *
+ * <p>If not set, defaults to an empty map.
+ *
+ * <p>See {@link #getPerBuyerSignals()} for more details.
+ */
+ @NonNull
+ public AdSelectionConfig.Builder setPerBuyerSignals(
+ @NonNull Map<AdTechIdentifier, AdSelectionSignals> perBuyerSignals) {
+ Objects.requireNonNull(perBuyerSignals);
+
+ this.mPerBuyerSignals = perBuyerSignals;
+ return this;
+ }
+
+ /**
+ * Sets the contextual Ads corresponding to each buyer during ad selection.
+ *
+ * <p>If not set, defaults to an empty map.
+ *
+ * <p>See {@link #getPerBuyerSignedContextualAds()} for more details.
+ */
+ @FlaggedApi(Flags.FLAG_FLEDGE_AD_SELECTION_FILTERING_ENABLED)
+ @NonNull
+ public AdSelectionConfig.Builder setPerBuyerSignedContextualAds(
+ @NonNull Map<AdTechIdentifier, SignedContextualAds> buyerSignedContextualAds) {
+ Objects.requireNonNull(buyerSignedContextualAds);
+
+ this.mBuyerSignedContextualAds = buyerSignedContextualAds;
+ return this;
+ }
+
+ /**
+ * Sets the URI endpoint of sell-side trusted signal from which creative specific realtime
+ * information can be fetched from.
+ *
+ * <p>If {@link Uri#EMPTY} is passed then network call will be skipped and {@link
+ * AdSelectionSignals#EMPTY} will be passed to ad selection.
+ *
+ * <p>See {@link #getTrustedScoringSignalsUri()} for more details.
+ */
+ @NonNull
+ public AdSelectionConfig.Builder setTrustedScoringSignalsUri(
+ @NonNull Uri trustedScoringSignalsUri) {
+ Objects.requireNonNull(trustedScoringSignalsUri);
+
+ this.mTrustedScoringSignalsUri = trustedScoringSignalsUri;
+ return this;
+ }
+
+ /**
+ * Builds an {@link AdSelectionConfig} instance.
+ *
+ * @throws NullPointerException if any required params are null
+ */
+ @NonNull
+ public AdSelectionConfig build() {
+ Objects.requireNonNull(mSeller, "The seller has not been provided");
+ Objects.requireNonNull(
+ mDecisionLogicUri, "The decision logic URI has not been provided");
+ Objects.requireNonNull(
+ mCustomAudienceBuyers, "The custom audience buyers have not been provided");
+ Objects.requireNonNull(
+ mAdSelectionSignals, "The ad selection signals have not been provided");
+ Objects.requireNonNull(mSellerSignals, "The seller signals have not been provided");
+ Objects.requireNonNull(
+ mPerBuyerSignals, "The per buyer signals have not been provided");
+ Objects.requireNonNull(
+ mBuyerSignedContextualAds,
+ "The buyer signed contextual ads have not been provided");
+ Objects.requireNonNull(
+ mTrustedScoringSignalsUri,
+ "The trusted scoring signals URI have not been provided");
+ return new AdSelectionConfig(
+ mSeller,
+ mDecisionLogicUri,
+ mCustomAudienceBuyers,
+ mAdSelectionSignals,
+ mSellerSignals,
+ mPerBuyerSignals,
+ mBuyerSignedContextualAds,
+ mTrustedScoringSignalsUri);
+ }
+ }
+}
diff --git a/android-35/android/adservices/adselection/AdSelectionFromOutcomesConfig.java b/android-35/android/adservices/adselection/AdSelectionFromOutcomesConfig.java
new file mode 100644
index 0000000..531b3cf
--- /dev/null
+++ b/android-35/android/adservices/adselection/AdSelectionFromOutcomesConfig.java
@@ -0,0 +1,245 @@
+/*
+ * 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.adselection;
+
+import android.adservices.common.AdSelectionSignals;
+import android.adservices.common.AdTechIdentifier;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Contains the configuration of the ad selection process that select a winner from a given list of
+ * ad selection ids.
+ *
+ * <p>Instances of this class are created by SDKs to be provided as arguments to the {@link
+ * AdSelectionManager#selectAds} methods in {@link AdSelectionManager}.
+ */
+public final class AdSelectionFromOutcomesConfig implements Parcelable {
+ @NonNull private final AdTechIdentifier mSeller;
+ @NonNull private final List<Long> mAdSelectionIds;
+ @NonNull private final AdSelectionSignals mSelectionSignals;
+ @NonNull private final Uri mSelectionLogicUri;
+
+ @NonNull
+ public static final Creator<AdSelectionFromOutcomesConfig> CREATOR =
+ new Creator<AdSelectionFromOutcomesConfig>() {
+ @Override
+ public AdSelectionFromOutcomesConfig createFromParcel(@NonNull Parcel in) {
+ return new AdSelectionFromOutcomesConfig(in);
+ }
+
+ @Override
+ public AdSelectionFromOutcomesConfig[] newArray(int size) {
+ return new AdSelectionFromOutcomesConfig[size];
+ }
+ };
+
+ private AdSelectionFromOutcomesConfig(
+ @NonNull AdTechIdentifier seller,
+ @NonNull List<Long> adSelectionIds,
+ @NonNull AdSelectionSignals selectionSignals,
+ @NonNull Uri selectionLogicUri) {
+ Objects.requireNonNull(seller);
+ Objects.requireNonNull(adSelectionIds);
+ Objects.requireNonNull(selectionSignals);
+ Objects.requireNonNull(selectionLogicUri);
+
+ this.mSeller = seller;
+ this.mAdSelectionIds = adSelectionIds;
+ this.mSelectionSignals = selectionSignals;
+ this.mSelectionLogicUri = selectionLogicUri;
+ }
+
+ private AdSelectionFromOutcomesConfig(@NonNull Parcel in) {
+ Objects.requireNonNull(in);
+
+ this.mSeller = AdTechIdentifier.CREATOR.createFromParcel(in);
+ this.mAdSelectionIds =
+ Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU
+ ? in.readArrayList(Long.class.getClassLoader())
+ : in.readArrayList(Long.class.getClassLoader(), Long.class);
+ this.mSelectionSignals = AdSelectionSignals.CREATOR.createFromParcel(in);
+ this.mSelectionLogicUri = Uri.CREATOR.createFromParcel(in);
+ }
+
+ /** @return a AdTechIdentifier of the seller, for example "www.example-ssp.com" */
+ @NonNull
+ public AdTechIdentifier getSeller() {
+ return mSeller;
+ }
+
+ /**
+ * @return a list of ad selection ids passed by the SSP to participate in the ad selection from
+ * outcomes process
+ */
+ @NonNull
+ public List<Long> getAdSelectionIds() {
+ return mAdSelectionIds;
+ }
+
+ /**
+ * @return JSON in an {@link AdSelectionSignals} object, fetched from the {@link
+ * AdSelectionFromOutcomesConfig} and consumed by the JS logic fetched from the DSP {@code
+ * SelectionLogicUri}.
+ */
+ @NonNull
+ public AdSelectionSignals getSelectionSignals() {
+ return mSelectionSignals;
+ }
+
+ /**
+ * @return the URI used to retrieve the JS code containing the seller/SSP {@code selectOutcome}
+ * function used during the ad selection
+ */
+ @NonNull
+ public Uri getSelectionLogicUri() {
+ return mSelectionLogicUri;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ Objects.requireNonNull(dest);
+
+ mSeller.writeToParcel(dest, flags);
+ dest.writeList(mAdSelectionIds);
+ mSelectionSignals.writeToParcel(dest, flags);
+ mSelectionLogicUri.writeToParcel(dest, flags);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof AdSelectionFromOutcomesConfig)) return false;
+ AdSelectionFromOutcomesConfig that = (AdSelectionFromOutcomesConfig) o;
+ return Objects.equals(this.mSeller, that.mSeller)
+ && Objects.equals(this.mAdSelectionIds, that.mAdSelectionIds)
+ && Objects.equals(this.mSelectionSignals, that.mSelectionSignals)
+ && Objects.equals(this.mSelectionLogicUri, that.mSelectionLogicUri);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mSeller, mAdSelectionIds, mSelectionSignals, mSelectionLogicUri);
+ }
+
+ /**
+ * Builder for {@link AdSelectionFromOutcomesConfig} objects. All fields require non-null values
+ * to build.
+ */
+ public static final class Builder {
+ @Nullable private AdTechIdentifier mSeller;
+ @Nullable private List<Long> mAdSelectionIds;
+ @Nullable private AdSelectionSignals mSelectionSignals;
+ @Nullable private Uri mSelectionLogicUri;
+
+ public Builder() {}
+
+ /** Sets the seller {@link AdTechIdentifier}. */
+ @NonNull
+ public AdSelectionFromOutcomesConfig.Builder setSeller(@NonNull AdTechIdentifier seller) {
+ Objects.requireNonNull(seller);
+
+ this.mSeller = seller;
+ return this;
+ }
+
+ /** Sets the list of {@code AdSelectionIds} to participate in the selection process. */
+ @NonNull
+ public AdSelectionFromOutcomesConfig.Builder setAdSelectionIds(
+ @NonNull List<Long> adSelectionIds) {
+ Objects.requireNonNull(adSelectionIds);
+
+ this.mAdSelectionIds = adSelectionIds;
+ return this;
+ }
+
+ /**
+ * Sets the {@code SelectionSignals} to be consumed by the JS script downloaded from {@code
+ * SelectionLogicUri}
+ */
+ @NonNull
+ public AdSelectionFromOutcomesConfig.Builder setSelectionSignals(
+ @NonNull AdSelectionSignals selectionSignals) {
+ Objects.requireNonNull(selectionSignals);
+
+ this.mSelectionSignals = selectionSignals;
+ return this;
+ }
+
+ /**
+ * Sets the {@code SelectionLogicUri}. Selection URI could be either of the two schemas:
+ *
+ * <ul>
+ * <li><b>HTTPS:</b> HTTPS URIs have to be absolute URIs where the host matches the {@code
+ * seller}
+ * <li><b>Ad Selection Prebuilt:</b> Ad Selection Service URIs follow {@code
+ * ad-selection-prebuilt://ad-selection-from-outcomes/<name>?<script-generation-parameters>}
+ * format. FLEDGE generates the appropriate JS script without the need for a network
+ * call.
+ * <p>Available prebuilt scripts:
+ * <ul>
+ * <li><b>{@code waterfall-mediation-truncation} for {@code selectOutcome}:</b> This
+ * JS implements Waterfall mediation truncation logic. Mediation SDK's ad is
+ * returned if its bid greater than or equal to the bid floor. Below
+ * parameter(s) are required to use this prebuilt:
+ * <ul>
+ * <li><b>{@code bidFloor}:</b> Key of the bid floor value passed in the
+ * {@link AdSelectionFromOutcomesConfig#getSelectionSignals()} that will
+ * be compared against mediation SDK's winner ad.
+ * </ul>
+ * <p>Ex. If your selection signals look like {@code {"bid_floor": 10}} then,
+ * {@code
+ * ad-selection-prebuilt://ad-selection-from-outcomes/waterfall-mediation-truncation/?bidFloor=bid_floor}
+ * </ul>
+ * </ul>
+ *
+ * {@code AdSelectionIds} and {@code SelectionSignals}.
+ */
+ @NonNull
+ public AdSelectionFromOutcomesConfig.Builder setSelectionLogicUri(
+ @NonNull Uri selectionLogicUri) {
+ Objects.requireNonNull(selectionLogicUri);
+
+ this.mSelectionLogicUri = selectionLogicUri;
+ return this;
+ }
+
+ /** Builds a {@link AdSelectionFromOutcomesConfig} instance. */
+ @NonNull
+ public AdSelectionFromOutcomesConfig build() {
+ Objects.requireNonNull(mSeller);
+ Objects.requireNonNull(mAdSelectionIds);
+ Objects.requireNonNull(mSelectionSignals);
+ Objects.requireNonNull(mSelectionLogicUri);
+
+ return new AdSelectionFromOutcomesConfig(
+ mSeller, mAdSelectionIds, mSelectionSignals, mSelectionLogicUri);
+ }
+ }
+}
diff --git a/android-35/android/adservices/adselection/AdSelectionFromOutcomesInput.java b/android-35/android/adservices/adselection/AdSelectionFromOutcomesInput.java
new file mode 100644
index 0000000..8e72a07
--- /dev/null
+++ b/android-35/android/adservices/adselection/AdSelectionFromOutcomesInput.java
@@ -0,0 +1,163 @@
+/*
+ * 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.adselection;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Represents input parameters to the {@link
+ * com.android.adservices.service.adselection.AdSelectionServiceImpl#selectAdsFromOutcomes} API.
+ *
+ * @hide
+ */
+public final class AdSelectionFromOutcomesInput implements Parcelable {
+ @NonNull private final AdSelectionFromOutcomesConfig mAdSelectionFromOutcomesConfig;
+ @NonNull private final String mCallerPackageName;
+
+ @NonNull
+ public static final Creator<AdSelectionFromOutcomesInput> CREATOR =
+ new Creator<AdSelectionFromOutcomesInput>() {
+ @Override
+ public AdSelectionFromOutcomesInput createFromParcel(@NonNull Parcel source) {
+ return new AdSelectionFromOutcomesInput(source);
+ }
+
+ @Override
+ public AdSelectionFromOutcomesInput[] newArray(int size) {
+ return new AdSelectionFromOutcomesInput[size];
+ }
+ };
+
+ private AdSelectionFromOutcomesInput(
+ @NonNull AdSelectionFromOutcomesConfig adSelectionFromOutcomesConfig,
+ @NonNull String callerPackageName) {
+ Objects.requireNonNull(adSelectionFromOutcomesConfig);
+ Objects.requireNonNull(callerPackageName);
+
+ this.mAdSelectionFromOutcomesConfig = adSelectionFromOutcomesConfig;
+ this.mCallerPackageName = callerPackageName;
+ }
+
+ private AdSelectionFromOutcomesInput(@NonNull Parcel in) {
+ Objects.requireNonNull(in);
+
+ this.mAdSelectionFromOutcomesConfig =
+ AdSelectionFromOutcomesConfig.CREATOR.createFromParcel(in);
+ this.mCallerPackageName = in.readString();
+ }
+
+ @NonNull
+ public AdSelectionFromOutcomesConfig getAdSelectionFromOutcomesConfig() {
+ return mAdSelectionFromOutcomesConfig;
+ }
+
+ @NonNull
+ public String getCallerPackageName() {
+ return mCallerPackageName;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof AdSelectionFromOutcomesInput)) return false;
+ AdSelectionFromOutcomesInput that = (AdSelectionFromOutcomesInput) o;
+ return Objects.equals(
+ this.mAdSelectionFromOutcomesConfig, that.mAdSelectionFromOutcomesConfig)
+ && Objects.equals(this.mCallerPackageName, that.mCallerPackageName);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mAdSelectionFromOutcomesConfig, mCallerPackageName);
+ }
+
+ /**
+ * Describe the kinds of special objects contained in this Parcelable instance's marshaled
+ * representation. For example, if the object will include a file descriptor in the output of
+ * {@link #writeToParcel(Parcel, int)}, the return value of this method must include the {@link
+ * #CONTENTS_FILE_DESCRIPTOR} bit.
+ *
+ * @return a bitmask indicating the set of special object types marshaled by this Parcelable
+ * object instance.
+ */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Flatten this object in to a Parcel.
+ *
+ * @param dest The Parcel in which the object should be written.
+ * @param flags Additional flags about how the object should be written. May be 0 or {@link
+ * #PARCELABLE_WRITE_RETURN_VALUE}.
+ */
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ Objects.requireNonNull(dest);
+
+ mAdSelectionFromOutcomesConfig.writeToParcel(dest, flags);
+ dest.writeString(mCallerPackageName);
+ }
+
+ /**
+ * Builder for {@link AdSelectionFromOutcomesInput} objects.
+ *
+ * @hide
+ */
+ public static final class Builder {
+ @Nullable private AdSelectionFromOutcomesConfig mAdSelectionFromOutcomesConfig;
+ @Nullable private String mCallerPackageName;
+
+ public Builder() {}
+
+ /** Sets the {@link AdSelectionFromOutcomesConfig}. */
+ @NonNull
+ public AdSelectionFromOutcomesInput.Builder setAdSelectionFromOutcomesConfig(
+ @NonNull AdSelectionFromOutcomesConfig adSelectionFromOutcomesConfig) {
+ Objects.requireNonNull(adSelectionFromOutcomesConfig);
+
+ this.mAdSelectionFromOutcomesConfig = adSelectionFromOutcomesConfig;
+ return this;
+ }
+
+ /** Sets the caller's package name. */
+ @NonNull
+ public AdSelectionFromOutcomesInput.Builder setCallerPackageName(
+ @NonNull String callerPackageName) {
+ Objects.requireNonNull(callerPackageName);
+
+ this.mCallerPackageName = callerPackageName;
+ return this;
+ }
+
+ /** Builds a {@link AdSelectionFromOutcomesInput} instance. */
+ @NonNull
+ public AdSelectionFromOutcomesInput build() {
+ Objects.requireNonNull(mAdSelectionFromOutcomesConfig);
+ Objects.requireNonNull(mCallerPackageName);
+
+ return new AdSelectionFromOutcomesInput(
+ mAdSelectionFromOutcomesConfig, mCallerPackageName);
+ }
+ }
+}
diff --git a/android-35/android/adservices/adselection/AdSelectionInput.java b/android-35/android/adservices/adselection/AdSelectionInput.java
new file mode 100644
index 0000000..b926f58
--- /dev/null
+++ b/android-35/android/adservices/adselection/AdSelectionInput.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.adselection;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Represent input params to the RunAdSelectionInput API.
+ *
+ * @hide
+ */
+public final class AdSelectionInput implements Parcelable {
+ @Nullable private final AdSelectionConfig mAdSelectionConfig;
+ @Nullable private final String mCallerPackageName;
+
+ @NonNull
+ public static final Creator<AdSelectionInput> CREATOR =
+ new Creator<AdSelectionInput>() {
+ public AdSelectionInput createFromParcel(Parcel in) {
+ return new AdSelectionInput(in);
+ }
+
+ public AdSelectionInput[] newArray(int size) {
+ return new AdSelectionInput[size];
+ }
+ };
+
+ private AdSelectionInput(
+ @NonNull AdSelectionConfig adSelectionConfig, @NonNull String callerPackageName) {
+ Objects.requireNonNull(adSelectionConfig);
+
+ this.mAdSelectionConfig = adSelectionConfig;
+ this.mCallerPackageName = callerPackageName;
+ }
+
+ private AdSelectionInput(@NonNull Parcel in) {
+ Objects.requireNonNull(in);
+
+ this.mAdSelectionConfig = AdSelectionConfig.CREATOR.createFromParcel(in);
+ this.mCallerPackageName = in.readString();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ Objects.requireNonNull(dest);
+
+ mAdSelectionConfig.writeToParcel(dest, flags);
+ dest.writeString(mCallerPackageName);
+ }
+
+ /**
+ * Returns the adSelectionConfig, one of the inputs to {@link AdSelectionInput} as noted in
+ * {@code AdSelectionService}.
+ */
+ @NonNull
+ public AdSelectionConfig getAdSelectionConfig() {
+ return mAdSelectionConfig;
+ }
+
+ /** @return the caller package name */
+ @NonNull
+ public String getCallerPackageName() {
+ return mCallerPackageName;
+ }
+
+ /**
+ * Builder for {@link AdSelectionInput} objects.
+ *
+ * @hide
+ */
+ public static final class Builder {
+ @Nullable private AdSelectionConfig mAdSelectionConfig;
+ @Nullable private String mCallerPackageName;
+
+ public Builder() {}
+
+ /** Set the AdSelectionConfig. */
+ @NonNull
+ public AdSelectionInput.Builder setAdSelectionConfig(
+ @NonNull AdSelectionConfig adSelectionConfig) {
+ Objects.requireNonNull(adSelectionConfig);
+
+ this.mAdSelectionConfig = adSelectionConfig;
+ return this;
+ }
+
+ /** Sets the caller's package name. */
+ @NonNull
+ public AdSelectionInput.Builder setCallerPackageName(@NonNull String callerPackageName) {
+ Objects.requireNonNull(callerPackageName);
+
+ this.mCallerPackageName = callerPackageName;
+ return this;
+ }
+
+ /** Builds a {@link AdSelectionInput} instance. */
+ @NonNull
+ public AdSelectionInput build() {
+ Objects.requireNonNull(mAdSelectionConfig);
+ Objects.requireNonNull(mCallerPackageName);
+
+ return new AdSelectionInput(mAdSelectionConfig, mCallerPackageName);
+ }
+ }
+}
diff --git a/android-35/android/adservices/adselection/AdSelectionManager.java b/android-35/android/adservices/adselection/AdSelectionManager.java
new file mode 100644
index 0000000..19b0b83
--- /dev/null
+++ b/android-35/android/adservices/adselection/AdSelectionManager.java
@@ -0,0 +1,1027 @@
+/*
+ * 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.adselection;
+
+import static android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE;
+import static android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_AD_SELECTION;
+import static android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_PROTECTED_SIGNALS;
+
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+import android.adservices.adid.AdId;
+import android.adservices.adid.AdIdCompatibleManager;
+import android.adservices.common.AdServicesStatusUtils;
+import android.adservices.common.AssetFileDescriptorUtil;
+import android.adservices.common.CallerMetadata;
+import android.adservices.common.FledgeErrorResponse;
+import android.adservices.common.SandboxedSdkContextUtils;
+import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
+import android.app.sdksandbox.SandboxedSdkContext;
+import android.content.Context;
+import android.content.res.AssetFileDescriptor;
+import android.os.Build;
+import android.os.LimitExceededException;
+import android.os.OutcomeReceiver;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.TransactionTooLargeException;
+
+import androidx.annotation.RequiresApi;
+
+import com.android.adservices.AdServicesCommon;
+import com.android.adservices.LoggerFactory;
+import com.android.adservices.ServiceBinder;
+import com.android.adservices.flags.Flags;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.IOException;
+import java.util.Objects;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * AdSelection Manager provides APIs for app and ad-SDKs to run ad selection processes as well as
+ * report impressions.
+ */
+@RequiresApi(Build.VERSION_CODES.S)
+public class AdSelectionManager {
+ private static final LoggerFactory.Logger sLogger = LoggerFactory.getFledgeLogger();
+
+ /**
+ * Constant that represents the service name for {@link AdSelectionManager} to be used in {@link
+ * android.adservices.AdServicesFrameworkInitializer#registerServiceWrappers}
+ *
+ * @hide
+ */
+ public static final String AD_SELECTION_SERVICE = "ad_selection_service";
+
+ private static final long AD_ID_TIMEOUT_MS = 400;
+ private static final String DEBUG_API_WARNING_MESSAGE =
+ "To enable debug api, include ACCESS_ADSERVICES_AD_ID "
+ + "permission and enable advertising ID under device settings";
+ private final Executor mAdIdExecutor = Executors.newCachedThreadPool();
+ @NonNull private Context mContext;
+ @NonNull private ServiceBinder<AdSelectionService> mServiceBinder;
+ @NonNull private AdIdCompatibleManager mAdIdManager;
+ @NonNull private ServiceProvider mServiceProvider;
+
+ /**
+ * Factory method for creating an instance of AdSelectionManager.
+ *
+ * @param context The {@link Context} to use
+ * @return A {@link AdSelectionManager} instance
+ */
+ @NonNull
+ public static AdSelectionManager get(@NonNull Context context) {
+ // On T+, context.getSystemService() does more than just call constructor.
+ return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
+ ? context.getSystemService(AdSelectionManager.class)
+ : new AdSelectionManager(context);
+ }
+
+ /**
+ * Factory method for creating an instance of AdSelectionManager.
+ *
+ * <p>Note: This is for testing only.
+ *
+ * @param context The {@link Context} to use
+ * @param adIdManager The {@link AdIdCompatibleManager} instance to use
+ * @param adSelectionService The {@link AdSelectionService} instance to use
+ * @return A {@link AdSelectionManager} instance
+ * @hide
+ */
+ @VisibleForTesting
+ @NonNull
+ public static AdSelectionManager get(
+ @NonNull Context context,
+ @NonNull AdIdCompatibleManager adIdManager,
+ @NonNull AdSelectionService adSelectionService) {
+ AdSelectionManager adSelectionManager = AdSelectionManager.get(context);
+ adSelectionManager.mAdIdManager = adIdManager;
+ adSelectionManager.mServiceProvider = () -> adSelectionService;
+ return adSelectionManager;
+ }
+
+ /**
+ * Create AdSelectionManager
+ *
+ * @hide
+ */
+ public AdSelectionManager(@NonNull Context context) {
+ Objects.requireNonNull(context);
+
+ // Initialize the default service provider
+ mServiceProvider = this::doGetService;
+
+ // In case the AdSelectionManager is initiated from inside a sdk_sandbox process the
+ // fields will be immediately rewritten by the initialize method below.
+ initialize(context);
+ }
+
+ /**
+ * Initializes {@link AdSelectionManager} with the given {@code context}.
+ *
+ * <p>This method is called by the {@link SandboxedSdkContext} to propagate the correct context.
+ * For more information check the javadoc on the {@link
+ * android.app.sdksandbox.SdkSandboxSystemServiceRegistry}.
+ *
+ * @hide
+ * @see android.app.sdksandbox.SdkSandboxSystemServiceRegistry
+ */
+ public AdSelectionManager initialize(@NonNull Context context) {
+ Objects.requireNonNull(context);
+
+ mContext = context;
+ mServiceBinder =
+ ServiceBinder.getServiceBinder(
+ context,
+ AdServicesCommon.ACTION_AD_SELECTION_SERVICE,
+ AdSelectionService.Stub::asInterface);
+ mAdIdManager = new AdIdCompatibleManager(context);
+ return this;
+ }
+
+ @NonNull
+ public TestAdSelectionManager getTestAdSelectionManager() {
+ return new TestAdSelectionManager(this);
+ }
+
+ /**
+ * Using this interface {@code getService}'s implementation is decoupled from the default {@link
+ * #doGetService()}. This allows us to inject mock instances of {@link AdSelectionService} to
+ * inspect and test the manager-service boundary.
+ */
+ interface ServiceProvider {
+ @NonNull
+ AdSelectionService getService();
+ }
+
+ @NonNull
+ ServiceProvider getServiceProvider() {
+ return mServiceProvider;
+ }
+
+ @NonNull
+ AdSelectionService doGetService() {
+ return mServiceBinder.getService();
+ }
+
+ /**
+ * Collects custom audience data from device. Returns a compressed and encrypted blob to send to
+ * auction servers for ad selection. For more details, please visit <a
+ * href="https://developer.android.com/design-for-safety/privacy-sandbox/protected-audience-bidding-and-auction-services">Bidding
+ * and Auction Services Explainer</a>.
+ *
+ * <p>Custom audience ads must have a {@code ad_render_id} to be eligible for to be collected.
+ *
+ * <p>See {@link AdSelectionManager#persistAdSelectionResult} for how to process the results of
+ * the ad selection run on server-side with the blob generated by this API.
+ *
+ * <p>The output is passed by the receiver, which either returns an {@link
+ * GetAdSelectionDataOutcome} for a successful run, or an {@link Exception} includes the type of
+ * the exception thrown and the corresponding error message.
+ *
+ * <p>If the {@link IllegalArgumentException} is thrown, it is caused by invalid input argument
+ * the API received to run the ad selection.
+ *
+ * <p>If the {@link IllegalStateException} is thrown with error message "Failure of AdSelection
+ * services.", it is caused by an internal failure of the ad selection service.
+ *
+ * <p>If the {@link TimeoutException} is thrown, it is caused when a timeout is encountered
+ * during bidding, scoring, or overall selection process to find winning Ad.
+ *
+ * <p>If the {@link LimitExceededException} is thrown, it is caused when the calling package
+ * exceeds the allowed rate limits and is throttled.
+ *
+ * <p>If the {@link SecurityException} is thrown, it is caused when the caller is not authorized
+ * or permission is not requested.
+ */
+ @RequiresPermission(
+ anyOf = {
+ ACCESS_ADSERVICES_CUSTOM_AUDIENCE,
+ ACCESS_ADSERVICES_PROTECTED_SIGNALS,
+ ACCESS_ADSERVICES_AD_SELECTION
+ })
+ public void getAdSelectionData(
+ @NonNull GetAdSelectionDataRequest request,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OutcomeReceiver<GetAdSelectionDataOutcome, Exception> receiver) {
+ Objects.requireNonNull(request);
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(receiver);
+ try {
+ final AdSelectionService service = getServiceProvider().getService();
+ service.getAdSelectionData(
+ new GetAdSelectionDataInput.Builder()
+ .setSeller(request.getSeller())
+ .setCallerPackageName(getCallerPackageName())
+ .setCoordinatorOriginUri(request.getCoordinatorOriginUri())
+ .build(),
+ new CallerMetadata.Builder()
+ .setBinderElapsedTimestamp(SystemClock.elapsedRealtime())
+ .build(),
+ new GetAdSelectionDataCallback.Stub() {
+ @Override
+ public void onSuccess(GetAdSelectionDataResponse resultParcel) {
+ executor.execute(
+ () -> {
+ byte[] adSelectionData;
+ try {
+ adSelectionData = getAdSelectionData(resultParcel);
+ } catch (IOException e) {
+ receiver.onError(
+ new IllegalStateException(
+ "Unable to return the AdSelectionData",
+ e));
+ return;
+ }
+ receiver.onResult(
+ new GetAdSelectionDataOutcome.Builder()
+ .setAdSelectionId(
+ resultParcel.getAdSelectionId())
+ .setAdSelectionData(adSelectionData)
+ .build());
+ });
+ }
+
+ @Override
+ public void onFailure(FledgeErrorResponse failureParcel) {
+ executor.execute(
+ () -> {
+ receiver.onError(
+ AdServicesStatusUtils.asException(failureParcel));
+ });
+ }
+ });
+ } catch (NullPointerException e) {
+ sLogger.e(e, "Unable to find the AdSelection service.");
+ receiver.onError(
+ new IllegalStateException("Unable to find the AdSelection service.", e));
+ } catch (RemoteException e) {
+ sLogger.e(e, "Failure of AdSelection service.");
+ receiver.onError(new IllegalStateException("Failure of AdSelection service.", e));
+ }
+ }
+
+ /**
+ * Persists the ad selection results from the server-side. For more details, please visit <a
+ * href="https://developer.android.com/design-for-safety/privacy-sandbox/protected-audience-bidding-and-auction-services">Bidding
+ * and Auction Services Explainer</a>
+ *
+ * <p>See {@link AdSelectionManager#getAdSelectionData} for how to generate an encrypted blob to
+ * run an ad selection on the server side.
+ *
+ * <p>The output is passed by the receiver, which either returns an {@link AdSelectionOutcome}
+ * for a successful run, or an {@link Exception} includes the type of the exception thrown and
+ * the corresponding error message. The {@link AdSelectionOutcome#getAdSelectionId()} is not
+ * guaranteed to be the same as the {@link
+ * PersistAdSelectionResultRequest#getAdSelectionDataId()} or the deprecated {@link
+ * PersistAdSelectionResultRequest#getAdSelectionId()}.
+ *
+ * <p>If the {@link IllegalArgumentException} is thrown, it is caused by invalid input argument
+ * the API received to run the ad selection.
+ *
+ * <p>If the {@link IllegalStateException} is thrown with error message "Failure of AdSelection
+ * services.", it is caused by an internal failure of the ad selection service.
+ *
+ * <p>If the {@link TimeoutException} is thrown, it is caused when a timeout is encountered
+ * during bidding, scoring, or overall selection process to find winning Ad.
+ *
+ * <p>If the {@link LimitExceededException} is thrown, it is caused when the calling package
+ * exceeds the allowed rate limits and is throttled.
+ *
+ * <p>If the {@link SecurityException} is thrown, it is caused when the caller is not authorized
+ * or permission is not requested.
+ */
+ @RequiresPermission(
+ anyOf = {
+ ACCESS_ADSERVICES_CUSTOM_AUDIENCE,
+ ACCESS_ADSERVICES_PROTECTED_SIGNALS,
+ ACCESS_ADSERVICES_AD_SELECTION
+ })
+ public void persistAdSelectionResult(
+ @NonNull PersistAdSelectionResultRequest request,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OutcomeReceiver<AdSelectionOutcome, Exception> receiver) {
+ Objects.requireNonNull(request);
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(receiver);
+
+ try {
+ final AdSelectionService service = getServiceProvider().getService();
+ service.persistAdSelectionResult(
+ new PersistAdSelectionResultInput.Builder()
+ .setSeller(request.getSeller())
+ .setAdSelectionId(request.getAdSelectionId())
+ .setAdSelectionResult(request.getAdSelectionResult())
+ .setCallerPackageName(getCallerPackageName())
+ .build(),
+ new CallerMetadata.Builder()
+ .setBinderElapsedTimestamp(SystemClock.elapsedRealtime())
+ .build(),
+ new PersistAdSelectionResultCallback.Stub() {
+ @Override
+ public void onSuccess(PersistAdSelectionResultResponse resultParcel) {
+ executor.execute(
+ () ->
+ receiver.onResult(
+ new AdSelectionOutcome.Builder()
+ .setAdSelectionId(
+ resultParcel.getAdSelectionId())
+ .setRenderUri(
+ resultParcel.getAdRenderUri())
+ .build()));
+ }
+
+ @Override
+ public void onFailure(FledgeErrorResponse failureParcel) {
+ executor.execute(
+ () -> {
+ receiver.onError(
+ AdServicesStatusUtils.asException(failureParcel));
+ });
+ }
+ });
+ } catch (NullPointerException e) {
+ sLogger.e(e, "Unable to find the AdSelection service.");
+ receiver.onError(
+ new IllegalStateException("Unable to find the AdSelection service.", e));
+ } catch (RemoteException e) {
+ sLogger.e(e, "Failure of AdSelection service.");
+ receiver.onError(new IllegalStateException("Failure of AdSelection service.", e));
+ }
+ }
+
+ /**
+ * Runs the ad selection process on device to select a remarketing ad for the caller
+ * application.
+ *
+ * <p>The input {@code adSelectionConfig} is provided by the Ads SDK and the {@link
+ * AdSelectionConfig} object is transferred via a Binder call. For this reason, the total size
+ * of these objects is bound to the Android IPC limitations. Failures to transfer the {@link
+ * AdSelectionConfig} will throws an {@link TransactionTooLargeException}.
+ *
+ * <p>The input {@code adSelectionConfig} contains {@code Decision Logic Uri} that could follow
+ * either the HTTPS or Ad Selection Prebuilt schemas.
+ *
+ * <p>If the URI follows HTTPS schema then the host should match the {@code seller}. Otherwise,
+ * {@link IllegalArgumentException} will be thrown.
+ *
+ * <p>Prebuilt URIs are a way of substituting a generic pre-built logics for the required
+ * JavaScripts for {@code scoreAds}. Prebuilt Uri for this endpoint should follow;
+ *
+ * <ul>
+ * <li>{@code ad-selection-prebuilt://ad-selection/<name>?<script-generation-parameters>}
+ * </ul>
+ *
+ * <p>If an unsupported prebuilt URI is passed or prebuilt URI feature is disabled by the
+ * service then {@link IllegalArgumentException} will be thrown.
+ *
+ * <p>See {@link AdSelectionConfig.Builder#setDecisionLogicUri} for supported {@code <name>} and
+ * required {@code <script-generation-parameters>}.
+ *
+ * <p>The output is passed by the receiver, which either returns an {@link AdSelectionOutcome}
+ * for a successful run, or an {@link Exception} includes the type of the exception thrown and
+ * the corresponding error message.
+ *
+ * <p>If the {@link IllegalArgumentException} is thrown, it is caused by invalid input argument
+ * the API received to run the ad selection.
+ *
+ * <p>If the {@link IllegalStateException} is thrown with error message "Failure of AdSelection
+ * services.", it is caused by an internal failure of the ad selection service.
+ *
+ * <p>If the {@link TimeoutException} is thrown, it is caused when a timeout is encountered
+ * during bidding, scoring, or overall selection process to find winning Ad.
+ *
+ * <p>If the {@link LimitExceededException} is thrown, it is caused when the calling package
+ * exceeds the allowed rate limits and is throttled.
+ *
+ * <p>If the {@link SecurityException} is thrown, it is caused when the caller is not authorized
+ * or permission is not requested.
+ */
+ @RequiresPermission(
+ anyOf = {
+ ACCESS_ADSERVICES_CUSTOM_AUDIENCE,
+ ACCESS_ADSERVICES_PROTECTED_SIGNALS,
+ ACCESS_ADSERVICES_AD_SELECTION
+ })
+ public void selectAds(
+ @NonNull AdSelectionConfig adSelectionConfig,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OutcomeReceiver<AdSelectionOutcome, Exception> receiver) {
+ Objects.requireNonNull(adSelectionConfig);
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(receiver);
+
+ try {
+ final AdSelectionService service = getServiceProvider().getService();
+ service.selectAds(
+ new AdSelectionInput.Builder()
+ .setAdSelectionConfig(adSelectionConfig)
+ .setCallerPackageName(getCallerPackageName())
+ .build(),
+ new CallerMetadata.Builder()
+ .setBinderElapsedTimestamp(SystemClock.elapsedRealtime())
+ .build(),
+ new AdSelectionCallback.Stub() {
+ @Override
+ public void onSuccess(AdSelectionResponse resultParcel) {
+ executor.execute(
+ () ->
+ receiver.onResult(
+ new AdSelectionOutcome.Builder()
+ .setAdSelectionId(
+ resultParcel.getAdSelectionId())
+ .setRenderUri(
+ resultParcel.getRenderUri())
+ .build()));
+ }
+
+ @Override
+ public void onFailure(FledgeErrorResponse failureParcel) {
+ executor.execute(
+ () -> {
+ receiver.onError(
+ AdServicesStatusUtils.asException(failureParcel));
+ });
+ }
+ });
+ } catch (NullPointerException e) {
+ sLogger.e(e, "Unable to find the AdSelection service.");
+ receiver.onError(
+ new IllegalStateException("Unable to find the AdSelection service.", e));
+ } catch (RemoteException e) {
+ sLogger.e(e, "Failure of AdSelection service.");
+ receiver.onError(new IllegalStateException("Failure of AdSelection service.", e));
+ }
+ }
+
+ /**
+ * Selects an ad from the results of previously ran ad selections.
+ *
+ * <p>The input {@code adSelectionFromOutcomesConfig} is provided by the Ads SDK and the {@link
+ * AdSelectionFromOutcomesConfig} object is transferred via a Binder call. For this reason, the
+ * total size of these objects is bound to the Android IPC limitations. Failures to transfer the
+ * {@link AdSelectionFromOutcomesConfig} will throws an {@link TransactionTooLargeException}.
+ *
+ * <p>The output is passed by the receiver, which either returns an {@link AdSelectionOutcome}
+ * for a successful run, or an {@link Exception} includes the type of the exception thrown and
+ * the corresponding error message.
+ *
+ * <p>The input {@code adSelectionFromOutcomesConfig} contains:
+ *
+ * <ul>
+ * <li>{@code Seller} is required to be a registered {@link
+ * android.adservices.common.AdTechIdentifier}. Otherwise, {@link IllegalStateException}
+ * will be thrown.
+ * <li>{@code List of ad selection ids} should exist and come from {@link
+ * AdSelectionManager#selectAds} calls originated from the same application. Otherwise,
+ * {@link IllegalArgumentException} for input validation will raise listing violating ad
+ * selection ids.
+ * <li>{@code Selection logic URI} that could follow either the HTTPS or Ad Selection Prebuilt
+ * schemas.
+ * <p>If the URI follows HTTPS schema then the host should match the {@code seller}.
+ * Otherwise, {@link IllegalArgumentException} will be thrown.
+ * <p>Prebuilt URIs are a way of substituting a generic pre-built logics for the required
+ * JavaScripts for {@code selectOutcome}. Prebuilt Uri for this endpoint should follow;
+ * <ul>
+ * <li>{@code
+ * ad-selection-prebuilt://ad-selection-from-outcomes/<name>?<script-generation-parameters>}
+ * </ul>
+ * <p>If an unsupported prebuilt URI is passed or prebuilt URI feature is disabled by the
+ * service then {@link IllegalArgumentException} will be thrown.
+ * <p>See {@link AdSelectionFromOutcomesConfig.Builder#setSelectionLogicUri} for supported
+ * {@code <name>} and required {@code <script-generation-parameters>}.
+ * </ul>
+ *
+ * <p>If the {@link IllegalArgumentException} is thrown, it is caused by invalid input argument
+ * the API received to run the ad selection.
+ *
+ * <p>If the {@link IllegalStateException} is thrown with error message "Failure of AdSelection
+ * services.", it is caused by an internal failure of the ad selection service.
+ *
+ * <p>If the {@link TimeoutException} is thrown, it is caused when a timeout is encountered
+ * during bidding, scoring, or overall selection process to find winning Ad.
+ *
+ * <p>If the {@link LimitExceededException} is thrown, it is caused when the calling package
+ * exceeds the allowed rate limits and is throttled.
+ *
+ * <p>If the {@link SecurityException} is thrown, it is caused when the caller is not authorized
+ * or permission is not requested.
+ */
+ @RequiresPermission(
+ anyOf = {
+ ACCESS_ADSERVICES_CUSTOM_AUDIENCE,
+ ACCESS_ADSERVICES_PROTECTED_SIGNALS,
+ ACCESS_ADSERVICES_AD_SELECTION
+ })
+ public void selectAds(
+ @NonNull AdSelectionFromOutcomesConfig adSelectionFromOutcomesConfig,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OutcomeReceiver<AdSelectionOutcome, Exception> receiver) {
+ Objects.requireNonNull(adSelectionFromOutcomesConfig);
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(receiver);
+
+ try {
+ final AdSelectionService service = getServiceProvider().getService();
+ service.selectAdsFromOutcomes(
+ new AdSelectionFromOutcomesInput.Builder()
+ .setAdSelectionFromOutcomesConfig(adSelectionFromOutcomesConfig)
+ .setCallerPackageName(getCallerPackageName())
+ .build(),
+ new CallerMetadata.Builder()
+ .setBinderElapsedTimestamp(SystemClock.elapsedRealtime())
+ .build(),
+ new AdSelectionCallback.Stub() {
+ @Override
+ public void onSuccess(AdSelectionResponse resultParcel) {
+ executor.execute(
+ () -> {
+ if (resultParcel == null) {
+ receiver.onResult(AdSelectionOutcome.NO_OUTCOME);
+ } else {
+ receiver.onResult(
+ new AdSelectionOutcome.Builder()
+ .setAdSelectionId(
+ resultParcel.getAdSelectionId())
+ .setRenderUri(
+ resultParcel.getRenderUri())
+ .build());
+ }
+ });
+ }
+
+ @Override
+ public void onFailure(FledgeErrorResponse failureParcel) {
+ executor.execute(
+ () -> {
+ receiver.onError(
+ AdServicesStatusUtils.asException(failureParcel));
+ });
+ }
+ });
+ } catch (NullPointerException e) {
+ sLogger.e(e, "Unable to find the AdSelection service.");
+ receiver.onError(
+ new IllegalStateException("Unable to find the AdSelection service.", e));
+ } catch (RemoteException e) {
+ sLogger.e(e, "Failure of AdSelection service.");
+ receiver.onError(new IllegalStateException("Failure of AdSelection service.", e));
+ }
+ }
+
+ /**
+ * Notifies the service that there is a new impression to report for the ad selected by the
+ * ad-selection run identified by {@code adSelectionId}. There is no guarantee about when the
+ * impression will be reported. The impression reporting could be delayed and reports could be
+ * batched.
+ *
+ * <p>To calculate the winning seller reporting URL, the service fetches the seller's JavaScript
+ * logic from the {@link AdSelectionConfig#getDecisionLogicUri()} found at {@link
+ * ReportImpressionRequest#getAdSelectionConfig()}. Then, the service executes one of the
+ * functions found in the seller JS called {@code reportResult}, providing on-device signals as
+ * well as {@link ReportImpressionRequest#getAdSelectionConfig()} as input parameters.
+ *
+ * <p>The function definition of {@code reportResult} is:
+ *
+ * <p>{@code function reportResult(ad_selection_config, render_url, bid, contextual_signals) {
+ * return { 'status': status, 'results': {'signals_for_buyer': signals_for_buyer,
+ * 'reporting_url': reporting_url } }; } }
+ *
+ * <p>To calculate the winning buyer reporting URL, the service fetches the winning buyer's
+ * JavaScript logic which is fetched via the buyer's {@link
+ * android.adservices.customaudience.CustomAudience#getBiddingLogicUri()}. Then, the service
+ * executes one of the functions found in the buyer JS called {@code reportWin}, providing
+ * on-device signals, {@code signals_for_buyer} calculated by {@code reportResult}, and specific
+ * fields from {@link ReportImpressionRequest#getAdSelectionConfig()} as input parameters.
+ *
+ * <p>The function definition of {@code reportWin} is:
+ *
+ * <p>{@code function reportWin(ad_selection_signals, per_buyer_signals, signals_for_buyer,
+ * contextual_signals, custom_audience_reporting_signals) { return {'status': 0, 'results':
+ * {'reporting_url': reporting_url } }; } }
+ *
+ * <p>In addition, buyers and sellers have the option to register to receive reports on specific
+ * ad events. To do so, they can invoke the platform provided {@code registerAdBeacon} function
+ * inside {@code reportWin} and {@code reportResult} for buyers and sellers, respectively.
+ *
+ * <p>The function definition of {@code registerBeacon} is:
+ *
+ * <p>{@code function registerAdBeacon(beacons)}, where {@code beacons} is a dict of string to
+ * string pairs
+ *
+ * <p>For each ad event a buyer/seller is interested in reports for, they would add an {@code
+ * event_key}: {@code event_reporting_uri} pair to the {@code beacons} dict, where {@code
+ * event_key} is an identifier for that specific event. This {@code event_key} should match
+ * {@link ReportEventRequest#getKey()} when the SDK invokes {@link #reportEvent}. In addition,
+ * each {@code event_reporting_uri} should parse properly into a {@link android.net.Uri}. This
+ * will be the {@link android.net.Uri} reported to when the SDK invokes {@link #reportEvent}.
+ *
+ * <p>When the buyer/seller has added all the pairings they want to receive events for, they can
+ * invoke {@code registerAdBeacon(beacons)}, where {@code beacons} is the name of the dict they
+ * added the pairs to.
+ *
+ * <p>{@code registerAdBeacon} will throw a {@code TypeError} in these situations:
+ *
+ * <ol>
+ * <li>{@code registerAdBeacon}is called more than once. If this error is caught in
+ * reportWin/reportResult, the original set of pairings will be registered
+ * <li>{@code registerAdBeacon} doesn't have exactly 1 dict argument.
+ * <li>The contents of the 1 dict argument are not all {@code String: String} pairings.
+ * </ol>
+ *
+ * <p>The output is passed by the {@code receiver}, which either returns an empty {@link Object}
+ * for a successful run, or an {@link Exception} includes the type of the exception thrown and
+ * the corresponding error message.
+ *
+ * <p>If the {@link IllegalArgumentException} is thrown, it is caused by invalid input argument
+ * the API received to report the impression.
+ *
+ * <p>If the {@link IllegalStateException} is thrown with error message "Failure of AdSelection
+ * services.", it is caused by an internal failure of the ad selection service.
+ *
+ * <p>If the {@link LimitExceededException} is thrown, it is caused when the calling package
+ * exceeds the allowed rate limits and is throttled.
+ *
+ * <p>If the {@link SecurityException} is thrown, it is caused when the caller is not authorized
+ * or permission is not requested.
+ *
+ * <p>Impressions will be reported at most once as a best-effort attempt.
+ */
+ @RequiresPermission(
+ anyOf = {
+ ACCESS_ADSERVICES_CUSTOM_AUDIENCE,
+ ACCESS_ADSERVICES_PROTECTED_SIGNALS,
+ ACCESS_ADSERVICES_AD_SELECTION
+ })
+ public void reportImpression(
+ @NonNull ReportImpressionRequest request,
+ @NonNull Executor executor,
+ @NonNull OutcomeReceiver<Object, Exception> receiver) {
+ Objects.requireNonNull(request);
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(receiver);
+
+ try {
+ final AdSelectionService service = getServiceProvider().getService();
+ service.reportImpression(
+ new ReportImpressionInput.Builder()
+ .setAdSelectionId(request.getAdSelectionId())
+ .setAdSelectionConfig(request.getAdSelectionConfig())
+ .setCallerPackageName(getCallerPackageName())
+ .build(),
+ new ReportImpressionCallback.Stub() {
+ @Override
+ public void onSuccess() {
+ executor.execute(() -> receiver.onResult(new Object()));
+ }
+
+ @Override
+ public void onFailure(FledgeErrorResponse failureParcel) {
+ executor.execute(
+ () -> {
+ receiver.onError(
+ AdServicesStatusUtils.asException(failureParcel));
+ });
+ }
+ });
+ } catch (NullPointerException e) {
+ sLogger.e(e, "Unable to find the AdSelection service.");
+ receiver.onError(
+ new IllegalStateException("Unable to find the AdSelection service.", e));
+ } catch (RemoteException e) {
+ sLogger.e(e, "Exception");
+ receiver.onError(new IllegalStateException("Failure of AdSelection service.", e));
+ }
+ }
+
+ /**
+ * Notifies the service that there is a new ad event to report for the ad selected by the
+ * ad-selection run identified by {@code adSelectionId}. An ad event is any occurrence that
+ * happens to an ad associated with the given {@code adSelectionId}. There is no guarantee about
+ * when the ad event will be reported. The event reporting could be delayed and reports could be
+ * batched.
+ *
+ * <p>Using {@link ReportEventRequest#getKey()}, the service will fetch the {@code reportingUri}
+ * that was registered in {@code registerAdBeacon}. See documentation of {@link
+ * #reportImpression} for more details regarding {@code registerAdBeacon}. Then, the service
+ * will attach {@link ReportEventRequest#getData()} to the request body of a POST request and
+ * send the request. The body of the POST request will have the {@code content-type} of {@code
+ * text/plain}, and the data will be transmitted in {@code charset=UTF-8}.
+ *
+ * <p>The output is passed by the receiver, which either returns an empty {@link Object} for a
+ * successful run, or an {@link Exception} includes the type of the exception thrown and the
+ * corresponding error message.
+ *
+ * <p>If the {@link IllegalArgumentException} is thrown, it is caused by invalid input argument
+ * the API received to report the ad event.
+ *
+ * <p>If the {@link IllegalStateException} is thrown with error message "Failure of AdSelection
+ * services.", it is caused by an internal failure of the ad selection service.
+ *
+ * <p>If the {@link LimitExceededException} is thrown, it is caused when the calling package
+ * exceeds the allowed rate limits and is throttled.
+ *
+ * <p>If the {@link SecurityException} is thrown, it is caused when the caller is not authorized
+ * or permission is not requested.
+ *
+ * <p>Events will be reported at most once as a best-effort attempt.
+ */
+ @RequiresPermission(
+ anyOf = {
+ ACCESS_ADSERVICES_CUSTOM_AUDIENCE,
+ ACCESS_ADSERVICES_PROTECTED_SIGNALS,
+ ACCESS_ADSERVICES_AD_SELECTION
+ })
+ public void reportEvent(
+ @NonNull ReportEventRequest request,
+ @NonNull Executor executor,
+ @NonNull OutcomeReceiver<Object, Exception> receiver) {
+ Objects.requireNonNull(request);
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(receiver);
+ try {
+ ReportInteractionInput.Builder inputBuilder =
+ new ReportInteractionInput.Builder()
+ .setAdSelectionId(request.getAdSelectionId())
+ .setInteractionKey(request.getKey())
+ .setInteractionData(request.getData())
+ .setReportingDestinations(request.getReportingDestinations())
+ .setCallerPackageName(getCallerPackageName())
+ .setCallerSdkName(getCallerSdkName())
+ .setInputEvent(request.getInputEvent());
+
+ getAdId((adIdValue) -> inputBuilder.setAdId(adIdValue));
+
+ final AdSelectionService service = getServiceProvider().getService();
+ service.reportInteraction(
+ inputBuilder.build(),
+ new ReportInteractionCallback.Stub() {
+ @Override
+ public void onSuccess() {
+ executor.execute(() -> receiver.onResult(new Object()));
+ }
+
+ @Override
+ public void onFailure(FledgeErrorResponse failureParcel) {
+ executor.execute(
+ () -> {
+ receiver.onError(
+ AdServicesStatusUtils.asException(failureParcel));
+ });
+ }
+ });
+ } catch (NullPointerException e) {
+ sLogger.e(e, "Unable to find the AdSelection service.");
+ receiver.onError(
+ new IllegalStateException("Unable to find the AdSelection service.", e));
+ } catch (RemoteException e) {
+ sLogger.e(e, "Exception");
+ receiver.onError(new IllegalStateException("Failure of AdSelection service.", e));
+ }
+ }
+
+ /**
+ * Gives the provided list of adtechs the ability to do app install filtering on the calling
+ * app.
+ *
+ * <p>The input {@code request} is provided by the Ads SDK and the {@code request} object is
+ * transferred via a Binder call. For this reason, the total size of these objects is bound to
+ * the Android IPC limitations. Failures to transfer the {@code advertisers} will throws an
+ * {@link TransactionTooLargeException}.
+ *
+ * <p>The output is passed by the receiver, which either returns an empty {@link Object} for a
+ * successful run, or an {@link Exception} includes the type of the exception thrown and the
+ * corresponding error message.
+ *
+ * <p>If the {@link IllegalArgumentException} is thrown, it is caused by invalid input argument
+ * the API received.
+ *
+ * <p>If the {@link IllegalStateException} is thrown with error message "Failure of AdSelection
+ * services.", it is caused by an internal failure of the ad selection service.
+ *
+ * <p>If the {@link LimitExceededException} is thrown, it is caused when the calling package
+ * exceeds the allowed rate limits and is throttled.
+ *
+ * <p>If the {@link SecurityException} is thrown, it is caused when the caller is not authorized
+ * or permission is not requested.
+ */
+ @FlaggedApi(Flags.FLAG_FLEDGE_AD_SELECTION_FILTERING_ENABLED)
+ @RequiresPermission(
+ anyOf = {
+ ACCESS_ADSERVICES_CUSTOM_AUDIENCE,
+ ACCESS_ADSERVICES_PROTECTED_SIGNALS,
+ ACCESS_ADSERVICES_AD_SELECTION
+ })
+ public void setAppInstallAdvertisers(
+ @NonNull SetAppInstallAdvertisersRequest request,
+ @NonNull Executor executor,
+ @NonNull OutcomeReceiver<Object, Exception> receiver) {
+ Objects.requireNonNull(request);
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(receiver);
+
+ try {
+ final AdSelectionService service = getServiceProvider().getService();
+ service.setAppInstallAdvertisers(
+ new SetAppInstallAdvertisersInput.Builder()
+ .setAdvertisers(request.getAdvertisers())
+ .setCallerPackageName(getCallerPackageName())
+ .build(),
+ new SetAppInstallAdvertisersCallback.Stub() {
+ @Override
+ public void onSuccess() {
+ executor.execute(() -> receiver.onResult(new Object()));
+ }
+
+ @Override
+ public void onFailure(FledgeErrorResponse failureParcel) {
+ executor.execute(
+ () -> {
+ receiver.onError(
+ AdServicesStatusUtils.asException(failureParcel));
+ });
+ }
+ });
+ } catch (NullPointerException e) {
+ sLogger.e(e, "Unable to find the AdSelection service.");
+ receiver.onError(
+ new IllegalStateException("Unable to find the AdSelection service.", e));
+ } catch (RemoteException e) {
+ sLogger.e(e, "Exception");
+ receiver.onError(new IllegalStateException("Failure of AdSelection service.", e));
+ }
+ }
+
+ /**
+ * Updates the counter histograms for an ad which was previously selected by a call to {@link
+ * #selectAds(AdSelectionConfig, Executor, OutcomeReceiver)}.
+ *
+ * <p>The counter histograms are used in ad selection to inform frequency cap filtering on
+ * candidate ads, where ads whose frequency caps are met or exceeded are removed from the
+ * bidding process during ad selection.
+ *
+ * <p>Counter histograms can only be updated for ads specified by the given {@code
+ * adSelectionId} returned by a recent call to FLEDGE ad selection from the same caller app.
+ *
+ * <p>A {@link SecurityException} is returned via the {@code outcomeReceiver} if:
+ *
+ * <ol>
+ * <li>the app has not declared the correct permissions in its manifest, or
+ * <li>the app or entity identified by the {@code callerAdTechIdentifier} are not authorized
+ * to use the API.
+ * </ol>
+ *
+ * An {@link IllegalStateException} is returned via the {@code outcomeReceiver} if the call does
+ * not come from an app with a foreground activity.
+ *
+ * <p>A {@link LimitExceededException} is returned via the {@code outcomeReceiver} if the call
+ * exceeds the calling app's API throttle.
+ *
+ * <p>In all other failure cases, the {@code outcomeReceiver} will return an empty {@link
+ * Object}. Note that to protect user privacy, internal errors will not be sent back via an
+ * exception.
+ */
+ @RequiresPermission(
+ anyOf = {
+ ACCESS_ADSERVICES_CUSTOM_AUDIENCE,
+ ACCESS_ADSERVICES_PROTECTED_SIGNALS,
+ ACCESS_ADSERVICES_AD_SELECTION
+ })
+ public void updateAdCounterHistogram(
+ @NonNull UpdateAdCounterHistogramRequest updateAdCounterHistogramRequest,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OutcomeReceiver<Object, Exception> outcomeReceiver) {
+ Objects.requireNonNull(updateAdCounterHistogramRequest, "Request must not be null");
+ Objects.requireNonNull(executor, "Executor must not be null");
+ Objects.requireNonNull(outcomeReceiver, "Outcome receiver must not be null");
+
+ try {
+ final AdSelectionService service = getServiceProvider().getService();
+ Objects.requireNonNull(service);
+ service.updateAdCounterHistogram(
+ new UpdateAdCounterHistogramInput.Builder(
+ updateAdCounterHistogramRequest.getAdSelectionId(),
+ updateAdCounterHistogramRequest.getAdEventType(),
+ updateAdCounterHistogramRequest.getCallerAdTech(),
+ getCallerPackageName())
+ .build(),
+ new UpdateAdCounterHistogramCallback.Stub() {
+ @Override
+ public void onSuccess() {
+ executor.execute(() -> outcomeReceiver.onResult(new Object()));
+ }
+
+ @Override
+ public void onFailure(FledgeErrorResponse failureParcel) {
+ executor.execute(
+ () -> {
+ outcomeReceiver.onError(
+ AdServicesStatusUtils.asException(failureParcel));
+ });
+ }
+ });
+ } catch (NullPointerException e) {
+ sLogger.e(e, "Unable to find the AdSelection service");
+ outcomeReceiver.onError(
+ new IllegalStateException("Unable to find the AdSelection service", e));
+ } catch (RemoteException e) {
+ sLogger.e(e, "Remote exception encountered while updating ad counter histogram");
+ outcomeReceiver.onError(new IllegalStateException("Failure of AdSelection service", e));
+ }
+ }
+
+ private String getCallerPackageName() {
+ SandboxedSdkContext sandboxedSdkContext =
+ SandboxedSdkContextUtils.getAsSandboxedSdkContext(mContext);
+ return sandboxedSdkContext == null
+ ? mContext.getPackageName()
+ : sandboxedSdkContext.getClientPackageName();
+ }
+
+ private byte[] getAdSelectionData(GetAdSelectionDataResponse response) throws IOException {
+ if (Objects.nonNull(response.getAssetFileDescriptor())) {
+ AssetFileDescriptor assetFileDescriptor = response.getAssetFileDescriptor();
+ return AssetFileDescriptorUtil.readAssetFileDescriptorIntoBuffer(assetFileDescriptor);
+ } else {
+ return response.getAdSelectionData();
+ }
+ }
+
+ private String getCallerSdkName() {
+ SandboxedSdkContext sandboxedSdkContext =
+ SandboxedSdkContextUtils.getAsSandboxedSdkContext(mContext);
+ return sandboxedSdkContext == null ? "" : sandboxedSdkContext.getSdkPackageName();
+ }
+
+ private interface AdSelectionAdIdCallback {
+ void onResult(@Nullable String adIdValue);
+ }
+
+ @SuppressLint("MissingPermission")
+ private void getAdId(AdSelectionAdIdCallback adSelectionAdIdCallback) {
+ try {
+ CountDownLatch timer = new CountDownLatch(1);
+ AtomicReference<String> adIdValue = new AtomicReference<>();
+ mAdIdManager.getAdId(
+ mAdIdExecutor,
+ new android.adservices.common.AdServicesOutcomeReceiver<>() {
+ @Override
+ public void onResult(AdId adId) {
+ String id = adId.getAdId();
+ adIdValue.set(!AdId.ZERO_OUT.equals(id) ? id : null);
+ sLogger.v("AdId permission enabled: %b.", !AdId.ZERO_OUT.equals(id));
+ timer.countDown();
+ }
+
+ @Override
+ public void onError(Exception e) {
+ if (e instanceof IllegalStateException
+ || e instanceof SecurityException) {
+ sLogger.w(DEBUG_API_WARNING_MESSAGE);
+ } else {
+ sLogger.w(e, DEBUG_API_WARNING_MESSAGE);
+ }
+ timer.countDown();
+ }
+ });
+
+ boolean timedOut = false;
+ try {
+ timedOut = !timer.await(AD_ID_TIMEOUT_MS, MILLISECONDS);
+ } catch (InterruptedException e) {
+ sLogger.w(e, "Interrupted while getting the AdId.");
+ }
+ if (timedOut) {
+ sLogger.w("AdId call timed out.");
+ }
+ adSelectionAdIdCallback.onResult(adIdValue.get());
+ } catch (Exception e) {
+ sLogger.d(e, "Could not get AdId.");
+ adSelectionAdIdCallback.onResult(null);
+ }
+ }
+}
diff --git a/android-35/android/adservices/adselection/AdSelectionOutcome.java b/android-35/android/adservices/adselection/AdSelectionOutcome.java
new file mode 100644
index 0000000..1568711
--- /dev/null
+++ b/android-35/android/adservices/adselection/AdSelectionOutcome.java
@@ -0,0 +1,143 @@
+/*
+ * 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.adselection;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.Uri;
+import android.os.OutcomeReceiver;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/**
+ * This class represents a field in the {@code OutcomeReceiver}, which is an input to the {@link
+ * AdSelectionManager#selectAds} in the {@link AdSelectionManager}. This field is populated in the
+ * case of a successful {@link AdSelectionManager#selectAds} call.
+ *
+ * <p>Empty outcome may be returned from {@link
+ * AdSelectionManager#selectAds(AdSelectionFromOutcomesConfig, Executor, OutcomeReceiver)}. Use
+ * {@link AdSelectionOutcome#hasOutcome()} to check if an instance has a valid outcome. When {@link
+ * AdSelectionOutcome#hasOutcome()} returns {@code false}, results from {@link AdSelectionOutcome
+ * #getAdSelectionId()} and {@link AdSelectionOutcome#getRenderUri()} are invalid and shouldn't be
+ * used.
+ */
+public class AdSelectionOutcome {
+ /** Represents an AdSelectionOutcome with empty results. */
+ @NonNull public static final AdSelectionOutcome NO_OUTCOME = new AdSelectionOutcome();
+
+ /** @hide */
+ public static final String UNSET_AD_SELECTION_ID_MESSAGE =
+ "Non-zero ad selection ID must be set";
+
+ /** @hide */
+ public static final int UNSET_AD_SELECTION_ID = 0;
+
+ private final long mAdSelectionId;
+ @NonNull private final Uri mRenderUri;
+
+ private AdSelectionOutcome() {
+ mAdSelectionId = UNSET_AD_SELECTION_ID;
+ mRenderUri = Uri.EMPTY;
+ }
+
+ private AdSelectionOutcome(long adSelectionId, @NonNull Uri renderUri) {
+ Objects.requireNonNull(renderUri);
+
+ mAdSelectionId = adSelectionId;
+ mRenderUri = renderUri;
+ }
+
+ /** Returns the renderUri that the AdSelection returns. */
+ @NonNull
+ public Uri getRenderUri() {
+ return mRenderUri;
+ }
+
+ /** Returns the adSelectionId that identifies the AdSelection. */
+ @NonNull
+ public long getAdSelectionId() {
+ return mAdSelectionId;
+ }
+
+ /**
+ * Returns whether the outcome contains results or empty. Empty outcomes' {@code render uris}
+ * shouldn't be used.
+ */
+ public boolean hasOutcome() {
+ return !this.equals(NO_OUTCOME);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof AdSelectionOutcome) {
+ AdSelectionOutcome adSelectionOutcome = (AdSelectionOutcome) o;
+ return mAdSelectionId == adSelectionOutcome.mAdSelectionId
+ && Objects.equals(mRenderUri, adSelectionOutcome.mRenderUri);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mAdSelectionId, mRenderUri);
+ }
+
+ /**
+ * Builder for {@link AdSelectionOutcome} objects.
+ */
+ public static final class Builder {
+ private long mAdSelectionId = UNSET_AD_SELECTION_ID;
+ @Nullable private Uri mRenderUri;
+
+ public Builder() {}
+
+ /** Sets the mAdSelectionId. */
+ @NonNull
+ public AdSelectionOutcome.Builder setAdSelectionId(long adSelectionId) {
+ this.mAdSelectionId = adSelectionId;
+ return this;
+ }
+
+ /** Sets the RenderUri. */
+ @NonNull
+ public AdSelectionOutcome.Builder setRenderUri(@NonNull Uri renderUri) {
+ Objects.requireNonNull(renderUri);
+
+ mRenderUri = renderUri;
+ return this;
+ }
+
+ /**
+ * Builds a {@link AdSelectionOutcome} instance.
+ *
+ * @throws IllegalArgumentException if the adSelectionIid is not set
+ * @throws NullPointerException if the RenderUri is null
+ */
+ @NonNull
+ public AdSelectionOutcome build() {
+ Objects.requireNonNull(mRenderUri);
+
+ Preconditions.checkArgument(
+ mAdSelectionId != UNSET_AD_SELECTION_ID, UNSET_AD_SELECTION_ID_MESSAGE);
+
+ return new AdSelectionOutcome(mAdSelectionId, mRenderUri);
+ }
+ }
+}
diff --git a/android-35/android/adservices/adselection/AdSelectionResponse.java b/android-35/android/adservices/adselection/AdSelectionResponse.java
new file mode 100644
index 0000000..c1a0095
--- /dev/null
+++ b/android-35/android/adservices/adselection/AdSelectionResponse.java
@@ -0,0 +1,163 @@
+/*
+ * 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.adselection;
+
+import static android.adservices.adselection.AdSelectionOutcome.UNSET_AD_SELECTION_ID;
+import static android.adservices.adselection.AdSelectionOutcome.UNSET_AD_SELECTION_ID_MESSAGE;
+
+import android.annotation.NonNull;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.Objects;
+
+/**
+ * This class represents the response returned by the {@link AdSelectionManager} as the result of a
+ * successful {@code selectAds} call.
+ *
+ * @hide
+ */
+public final class AdSelectionResponse implements Parcelable {
+ private final long mAdSelectionId;
+ @NonNull private final Uri mRenderUri;
+
+ private AdSelectionResponse(long adSelectionId, @NonNull Uri renderUri) {
+ Objects.requireNonNull(renderUri);
+
+ mAdSelectionId = adSelectionId;
+ mRenderUri = renderUri;
+ }
+
+ private AdSelectionResponse(@NonNull Parcel in) {
+ Objects.requireNonNull(in);
+
+ mAdSelectionId = in.readLong();
+ mRenderUri = Uri.CREATOR.createFromParcel(in);
+ }
+
+ @NonNull
+ public static final Creator<AdSelectionResponse> CREATOR =
+ new Parcelable.Creator<AdSelectionResponse>() {
+ @Override
+ public AdSelectionResponse createFromParcel(@NonNull Parcel in) {
+ Objects.requireNonNull(in);
+ return new AdSelectionResponse(in);
+ }
+
+ @Override
+ public AdSelectionResponse[] newArray(int size) {
+ return new AdSelectionResponse[size];
+ }
+ };
+
+ /** Returns the renderUri that the AdSelection returns. */
+ @NonNull
+ public Uri getRenderUri() {
+ return mRenderUri;
+ }
+
+ /** Returns the adSelectionId that identifies the AdSelection. */
+ @NonNull
+ public long getAdSelectionId() {
+ return mAdSelectionId;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof AdSelectionResponse) {
+ AdSelectionResponse adSelectionResponse = (AdSelectionResponse) o;
+ return mAdSelectionId == adSelectionResponse.mAdSelectionId
+ && Objects.equals(mRenderUri, adSelectionResponse.mRenderUri);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mAdSelectionId, mRenderUri);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ Objects.requireNonNull(dest);
+
+ dest.writeLong(mAdSelectionId);
+ mRenderUri.writeToParcel(dest, flags);
+ }
+
+ @Override
+ public String toString() {
+ return "AdSelectionResponse{"
+ + "mAdSelectionId="
+ + mAdSelectionId
+ + ", mRenderUri="
+ + mRenderUri
+ + '}';
+ }
+
+ /**
+ * Builder for {@link AdSelectionResponse} objects.
+ *
+ * @hide
+ */
+ public static final class Builder {
+ private long mAdSelectionId = UNSET_AD_SELECTION_ID;
+ @NonNull private Uri mRenderUri;
+
+ public Builder() {}
+
+ /** Sets the mAdSelectionId. */
+ @NonNull
+ public AdSelectionResponse.Builder setAdSelectionId(long adSelectionId) {
+ this.mAdSelectionId = adSelectionId;
+ return this;
+ }
+
+ /** Sets the RenderUri. */
+ @NonNull
+ public AdSelectionResponse.Builder setRenderUri(@NonNull Uri renderUri) {
+ Objects.requireNonNull(renderUri);
+
+ mRenderUri = renderUri;
+ return this;
+ }
+
+ /**
+ * Builds a {@link AdSelectionResponse} instance.
+ *
+ * @throws IllegalArgumentException if the adSelectionIid is not set
+ * @throws NullPointerException if the RenderUri is null
+ */
+ @NonNull
+ public AdSelectionResponse build() {
+ Objects.requireNonNull(mRenderUri);
+
+ Preconditions.checkArgument(
+ mAdSelectionId != UNSET_AD_SELECTION_ID, UNSET_AD_SELECTION_ID_MESSAGE);
+
+ return new AdSelectionResponse(mAdSelectionId, mRenderUri);
+ }
+ }
+}
diff --git a/android-35/android/adservices/adselection/AdWithBid.java b/android-35/android/adservices/adselection/AdWithBid.java
new file mode 100644
index 0000000..af53bd0
--- /dev/null
+++ b/android-35/android/adservices/adselection/AdWithBid.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.adselection;
+
+import android.adservices.common.AdData;
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.adservices.flags.Flags;
+
+import java.util.Objects;
+
+/**
+ * Represents an ad and its corresponding bid value after the bid generation step in the ad
+ * selection process.
+ *
+ * <p>The ads and their bids are fed into an ad scoring process which will inform the final ad
+ * selection. The currency unit for the bid is expected to be the same requested by the seller when
+ * initiating the selection process and not specified in this class. The seller can provide the
+ * currency via AdSelectionSignals. The currency is opaque to FLEDGE.
+ */
+@FlaggedApi(Flags.FLAG_FLEDGE_AD_SELECTION_FILTERING_ENABLED)
+public final class AdWithBid implements Parcelable {
+ @NonNull
+ private final AdData mAdData;
+ private final double mBid;
+
+ @NonNull
+ public static final Creator<AdWithBid> CREATOR =
+ new Creator<AdWithBid>() {
+ @Override
+ public AdWithBid createFromParcel(@NonNull Parcel in) {
+ Objects.requireNonNull(in);
+
+ return new AdWithBid(in);
+ }
+
+ @Override
+ public AdWithBid[] newArray(int size) {
+ return new AdWithBid[size];
+ }
+ };
+
+ /**
+ * @param adData An {@link AdData} object defining an ad's render URI and buyer metadata
+ * @param bid The amount of money a buyer has bid to show an ad; note that while the bid is
+ * expected to be non-negative, this is only enforced during the ad selection process
+ * @throws NullPointerException if adData is null
+ */
+ public AdWithBid(@NonNull AdData adData, double bid) {
+ Objects.requireNonNull(adData);
+ mAdData = adData;
+ mBid = bid;
+ }
+
+ private AdWithBid(@NonNull Parcel in) {
+ Objects.requireNonNull(in);
+ mAdData = AdData.CREATOR.createFromParcel(in);
+ mBid = in.readDouble();
+ }
+
+ /**
+ * @return the ad that was bid on
+ */
+ @NonNull
+ public AdData getAdData() {
+ return mAdData;
+ }
+
+ /**
+ * The bid is the amount of money an advertiser has bid during the ad selection process to show
+ * an ad. The bid could be any non-negative {@code double}, such as 0.00, 0.17, 1.10, or
+ * 1000.00.
+ *
+ * <p>The currency for a bid would be controlled by Seller and will remain consistent across a
+ * run of Ad selection. This could be achieved by leveraging bidding signals during
+ * "generateBid()" phase and using the same currency during the creation of contextual ads.
+ * Having currency unit as a dedicated field could be supported in future releases.
+ *
+ * @return the bid value to be passed to the scoring function when scoring the ad returned by
+ * {@link #getAdData()}
+ */
+ public double getBid() {
+ return mBid;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ Objects.requireNonNull(dest);
+
+ mAdData.writeToParcel(dest, flags);
+ dest.writeDouble(mBid);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof AdWithBid)) return false;
+ AdWithBid adWithBid = (AdWithBid) o;
+ return Double.compare(adWithBid.mBid, mBid) == 0
+ && Objects.equals(mAdData, adWithBid.mAdData);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mAdData, mBid);
+ }
+}
diff --git a/android-35/android/adservices/adselection/AddAdSelectionFromOutcomesOverrideRequest.java b/android-35/android/adservices/adselection/AddAdSelectionFromOutcomesOverrideRequest.java
new file mode 100644
index 0000000..7e4f97f
--- /dev/null
+++ b/android-35/android/adservices/adselection/AddAdSelectionFromOutcomesOverrideRequest.java
@@ -0,0 +1,83 @@
+/*
+ * 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.adselection;
+
+import android.adservices.common.AdSelectionSignals;
+import android.annotation.NonNull;
+
+import java.util.Objects;
+
+/**
+ * This POJO represents the {@link
+ * TestAdSelectionManager#overrideAdSelectionFromOutcomesConfigRemoteInfo} (
+ * AddAdSelectionOverrideRequest, Executor, OutcomeReceiver)} request
+ *
+ * <p>It contains, a {@link AdSelectionFromOutcomesConfig} which will serve as the identifier for
+ * the specific override, a {@code String} selectionLogicJs and {@code String} selectionSignals
+ * field representing the override value
+ *
+ */
+public class AddAdSelectionFromOutcomesOverrideRequest {
+ @NonNull private final AdSelectionFromOutcomesConfig mAdSelectionFromOutcomesConfig;
+
+ @NonNull private final String mOutcomeSelectionLogicJs;
+
+ @NonNull private final AdSelectionSignals mOutcomeSelectionTrustedSignals;
+
+ /** Builds a {@link AddAdSelectionFromOutcomesOverrideRequest} instance. */
+ public AddAdSelectionFromOutcomesOverrideRequest(
+ @NonNull AdSelectionFromOutcomesConfig adSelectionFromOutcomesConfig,
+ @NonNull String outcomeSelectionLogicJs,
+ @NonNull AdSelectionSignals outcomeSelectionTrustedSignals) {
+ Objects.requireNonNull(adSelectionFromOutcomesConfig);
+ Objects.requireNonNull(outcomeSelectionLogicJs);
+ Objects.requireNonNull(outcomeSelectionTrustedSignals);
+
+ mAdSelectionFromOutcomesConfig = adSelectionFromOutcomesConfig;
+ mOutcomeSelectionLogicJs = outcomeSelectionLogicJs;
+ mOutcomeSelectionTrustedSignals = outcomeSelectionTrustedSignals;
+ }
+
+ /**
+ * @return an instance of {@link AdSelectionFromOutcomesConfig}, the configuration of the ad
+ * selection process. This configuration provides the data necessary to run Ad Selection
+ * flow that generates bids and scores to find a wining ad for rendering.
+ */
+ @NonNull
+ public AdSelectionFromOutcomesConfig getAdSelectionFromOutcomesConfig() {
+ return mAdSelectionFromOutcomesConfig;
+ }
+
+ /**
+ * @return The override javascript result, should be a string that contains valid JS code. The
+ * code should contain the outcome selection logic that will be executed during ad outcome
+ * selection.
+ */
+ @NonNull
+ public String getOutcomeSelectionLogicJs() {
+ return mOutcomeSelectionLogicJs;
+ }
+
+ /**
+ * @return The override trusted scoring signals, should be a valid json string. The trusted
+ * signals would be fed into the outcome selection logic during ad outcome selection.
+ */
+ @NonNull
+ public AdSelectionSignals getOutcomeSelectionTrustedSignals() {
+ return mOutcomeSelectionTrustedSignals;
+ }
+}
diff --git a/android-35/android/adservices/adselection/AddAdSelectionOverrideRequest.java b/android-35/android/adservices/adselection/AddAdSelectionOverrideRequest.java
new file mode 100644
index 0000000..f537643
--- /dev/null
+++ b/android-35/android/adservices/adselection/AddAdSelectionOverrideRequest.java
@@ -0,0 +1,132 @@
+/*
+ * 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.adselection;
+
+import android.adservices.common.AdSelectionSignals;
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.os.OutcomeReceiver;
+
+import com.android.adservices.flags.Flags;
+
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/**
+ * This POJO represents the {@link
+ * TestAdSelectionManager#overrideAdSelectionConfigRemoteInfo(AddAdSelectionOverrideRequest,
+ * Executor, OutcomeReceiver)} request
+ *
+ * <p>It contains, a {@link AdSelectionConfig} which will serve as the identifier for the specific
+ * override, a {@code String} decisionLogicJs and {@code String} trustedScoringSignals field
+ * representing the override value
+ */
+public class AddAdSelectionOverrideRequest {
+ @NonNull private final AdSelectionConfig mAdSelectionConfig;
+
+ @NonNull private final String mDecisionLogicJs;
+
+ @NonNull private final AdSelectionSignals mTrustedScoringSignals;
+
+ @NonNull private final PerBuyerDecisionLogic mPerBuyerDecisionLogic;
+
+ /**
+ * Builds a {@link AddAdSelectionOverrideRequest} instance.
+ *
+ * @param adSelectionConfig configuration for ad selection. See {@link AdSelectionConfig}
+ * @param decisionLogicJs override for scoring logic. See {@link
+ * AdSelectionConfig#getDecisionLogicUri()}
+ * @param trustedScoringSignals override for trusted seller signals. See {@link
+ * AdSelectionConfig#getTrustedScoringSignalsUri()}
+ * @param perBuyerDecisionLogic override for buyer's reporting logic for contextual ads. See
+ * {@link SignedContextualAds#getDecisionLogicUri()}
+ */
+ @FlaggedApi(Flags.FLAG_FLEDGE_AD_SELECTION_FILTERING_ENABLED)
+ public AddAdSelectionOverrideRequest(
+ @NonNull AdSelectionConfig adSelectionConfig,
+ @NonNull String decisionLogicJs,
+ @NonNull AdSelectionSignals trustedScoringSignals,
+ @NonNull PerBuyerDecisionLogic perBuyerDecisionLogic) {
+ Objects.requireNonNull(adSelectionConfig);
+ Objects.requireNonNull(decisionLogicJs);
+ Objects.requireNonNull(trustedScoringSignals);
+ Objects.requireNonNull(perBuyerDecisionLogic);
+
+ mAdSelectionConfig = adSelectionConfig;
+ mDecisionLogicJs = decisionLogicJs;
+ mTrustedScoringSignals = trustedScoringSignals;
+ mPerBuyerDecisionLogic = perBuyerDecisionLogic;
+ }
+
+ /**
+ * Builds a {@link AddAdSelectionOverrideRequest} instance.
+ *
+ * @param adSelectionConfig configuration for ad selection. See {@link AdSelectionConfig}
+ * @param decisionLogicJs override for scoring logic. See {@link
+ * AdSelectionConfig#getDecisionLogicUri()}
+ * @param trustedScoringSignals override for trusted seller signals. See {@link
+ * AdSelectionConfig#getTrustedScoringSignalsUri()}
+ */
+ public AddAdSelectionOverrideRequest(
+ @NonNull AdSelectionConfig adSelectionConfig,
+ @NonNull String decisionLogicJs,
+ @NonNull AdSelectionSignals trustedScoringSignals) {
+ this(
+ adSelectionConfig,
+ decisionLogicJs,
+ trustedScoringSignals,
+ PerBuyerDecisionLogic.EMPTY);
+ }
+
+ /**
+ * @return an instance of {@link AdSelectionConfig}, the configuration of the ad selection
+ * process. This configuration provides the data necessary to run Ad Selection flow that
+ * generates bids and scores to find a wining ad for rendering.
+ */
+ @NonNull
+ public AdSelectionConfig getAdSelectionConfig() {
+ return mAdSelectionConfig;
+ }
+
+ /**
+ * @return The override javascript result, should be a string that contains valid JS code. The
+ * code should contain the scoring logic that will be executed during Ad selection.
+ */
+ @NonNull
+ public String getDecisionLogicJs() {
+ return mDecisionLogicJs;
+ }
+
+ /**
+ * @return The override trusted scoring signals, should be a valid json string. The trusted
+ * signals would be fed into the scoring logic during Ad Selection.
+ */
+ @NonNull
+ public AdSelectionSignals getTrustedScoringSignals() {
+ return mTrustedScoringSignals;
+ }
+
+ /**
+ * @return The override for the decision logic for each buyer that is used by contextual ads for
+ * reporting, which may be extended to updating bid values for contextual ads in the future
+ */
+ @FlaggedApi(Flags.FLAG_FLEDGE_AD_SELECTION_FILTERING_ENABLED)
+ @NonNull
+ public PerBuyerDecisionLogic getPerBuyerDecisionLogic() {
+ return mPerBuyerDecisionLogic;
+ }
+}
diff --git a/android-35/android/adservices/adselection/DecisionLogic.java b/android-35/android/adservices/adselection/DecisionLogic.java
new file mode 100644
index 0000000..1cb2680
--- /dev/null
+++ b/android-35/android/adservices/adselection/DecisionLogic.java
@@ -0,0 +1,91 @@
+/*
+ * 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.adselection;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.adservices.flags.Flags;
+
+import java.util.Objects;
+
+/** Generic Decision logic that could be provided by the buyer or seller. */
+@FlaggedApi(Flags.FLAG_FLEDGE_AD_SELECTION_FILTERING_ENABLED)
+public final class DecisionLogic implements Parcelable {
+
+ @NonNull private String mDecisionLogic;
+
+ public DecisionLogic(@NonNull String buyerDecisionLogic) {
+ Objects.requireNonNull(buyerDecisionLogic);
+ mDecisionLogic = buyerDecisionLogic;
+ }
+
+ private DecisionLogic(@NonNull Parcel in) {
+ this(in.readString());
+ }
+
+ @NonNull
+ public static final Creator<DecisionLogic> CREATOR =
+ new Creator<DecisionLogic>() {
+ @Override
+ public DecisionLogic createFromParcel(Parcel in) {
+ Objects.requireNonNull(in);
+ return new DecisionLogic(in);
+ }
+
+ @Override
+ public DecisionLogic[] newArray(int size) {
+ return new DecisionLogic[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ Objects.requireNonNull(dest);
+ dest.writeString(mDecisionLogic);
+ }
+
+ @Override
+ public String toString() {
+ return mDecisionLogic;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mDecisionLogic);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof DecisionLogic)) return false;
+ DecisionLogic decisionLogic = (DecisionLogic) o;
+ return mDecisionLogic.equals(decisionLogic.getLogic());
+ }
+
+ @NonNull
+ public String getLogic() {
+ return mDecisionLogic;
+ }
+}
diff --git a/android-35/android/adservices/adselection/GetAdSelectionDataInput.java b/android-35/android/adservices/adselection/GetAdSelectionDataInput.java
new file mode 100644
index 0000000..98862a5
--- /dev/null
+++ b/android-35/android/adservices/adselection/GetAdSelectionDataInput.java
@@ -0,0 +1,184 @@
+/*
+ * 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.adselection;
+
+import android.adservices.common.AdTechIdentifier;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.adservices.AdServicesParcelableUtil;
+
+import java.util.Objects;
+
+/**
+ * Represent input params to the GetAdSelectionData API.
+ *
+ * @hide
+ */
+public final class GetAdSelectionDataInput implements Parcelable {
+ @Nullable private final AdTechIdentifier mSeller;
+ @NonNull private final String mCallerPackageName;
+
+ @Nullable private final Uri mCoordinatorOriginUri;
+
+ @NonNull
+ public static final Creator<GetAdSelectionDataInput> CREATOR =
+ new Creator<>() {
+ public GetAdSelectionDataInput createFromParcel(Parcel in) {
+ return new GetAdSelectionDataInput(in);
+ }
+
+ public GetAdSelectionDataInput[] newArray(int size) {
+ return new GetAdSelectionDataInput[size];
+ }
+ };
+
+ private GetAdSelectionDataInput(
+ @Nullable AdTechIdentifier seller,
+ @NonNull String callerPackageName,
+ @Nullable Uri coordinatorOriginUri) {
+ Objects.requireNonNull(callerPackageName);
+
+ this.mSeller = seller;
+ this.mCallerPackageName = callerPackageName;
+ this.mCoordinatorOriginUri = coordinatorOriginUri;
+ }
+
+ private GetAdSelectionDataInput(@NonNull Parcel in) {
+ Objects.requireNonNull(in);
+
+ this.mSeller =
+ AdServicesParcelableUtil.readNullableFromParcel(
+ in, AdTechIdentifier.CREATOR::createFromParcel);
+ this.mCallerPackageName = in.readString();
+ this.mCoordinatorOriginUri =
+ AdServicesParcelableUtil.readNullableFromParcel(in, Uri.CREATOR::createFromParcel);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof GetAdSelectionDataInput) {
+ GetAdSelectionDataInput obj = (GetAdSelectionDataInput) o;
+ return Objects.equals(mSeller, obj.mSeller)
+ && Objects.equals(mCallerPackageName, obj.mCallerPackageName)
+ && Objects.equals(mCoordinatorOriginUri, obj.mCoordinatorOriginUri);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mSeller, mCallerPackageName, mCoordinatorOriginUri);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ Objects.requireNonNull(dest);
+
+ AdServicesParcelableUtil.writeNullableToParcel(
+ dest,
+ mSeller,
+ (targetParcel, sourceSignals) -> sourceSignals.writeToParcel(targetParcel, flags));
+ dest.writeString(mCallerPackageName);
+ AdServicesParcelableUtil.writeNullableToParcel(
+ dest,
+ mCoordinatorOriginUri,
+ (targetParcel, sourceOrigin) -> sourceOrigin.writeToParcel(targetParcel, flags));
+ }
+
+ /**
+ * @return a AdTechIdentifier of the seller, for example "www.example-ssp.com"
+ */
+ @Nullable
+ public AdTechIdentifier getSeller() {
+ return mSeller;
+ }
+
+ /**
+ * @return the caller package name
+ */
+ @NonNull
+ public String getCallerPackageName() {
+ return mCallerPackageName;
+ }
+
+ /**
+ * @return the caller package name
+ */
+ @Nullable
+ public Uri getCoordinatorOriginUri() {
+ return mCoordinatorOriginUri;
+ }
+
+ /**
+ * Builder for {@link GetAdSelectionDataInput} objects.
+ *
+ * @hide
+ */
+ public static final class Builder {
+ @Nullable private AdTechIdentifier mSeller;
+ @Nullable private String mCallerPackageName;
+ @Nullable private Uri mCoordinatorOrigin;
+
+ public Builder() {}
+
+ /** Sets the seller {@link AdTechIdentifier}. */
+ @NonNull
+ public GetAdSelectionDataInput.Builder setSeller(@Nullable AdTechIdentifier seller) {
+ this.mSeller = seller;
+ return this;
+ }
+
+ /** Sets the caller's package name. */
+ @NonNull
+ public GetAdSelectionDataInput.Builder setCallerPackageName(
+ @NonNull String callerPackageName) {
+ Objects.requireNonNull(callerPackageName);
+
+ this.mCallerPackageName = callerPackageName;
+ return this;
+ }
+
+ /** Sets the coordinator origin URI . */
+ @NonNull
+ public GetAdSelectionDataInput.Builder setCoordinatorOriginUri(
+ @Nullable Uri coordinatorOrigin) {
+ this.mCoordinatorOrigin = coordinatorOrigin;
+ return this;
+ }
+
+ /**
+ * Builds a {@link GetAdSelectionDataInput} instance.
+ *
+ * @throws NullPointerException if the CallerPackageName is null
+ */
+ @NonNull
+ public GetAdSelectionDataInput build() {
+ Objects.requireNonNull(mCallerPackageName);
+
+ return new GetAdSelectionDataInput(mSeller, mCallerPackageName, mCoordinatorOrigin);
+ }
+ }
+}
diff --git a/android-35/android/adservices/adselection/GetAdSelectionDataOutcome.java b/android-35/android/adservices/adselection/GetAdSelectionDataOutcome.java
new file mode 100644
index 0000000..c721e56
--- /dev/null
+++ b/android-35/android/adservices/adselection/GetAdSelectionDataOutcome.java
@@ -0,0 +1,111 @@
+/*
+ * 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.adselection;
+
+import static android.adservices.adselection.AdSelectionOutcome.UNSET_AD_SELECTION_ID;
+import static android.adservices.adselection.AdSelectionOutcome.UNSET_AD_SELECTION_ID_MESSAGE;
+
+import android.annotation.FlaggedApi;
+import android.annotation.Nullable;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.Arrays;
+import java.util.Objects;
+
+/** Represents ad selection data collected from device for ad selection. */
+public final class GetAdSelectionDataOutcome {
+ private final long mAdSelectionId;
+ @Nullable private final byte[] mAdSelectionData;
+
+ private GetAdSelectionDataOutcome(long adSelectionId, @Nullable byte[] adSelectionData) {
+ this.mAdSelectionId = adSelectionId;
+ this.mAdSelectionData = adSelectionData;
+ }
+
+ /**
+ * Returns the adSelectionId that identifies the AdSelection.
+ *
+ * @deprecated Use the {@link #getAdSelectionDataId()} instead.
+ */
+ public long getAdSelectionId() {
+ return mAdSelectionId;
+ }
+
+ /** Returns the id that uniquely identifies this GetAdSelectionData payload. */
+ @FlaggedApi(
+ "com.android.adservices.flags.fledge_auction_server_get_ad_selection_data_id_enabled")
+ public long getAdSelectionDataId() {
+ return mAdSelectionId;
+ }
+
+ /** Returns the adSelectionData that is collected from device. */
+ @Nullable
+ public byte[] getAdSelectionData() {
+ if (Objects.isNull(mAdSelectionData)) {
+ return null;
+ } else {
+ return Arrays.copyOf(mAdSelectionData, mAdSelectionData.length);
+ }
+ }
+
+ /**
+ * Builder for {@link GetAdSelectionDataOutcome} objects.
+ *
+ * @hide
+ */
+ public static final class Builder {
+ private long mAdSelectionId;
+ @Nullable private byte[] mAdSelectionData;
+
+ public Builder() {}
+
+ /** Sets the adSelectionId. */
+ @NonNull
+ public GetAdSelectionDataOutcome.Builder setAdSelectionId(long adSelectionId) {
+ this.mAdSelectionId = adSelectionId;
+ return this;
+ }
+
+ /** Sets the adSelectionData. */
+ @NonNull
+ public GetAdSelectionDataOutcome.Builder setAdSelectionData(
+ @Nullable byte[] adSelectionData) {
+ if (!Objects.isNull(adSelectionData)) {
+ this.mAdSelectionData = Arrays.copyOf(adSelectionData, adSelectionData.length);
+ } else {
+ this.mAdSelectionData = null;
+ }
+ return this;
+ }
+
+ /**
+ * Builds a {@link GetAdSelectionDataOutcome} instance.
+ *
+ * @throws IllegalArgumentException if the adSelectionId is not set
+ */
+ @NonNull
+ public GetAdSelectionDataOutcome build() {
+ Preconditions.checkArgument(
+ mAdSelectionId != UNSET_AD_SELECTION_ID, UNSET_AD_SELECTION_ID_MESSAGE);
+
+ return new GetAdSelectionDataOutcome(mAdSelectionId, mAdSelectionData);
+ }
+ }
+}
diff --git a/android-35/android/adservices/adselection/GetAdSelectionDataRequest.java b/android-35/android/adservices/adselection/GetAdSelectionDataRequest.java
new file mode 100644
index 0000000..362ad83
--- /dev/null
+++ b/android-35/android/adservices/adselection/GetAdSelectionDataRequest.java
@@ -0,0 +1,106 @@
+/*
+ * 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.adselection;
+
+import android.adservices.common.AdTechIdentifier;
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.Uri;
+
+import com.android.adservices.flags.Flags;
+
+/**
+ * Represents a request containing the information to get ad selection data.
+ *
+ * <p>Instances of this class are created by SDKs to be provided as arguments to the {@link
+ * AdSelectionManager#getAdSelectionData} methods in {@link AdSelectionManager}.
+ */
+public final class GetAdSelectionDataRequest {
+ @Nullable private final AdTechIdentifier mSeller;
+
+ @Nullable private final Uri mCoordinatorOriginUri;
+
+ private GetAdSelectionDataRequest(
+ @Nullable AdTechIdentifier seller, @Nullable Uri coordinatorOriginUri) {
+ this.mSeller = seller;
+ this.mCoordinatorOriginUri = coordinatorOriginUri;
+ }
+
+ /**
+ * @return a AdTechIdentifier of the seller, for example "www.example-ssp.com"
+ */
+ @Nullable
+ public AdTechIdentifier getSeller() {
+ return mSeller;
+ }
+
+ /**
+ * @return the coordinator origin Uri where the public keys for encryption are fetched from
+ * <p>See {@link Builder#setCoordinatorOriginUri(Uri)} for more details on the coordinator
+ * origin
+ */
+ @Nullable
+ @FlaggedApi(Flags.FLAG_FLEDGE_SERVER_AUCTION_MULTI_CLOUD_ENABLED)
+ public Uri getCoordinatorOriginUri() {
+ return mCoordinatorOriginUri;
+ }
+
+ /**
+ * Builder for {@link GetAdSelectionDataRequest} objects.
+ */
+ public static final class Builder {
+ @Nullable private AdTechIdentifier mSeller;
+
+ @Nullable private Uri mCoordinatorOriginUri;
+
+ public Builder() {}
+
+ /** Sets the seller {@link AdTechIdentifier}. */
+ @NonNull
+ public GetAdSelectionDataRequest.Builder setSeller(@Nullable AdTechIdentifier seller) {
+ this.mSeller = seller;
+ return this;
+ }
+
+ /**
+ * Sets the coordinator origin from which PPAPI should fetch the public key for payload
+ * encryption. The origin must use HTTPS URI.
+ *
+ * <p>The origin will only contain the scheme, hostname and port of the URL. If the origin
+ * is not provided or is null, PPAPI will use the default coordinator URI.
+ *
+ * <p>The origin must belong to a list of pre-approved coordinator origins. Otherwise,
+ * {@link AdSelectionManager#getAdSelectionData} will throw an IllegalArgumentException
+ */
+ @NonNull
+ @FlaggedApi(Flags.FLAG_FLEDGE_SERVER_AUCTION_MULTI_CLOUD_ENABLED)
+ public GetAdSelectionDataRequest.Builder setCoordinatorOriginUri(
+ @Nullable Uri coordinatorOriginUri) {
+ this.mCoordinatorOriginUri = coordinatorOriginUri;
+ return this;
+ }
+
+ /**
+ * Builds a {@link GetAdSelectionDataRequest} instance.
+ */
+ @NonNull
+ public GetAdSelectionDataRequest build() {
+ return new GetAdSelectionDataRequest(mSeller, mCoordinatorOriginUri);
+ }
+ }
+}
diff --git a/android-35/android/adservices/adselection/GetAdSelectionDataResponse.java b/android-35/android/adservices/adselection/GetAdSelectionDataResponse.java
new file mode 100644
index 0000000..3f95a97
--- /dev/null
+++ b/android-35/android/adservices/adselection/GetAdSelectionDataResponse.java
@@ -0,0 +1,188 @@
+/*
+ * 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.adselection;
+
+import static android.adservices.adselection.AdSelectionOutcome.UNSET_AD_SELECTION_ID;
+import static android.adservices.adselection.AdSelectionOutcome.UNSET_AD_SELECTION_ID_MESSAGE;
+
+import android.annotation.Nullable;
+import android.content.res.AssetFileDescriptor;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+import com.android.adservices.AdServicesParcelableUtil;
+import com.android.internal.util.Preconditions;
+
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * Represents ad selection data collected from device for ad selection.
+ *
+ * @hide
+ */
+public final class GetAdSelectionDataResponse implements Parcelable {
+ private final long mAdSelectionId;
+ @Nullable private final byte[] mAdSelectionData;
+ @Nullable private final AssetFileDescriptor mAssetFileDescriptor;
+
+ public static final Creator<GetAdSelectionDataResponse> CREATOR =
+ new Creator<>() {
+ @Override
+ public GetAdSelectionDataResponse createFromParcel(Parcel in) {
+ Objects.requireNonNull(in);
+
+ return new GetAdSelectionDataResponse(in);
+ }
+
+ @Override
+ public GetAdSelectionDataResponse[] newArray(int size) {
+ return new GetAdSelectionDataResponse[size];
+ }
+ };
+
+ private GetAdSelectionDataResponse(
+ long adSelectionId, byte[] adSelectionData, AssetFileDescriptor assetFileDescriptor) {
+ this.mAdSelectionId = adSelectionId;
+ this.mAdSelectionData = adSelectionData;
+ this.mAssetFileDescriptor = assetFileDescriptor;
+ }
+
+ private GetAdSelectionDataResponse(@NonNull Parcel in) {
+ Objects.requireNonNull(in);
+
+ this.mAdSelectionId = in.readLong();
+ this.mAdSelectionData = in.createByteArray();
+ this.mAssetFileDescriptor =
+ AdServicesParcelableUtil.readNullableFromParcel(
+ in, AssetFileDescriptor.CREATOR::createFromParcel);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof GetAdSelectionDataResponse) {
+ GetAdSelectionDataResponse response = (GetAdSelectionDataResponse) o;
+ return mAdSelectionId == response.mAdSelectionId
+ && Arrays.equals(mAdSelectionData, response.mAdSelectionData)
+ && Objects.equals(mAssetFileDescriptor, response.mAssetFileDescriptor);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(
+ mAdSelectionId, Arrays.hashCode(mAdSelectionData), mAssetFileDescriptor);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Returns the adSelectionId that identifies the AdSelection. */
+ public long getAdSelectionId() {
+ return mAdSelectionId;
+ }
+
+ /** Returns the adSelectionData that is collected from device. */
+ @Nullable
+ public byte[] getAdSelectionData() {
+ if (Objects.isNull(mAdSelectionData)) {
+ return null;
+ } else {
+ return Arrays.copyOf(mAdSelectionData, mAdSelectionData.length);
+ }
+ }
+
+ /**
+ * Returns the {@link AssetFileDescriptor} that points to a piece of memory where the
+ * adSelectionData is stored
+ */
+ @Nullable
+ public AssetFileDescriptor getAssetFileDescriptor() {
+ return mAssetFileDescriptor;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ Objects.requireNonNull(dest);
+
+ dest.writeLong(mAdSelectionId);
+ dest.writeByteArray(mAdSelectionData);
+ AdServicesParcelableUtil.writeNullableToParcel(
+ dest,
+ mAssetFileDescriptor,
+ (targetParcel, sourceSignals) -> sourceSignals.writeToParcel(targetParcel, flags));
+ }
+
+ /**
+ * Builder for {@link GetAdSelectionDataResponse} objects.
+ *
+ * @hide
+ */
+ public static final class Builder {
+ private long mAdSelectionId;
+ @Nullable private byte[] mAdSelectionData;
+ @Nullable private AssetFileDescriptor mAssetFileDescriptor;
+
+ public Builder() {}
+
+ /** Sets the adSelectionId. */
+ @NonNull
+ public GetAdSelectionDataResponse.Builder setAdSelectionId(long adSelectionId) {
+ this.mAdSelectionId = adSelectionId;
+ return this;
+ }
+
+ /** Sets the adSelectionData. */
+ @NonNull
+ public GetAdSelectionDataResponse.Builder setAdSelectionData(
+ @Nullable byte[] adSelectionData) {
+ if (!Objects.isNull(adSelectionData)) {
+ this.mAdSelectionData = Arrays.copyOf(adSelectionData, adSelectionData.length);
+ } else {
+ this.mAdSelectionData = null;
+ }
+ return this;
+ }
+
+ /** Sets the assetFileDescriptor */
+ @NonNull
+ public GetAdSelectionDataResponse.Builder setAssetFileDescriptor(
+ @Nullable AssetFileDescriptor assetFileDescriptor) {
+ this.mAssetFileDescriptor = assetFileDescriptor;
+ return this;
+ }
+
+ /**
+ * Builds a {@link GetAdSelectionDataResponse} instance.
+ *
+ * @throws IllegalArgumentException if the adSelectionId is not set
+ */
+ @NonNull
+ public GetAdSelectionDataResponse build() {
+ Preconditions.checkArgument(
+ mAdSelectionId != UNSET_AD_SELECTION_ID, UNSET_AD_SELECTION_ID_MESSAGE);
+
+ return new GetAdSelectionDataResponse(
+ mAdSelectionId, mAdSelectionData, mAssetFileDescriptor);
+ }
+ }
+}
diff --git a/android-35/android/adservices/adselection/PerBuyerDecisionLogic.java b/android-35/android/adservices/adselection/PerBuyerDecisionLogic.java
new file mode 100644
index 0000000..dd3d705
--- /dev/null
+++ b/android-35/android/adservices/adselection/PerBuyerDecisionLogic.java
@@ -0,0 +1,112 @@
+/*
+ * 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.adselection;
+
+import android.adservices.common.AdTechIdentifier;
+import android.adservices.customaudience.CustomAudience;
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.adservices.AdServicesParcelableUtil;
+import com.android.adservices.flags.Flags;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * The override object for decision logic JS per buyer for {@link SignedContextualAds}.
+ *
+ * <p>This decision logic is used for reporting when an ad wins from a buyer's bundle of {@link
+ * SignedContextualAds}.
+ *
+ * <p>This JS code may be extended to updating bid values for contextual ads in the future.
+ *
+ * <p>See {@link CustomAudience#getBiddingLogicUri()}.
+ */
+@FlaggedApi(Flags.FLAG_FLEDGE_AD_SELECTION_FILTERING_ENABLED)
+public final class PerBuyerDecisionLogic implements Parcelable {
+
+ @NonNull
+ public static final PerBuyerDecisionLogic EMPTY =
+ new PerBuyerDecisionLogic(Collections.emptyMap());
+
+ @NonNull private final Map<AdTechIdentifier, DecisionLogic> mPerBuyerLogicMap;
+
+ /**
+ * Builds a {@link PerBuyerDecisionLogic} instance.
+ *
+ * @param perBuyerLogicMap map of buyers and their decision logic to be fetched during ad
+ * selection
+ */
+ public PerBuyerDecisionLogic(@NonNull Map<AdTechIdentifier, DecisionLogic> perBuyerLogicMap) {
+ Objects.requireNonNull(perBuyerLogicMap);
+ mPerBuyerLogicMap = perBuyerLogicMap;
+ }
+
+ private PerBuyerDecisionLogic(@NonNull Parcel in) {
+ mPerBuyerLogicMap =
+ AdServicesParcelableUtil.readMapFromParcel(
+ in, AdTechIdentifier::fromString, DecisionLogic.class);
+ }
+
+ @NonNull
+ public static final Creator<PerBuyerDecisionLogic> CREATOR =
+ new Creator<PerBuyerDecisionLogic>() {
+ @Override
+ public PerBuyerDecisionLogic createFromParcel(Parcel in) {
+ Objects.requireNonNull(in);
+ return new PerBuyerDecisionLogic(in);
+ }
+
+ @Override
+ public PerBuyerDecisionLogic[] newArray(int size) {
+ return new PerBuyerDecisionLogic[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ Objects.requireNonNull(dest);
+ AdServicesParcelableUtil.writeMapToParcel(dest, mPerBuyerLogicMap);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mPerBuyerLogicMap);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof PerBuyerDecisionLogic)) return false;
+ PerBuyerDecisionLogic logicMap = (PerBuyerDecisionLogic) o;
+ return mPerBuyerLogicMap.equals(logicMap.getPerBuyerLogicMap());
+ }
+
+ @NonNull
+ public Map<AdTechIdentifier, DecisionLogic> getPerBuyerLogicMap() {
+ return mPerBuyerLogicMap;
+ }
+}
diff --git a/android-35/android/adservices/adselection/PersistAdSelectionResultInput.java b/android-35/android/adservices/adselection/PersistAdSelectionResultInput.java
new file mode 100644
index 0000000..de0c459
--- /dev/null
+++ b/android-35/android/adservices/adselection/PersistAdSelectionResultInput.java
@@ -0,0 +1,218 @@
+/*
+ * 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.adselection;
+
+import static android.adservices.adselection.AdSelectionOutcome.UNSET_AD_SELECTION_ID;
+import static android.adservices.adselection.AdSelectionOutcome.UNSET_AD_SELECTION_ID_MESSAGE;
+
+import android.adservices.common.AdTechIdentifier;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.adservices.AdServicesParcelableUtil;
+import com.android.internal.util.Preconditions;
+
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * Represents input params to the PersistAdSelectionResult API.
+ *
+ * @hide
+ */
+public final class PersistAdSelectionResultInput implements Parcelable {
+ private final long mAdSelectionId;
+ @Nullable private final AdTechIdentifier mSeller;
+ @Nullable private final byte[] mAdSelectionResult;
+ @NonNull private final String mCallerPackageName;
+
+ @NonNull
+ public static final Creator<PersistAdSelectionResultInput> CREATOR =
+ new Creator<>() {
+ public PersistAdSelectionResultInput createFromParcel(Parcel in) {
+ return new PersistAdSelectionResultInput(in);
+ }
+
+ public PersistAdSelectionResultInput[] newArray(int size) {
+ return new PersistAdSelectionResultInput[size];
+ }
+ };
+
+ private PersistAdSelectionResultInput(
+ long adSelectionId,
+ @Nullable AdTechIdentifier seller,
+ @Nullable byte[] adSelectionResult,
+ @NonNull String callerPackageName) {
+ Objects.requireNonNull(callerPackageName);
+
+ this.mAdSelectionId = adSelectionId;
+ this.mSeller = seller;
+ this.mAdSelectionResult = adSelectionResult;
+ this.mCallerPackageName = callerPackageName;
+ }
+
+ private PersistAdSelectionResultInput(@NonNull Parcel in) {
+ Objects.requireNonNull(in);
+
+ this.mAdSelectionId = in.readLong();
+ this.mSeller =
+ AdServicesParcelableUtil.readNullableFromParcel(
+ in, AdTechIdentifier.CREATOR::createFromParcel);
+ this.mAdSelectionResult = in.createByteArray();
+ this.mCallerPackageName = in.readString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof PersistAdSelectionResultInput) {
+ PersistAdSelectionResultInput obj = (PersistAdSelectionResultInput) o;
+ return mAdSelectionId == obj.mAdSelectionId
+ && Objects.equals(mSeller, obj.mSeller)
+ && Arrays.equals(mAdSelectionResult, obj.mAdSelectionResult)
+ && Objects.equals(mCallerPackageName, obj.mCallerPackageName);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(
+ mAdSelectionId, mSeller, Arrays.hashCode(mAdSelectionResult), mCallerPackageName);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ Objects.requireNonNull(dest);
+
+ dest.writeLong(mAdSelectionId);
+ AdServicesParcelableUtil.writeNullableToParcel(
+ dest,
+ mSeller,
+ (targetParcel, sourceSignals) -> sourceSignals.writeToParcel(targetParcel, flags));
+ dest.writeByteArray(mAdSelectionResult);
+ dest.writeString(mCallerPackageName);
+ }
+
+ /**
+ * @return an ad selection id.
+ */
+ public long getAdSelectionId() {
+ return mAdSelectionId;
+ }
+
+ /**
+ * @return a seller.
+ */
+ @Nullable
+ public AdTechIdentifier getSeller() {
+ return mSeller;
+ }
+
+ /**
+ * @return an ad selection result.
+ */
+ @Nullable
+ public byte[] getAdSelectionResult() {
+ if (Objects.isNull(mAdSelectionResult)) {
+ return null;
+ } else {
+ return Arrays.copyOf(mAdSelectionResult, mAdSelectionResult.length);
+ }
+ }
+
+ /**
+ * @return the caller package name
+ */
+ @NonNull
+ public String getCallerPackageName() {
+ return mCallerPackageName;
+ }
+
+ /**
+ * Builder for {@link PersistAdSelectionResultInput} objects.
+ *
+ * @hide
+ */
+ public static final class Builder {
+ private long mAdSelectionId;
+ @Nullable private AdTechIdentifier mSeller;
+ @Nullable private byte[] mAdSelectionResult;
+ @Nullable private String mCallerPackageName;
+
+ public Builder() {}
+
+ /** Sets the ad selection id {@link Long}. */
+ @NonNull
+ public PersistAdSelectionResultInput.Builder setAdSelectionId(long adSelectionId) {
+ this.mAdSelectionId = adSelectionId;
+ return this;
+ }
+
+ /** Sets the seller {@link AdTechIdentifier}. */
+ @NonNull
+ public PersistAdSelectionResultInput.Builder setSeller(@Nullable AdTechIdentifier seller) {
+ this.mSeller = seller;
+ return this;
+ }
+
+ /** Sets the ad selection result {@link String}. */
+ @NonNull
+ public PersistAdSelectionResultInput.Builder setAdSelectionResult(
+ @Nullable byte[] adSelectionResult) {
+ if (!Objects.isNull(adSelectionResult)) {
+ this.mAdSelectionResult =
+ Arrays.copyOf(adSelectionResult, adSelectionResult.length);
+ } else {
+ this.mAdSelectionResult = null;
+ }
+ return this;
+ }
+
+ /** Sets the caller's package name. */
+ @NonNull
+ public PersistAdSelectionResultInput.Builder setCallerPackageName(
+ @NonNull String callerPackageName) {
+ Objects.requireNonNull(callerPackageName);
+
+ this.mCallerPackageName = callerPackageName;
+ return this;
+ }
+
+ /**
+ * Builds a {@link PersistAdSelectionResultInput} instance.
+ *
+ * @throws IllegalArgumentException if the adSelectionId is not set
+ * @throws NullPointerException if the CallerPackageName is null
+ */
+ @NonNull
+ public PersistAdSelectionResultInput build() {
+ Objects.requireNonNull(mCallerPackageName);
+ Preconditions.checkArgument(
+ mAdSelectionId != UNSET_AD_SELECTION_ID, UNSET_AD_SELECTION_ID_MESSAGE);
+
+ return new PersistAdSelectionResultInput(
+ mAdSelectionId, mSeller, mAdSelectionResult, mCallerPackageName);
+ }
+ }
+}
diff --git a/android-35/android/adservices/adselection/PersistAdSelectionResultRequest.java b/android-35/android/adservices/adselection/PersistAdSelectionResultRequest.java
new file mode 100644
index 0000000..b74b756
--- /dev/null
+++ b/android-35/android/adservices/adselection/PersistAdSelectionResultRequest.java
@@ -0,0 +1,157 @@
+/*
+ * 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.adselection;
+
+import static android.adservices.adselection.AdSelectionOutcome.UNSET_AD_SELECTION_ID;
+import static android.adservices.adselection.AdSelectionOutcome.UNSET_AD_SELECTION_ID_MESSAGE;
+
+import android.adservices.common.AdTechIdentifier;
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.OutcomeReceiver;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/**
+ * Represents a request containing the seller, the ad selection data id and data.
+ *
+ * <p>Instances of this class are created by SDKs to be provided as arguments to the {@link
+ * AdSelectionManager#persistAdSelectionResult} methods in {@link AdSelectionManager}.
+ */
+public final class PersistAdSelectionResultRequest {
+ private final long mAdSelectionId;
+ @Nullable private final AdTechIdentifier mSeller;
+ @Nullable private final byte[] mAdSelectionResult;
+
+ private PersistAdSelectionResultRequest(
+ long adSelectionId,
+ @Nullable AdTechIdentifier seller,
+ @Nullable byte[] adSelectionResult) {
+ this.mAdSelectionId = adSelectionId;
+ this.mSeller = seller;
+ this.mAdSelectionResult = adSelectionResult;
+ }
+
+ /**
+ * @return an ad selection id.
+ * @deprecated Use the {@link #getAdSelectionDataId()} instead, the underlying value is enforced
+ * to be the same.
+ */
+ public long getAdSelectionId() {
+ return mAdSelectionId;
+ }
+
+ /**
+ * Returns the id that identifies the {@link
+ * AdSelectionManager#getAdSelectionData(GetAdSelectionDataRequest, Executor, OutcomeReceiver)}
+ * payload that generated this result.
+ */
+ @FlaggedApi(
+ "com.android.adservices.flags.fledge_auction_server_get_ad_selection_data_id_enabled")
+ public long getAdSelectionDataId() {
+ return mAdSelectionId;
+ }
+
+ /**
+ * @return a seller.
+ */
+ @Nullable
+ public AdTechIdentifier getSeller() {
+ return mSeller;
+ }
+
+ /**
+ * @return an ad selection result.
+ */
+ @Nullable
+ public byte[] getAdSelectionResult() {
+ if (Objects.isNull(mAdSelectionResult)) {
+ return null;
+ } else {
+ return Arrays.copyOf(mAdSelectionResult, mAdSelectionResult.length);
+ }
+ }
+
+ /** Builder for {@link PersistAdSelectionResultRequest} objects. */
+ public static final class Builder {
+ private long mAdSelectionId;
+ @Nullable private AdTechIdentifier mSeller;
+ @Nullable private byte[] mAdSelectionResult;
+
+ public Builder() {}
+
+ /**
+ * Sets the ad selection id {@link Long}.
+ *
+ * @deprecated Use the {@link #setAdSelectionDataId(long)} instead.
+ */
+ @NonNull
+ public PersistAdSelectionResultRequest.Builder setAdSelectionId(long adSelectionId) {
+ this.mAdSelectionId = adSelectionId;
+ return this;
+ }
+
+ /** Sets the ad selection data id {@link Long}. */
+ @NonNull
+ @FlaggedApi(
+ "com.android.adservices.flags.fledge_auction_server_get_ad_selection_data_id_enabled")
+ public PersistAdSelectionResultRequest.Builder setAdSelectionDataId(
+ long adSelectionDataId) {
+ this.mAdSelectionId = adSelectionDataId;
+ return this;
+ }
+
+ /** Sets the seller {@link AdTechIdentifier}. */
+ @NonNull
+ public PersistAdSelectionResultRequest.Builder setSeller(
+ @Nullable AdTechIdentifier seller) {
+ this.mSeller = seller;
+ return this;
+ }
+
+ /** Sets the ad selection result {@link String}. */
+ @NonNull
+ public PersistAdSelectionResultRequest.Builder setAdSelectionResult(
+ @Nullable byte[] adSelectionResult) {
+ if (!Objects.isNull(adSelectionResult)) {
+ this.mAdSelectionResult =
+ Arrays.copyOf(adSelectionResult, adSelectionResult.length);
+ } else {
+ this.mAdSelectionResult = null;
+ }
+ return this;
+ }
+
+ /**
+ * Builds a {@link PersistAdSelectionResultRequest} instance.
+ *
+ * @throws IllegalArgumentException if the adSelectionIid is not set
+ */
+ @NonNull
+ public PersistAdSelectionResultRequest build() {
+ Preconditions.checkArgument(
+ mAdSelectionId != UNSET_AD_SELECTION_ID, UNSET_AD_SELECTION_ID_MESSAGE);
+
+ return new PersistAdSelectionResultRequest(mAdSelectionId, mSeller, mAdSelectionResult);
+ }
+ }
+}
diff --git a/android-35/android/adservices/adselection/PersistAdSelectionResultResponse.java b/android-35/android/adservices/adselection/PersistAdSelectionResultResponse.java
new file mode 100644
index 0000000..98e030a
--- /dev/null
+++ b/android-35/android/adservices/adselection/PersistAdSelectionResultResponse.java
@@ -0,0 +1,151 @@
+/*
+ * 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.adselection;
+
+import static android.adservices.adselection.AdSelectionOutcome.UNSET_AD_SELECTION_ID;
+import static android.adservices.adselection.AdSelectionOutcome.UNSET_AD_SELECTION_ID_MESSAGE;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.Objects;
+
+/**
+ * Represents the response from persistAdSelectionResult.
+ *
+ * @hide
+ */
+public final class PersistAdSelectionResultResponse implements Parcelable {
+ private final long mAdSelectionId;
+ @NonNull private final Uri mAdRenderUri;
+
+ public static final Creator<PersistAdSelectionResultResponse> CREATOR =
+ new Creator<>() {
+ @Override
+ public PersistAdSelectionResultResponse createFromParcel(Parcel in) {
+ Objects.requireNonNull(in);
+
+ return new PersistAdSelectionResultResponse(in);
+ }
+
+ @Override
+ public PersistAdSelectionResultResponse[] newArray(int size) {
+ return new PersistAdSelectionResultResponse[size];
+ }
+ };
+
+ private PersistAdSelectionResultResponse(long adSelectionId, @NonNull Uri adRenderUri) {
+ Objects.requireNonNull(adRenderUri);
+
+ this.mAdSelectionId = adSelectionId;
+ this.mAdRenderUri = adRenderUri;
+ }
+
+ private PersistAdSelectionResultResponse(@NonNull Parcel in) {
+ Objects.requireNonNull(in);
+
+ this.mAdSelectionId = in.readLong();
+ this.mAdRenderUri = Uri.CREATOR.createFromParcel(in);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof PersistAdSelectionResultResponse) {
+ PersistAdSelectionResultResponse response = (PersistAdSelectionResultResponse) o;
+ return mAdSelectionId == response.mAdSelectionId
+ && Objects.equals(mAdRenderUri, response.mAdRenderUri);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mAdSelectionId, mAdRenderUri);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Returns the adSelectionId that identifies the AdSelection. */
+ public long getAdSelectionId() {
+ return mAdSelectionId;
+ }
+
+ /** Returns the adSelectionData that is collected from device. */
+ public Uri getAdRenderUri() {
+ return mAdRenderUri;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ Objects.requireNonNull(dest);
+ Objects.requireNonNull(mAdRenderUri);
+
+ dest.writeLong(mAdSelectionId);
+ mAdRenderUri.writeToParcel(dest, flags);
+ }
+
+ /**
+ * Builder for {@link PersistAdSelectionResultResponse} objects.
+ *
+ * @hide
+ */
+ public static final class Builder {
+ private long mAdSelectionId;
+ @Nullable private Uri mAdRenderUri;
+
+ public Builder() {}
+
+ /** Sets the adSelectionId. */
+ @NonNull
+ public PersistAdSelectionResultResponse.Builder setAdSelectionId(long adSelectionId) {
+ this.mAdSelectionId = adSelectionId;
+ return this;
+ }
+
+ /** Sets the adRenderUri. */
+ @NonNull
+ public PersistAdSelectionResultResponse.Builder setAdRenderUri(@NonNull Uri adRenderUri) {
+ Objects.requireNonNull(adRenderUri);
+
+ this.mAdRenderUri = adRenderUri;
+ return this;
+ }
+
+ /**
+ * Builds a {@link PersistAdSelectionResultResponse} instance.
+ *
+ * @throws IllegalArgumentException if the adSelectionId is not set
+ * @throws NullPointerException if the RenderUri is null
+ */
+ @NonNull
+ public PersistAdSelectionResultResponse build() {
+ Objects.requireNonNull(mAdRenderUri);
+ Preconditions.checkArgument(
+ mAdSelectionId != UNSET_AD_SELECTION_ID, UNSET_AD_SELECTION_ID_MESSAGE);
+
+ return new PersistAdSelectionResultResponse(mAdSelectionId, mAdRenderUri);
+ }
+ }
+}
diff --git a/android-35/android/adservices/adselection/RemoveAdCounterHistogramOverrideInput.java b/android-35/android/adservices/adselection/RemoveAdCounterHistogramOverrideInput.java
new file mode 100644
index 0000000..dfb2570
--- /dev/null
+++ b/android-35/android/adservices/adselection/RemoveAdCounterHistogramOverrideInput.java
@@ -0,0 +1,205 @@
+/*
+ * 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.adselection;
+
+import static android.adservices.adselection.SetAdCounterHistogramOverrideRequest.NULL_BUYER_MESSAGE;
+import static android.adservices.adselection.UpdateAdCounterHistogramRequest.UNSET_AD_EVENT_TYPE_MESSAGE;
+import static android.adservices.common.FrequencyCapFilters.AD_EVENT_TYPE_INVALID;
+
+import android.adservices.common.AdTechIdentifier;
+import android.adservices.common.FrequencyCapFilters;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.Objects;
+
+/**
+ * Input object for removing ad counter histogram overrides.
+ *
+ * <p>Histogram overrides replace actual ad counter histograms used in ad selection. Overrides may
+ * only be set in debuggable apps on phones running a debuggable OS build with developer options
+ * enabled. Overrides are only available from the calling app.
+ *
+ * @hide
+ */
+public final class RemoveAdCounterHistogramOverrideInput implements Parcelable {
+ @FrequencyCapFilters.AdEventType private final int mAdEventType;
+ private final int mAdCounterKey;
+ @NonNull private final AdTechIdentifier mBuyer;
+
+ @NonNull
+ public static final Creator<RemoveAdCounterHistogramOverrideInput> CREATOR =
+ new Creator<RemoveAdCounterHistogramOverrideInput>() {
+ @Override
+ public RemoveAdCounterHistogramOverrideInput createFromParcel(@NonNull Parcel in) {
+ Objects.requireNonNull(in);
+
+ return new RemoveAdCounterHistogramOverrideInput(in);
+ }
+
+ @Override
+ public RemoveAdCounterHistogramOverrideInput[] newArray(int size) {
+ return new RemoveAdCounterHistogramOverrideInput[size];
+ }
+ };
+
+ private RemoveAdCounterHistogramOverrideInput(@NonNull Builder builder) {
+ Objects.requireNonNull(builder);
+
+ mAdEventType = builder.mAdEventType;
+ mAdCounterKey = builder.mAdCounterKey;
+ mBuyer = builder.mBuyer;
+ }
+
+ private RemoveAdCounterHistogramOverrideInput(@NonNull Parcel in) {
+ Objects.requireNonNull(in);
+
+ mAdEventType = in.readInt();
+ mAdCounterKey = in.readInt();
+ mBuyer = AdTechIdentifier.fromString(in.readString());
+ }
+
+ /**
+ * Gets the {@link FrequencyCapFilters.AdEventType} for the ad counter histogram override.
+ *
+ * <p>The ad event type is used with the ad counter key from {@link #getAdCounterKey()} and the
+ * buyer adtech from {@link #getBuyer()} to specify which histogram to use in ad selection
+ * filtering. The ad event type would normally be specified by an app/SDK after a
+ * FLEDGE-selected ad is rendered.
+ */
+ @FrequencyCapFilters.AdEventType
+ public int getAdEventType() {
+ return mAdEventType;
+ }
+
+ /**
+ * Gets the ad counter key for the ad counter histogram override.
+ *
+ * <p>The ad counter key is used with the ad event type from {@link #getAdEventType()} and the
+ * buyer adtech from {@link #getBuyer()} to specify which histogram to use in ad selection
+ * filtering. The ad counter key would normally be specified by a custom audience ad to
+ * represent a grouping to filter on.
+ */
+ @NonNull
+ public int getAdCounterKey() {
+ return mAdCounterKey;
+ }
+
+ /**
+ * Gets the {@link AdTechIdentifier} for the buyer which owns the ad counter histogram.
+ *
+ * <p>During filtering in FLEDGE ad selection, ads can only use ad counter histogram data
+ * generated by the same buyer. For {@link FrequencyCapFilters#AD_EVENT_TYPE_WIN}, ad counter
+ * histogram data is further restricted to ads from the same custom audience, which is
+ * identified by the buyer, the custom audience's owner app package name, and the custom
+ * audience name.
+ */
+ @NonNull
+ public AdTechIdentifier getBuyer() {
+ return mBuyer;
+ }
+
+ /** @hide */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ return "RemoveAdCounterHistogramOverrideInput{"
+ + "mAdEventType="
+ + mAdEventType
+ + ", mAdCounterKey="
+ + mAdCounterKey
+ + ", mBuyer="
+ + mBuyer
+ + '}';
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ Objects.requireNonNull(dest);
+
+ dest.writeInt(mAdEventType);
+ dest.writeInt(mAdCounterKey);
+ dest.writeString(mBuyer.toString());
+ }
+
+ /** Builder for {@link RemoveAdCounterHistogramOverrideInput} objects. */
+ public static final class Builder {
+ @FrequencyCapFilters.AdEventType private int mAdEventType = AD_EVENT_TYPE_INVALID;
+ private int mAdCounterKey;
+ @Nullable private AdTechIdentifier mBuyer;
+
+ public Builder() {}
+
+ /**
+ * Sets the {@link FrequencyCapFilters.AdEventType} for the ad counter histogram override.
+ *
+ * <p>See {@link #getAdEventType()} for more information.
+ */
+ @NonNull
+ public Builder setAdEventType(@FrequencyCapFilters.AdEventType int adEventType) {
+ mAdEventType = adEventType;
+ return this;
+ }
+
+ /**
+ * Sets the ad counter key for the ad counter histogram override.
+ *
+ * <p>See {@link #getAdCounterKey()} for more information.
+ */
+ @NonNull
+ public Builder setAdCounterKey(int adCounterKey) {
+ mAdCounterKey = adCounterKey;
+ return this;
+ }
+
+ /**
+ * Sets the {@link AdTechIdentifier} for the buyer which owns the ad counter histogram.
+ *
+ * <p>See {@link #getBuyer()} for more information.
+ */
+ @NonNull
+ public Builder setBuyer(@NonNull AdTechIdentifier buyer) {
+ Objects.requireNonNull(buyer, NULL_BUYER_MESSAGE);
+ mBuyer = buyer;
+ return this;
+ }
+
+ /**
+ * Builds the {@link RemoveAdCounterHistogramOverrideInput} object.
+ *
+ * @throws NullPointerException if any parameters are not set
+ * @throws IllegalArgumentException if the ad event type is invalid
+ */
+ @NonNull
+ public RemoveAdCounterHistogramOverrideInput build()
+ throws NullPointerException, IllegalArgumentException {
+ Preconditions.checkArgument(
+ mAdEventType != AD_EVENT_TYPE_INVALID, UNSET_AD_EVENT_TYPE_MESSAGE);
+ Objects.requireNonNull(mBuyer, NULL_BUYER_MESSAGE);
+
+ return new RemoveAdCounterHistogramOverrideInput(this);
+ }
+ }
+}
diff --git a/android-35/android/adservices/adselection/RemoveAdCounterHistogramOverrideRequest.java b/android-35/android/adservices/adselection/RemoveAdCounterHistogramOverrideRequest.java
new file mode 100644
index 0000000..ab2e301
--- /dev/null
+++ b/android-35/android/adservices/adselection/RemoveAdCounterHistogramOverrideRequest.java
@@ -0,0 +1,165 @@
+/*
+ * 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.adselection;
+
+import static android.adservices.adselection.SetAdCounterHistogramOverrideRequest.NULL_BUYER_MESSAGE;
+import static android.adservices.adselection.UpdateAdCounterHistogramRequest.UNSET_AD_EVENT_TYPE_MESSAGE;
+import static android.adservices.common.FrequencyCapFilters.AD_EVENT_TYPE_INVALID;
+
+import android.adservices.common.AdTechIdentifier;
+import android.adservices.common.FrequencyCapFilters;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.Objects;
+
+/**
+ * Request object for removing ad counter histogram overrides.
+ *
+ * <p>Histogram overrides replace actual ad counter histograms used in ad selection. Overrides may
+ * only be set in debuggable apps on phones running a debuggable OS build with developer options
+ * enabled. Overrides are only available from the calling app.
+ *
+ * @hide
+ */
+// TODO(b/265204820): Unhide for frequency cap dev override API review
+public class RemoveAdCounterHistogramOverrideRequest {
+ @FrequencyCapFilters.AdEventType private final int mAdEventType;
+ private final int mAdCounterKey;
+ @NonNull private final AdTechIdentifier mBuyer;
+
+ private RemoveAdCounterHistogramOverrideRequest(@NonNull Builder builder) {
+ Objects.requireNonNull(builder);
+
+ mAdEventType = builder.mAdEventType;
+ mAdCounterKey = builder.mAdCounterKey;
+ mBuyer = builder.mBuyer;
+ }
+
+ /**
+ * Gets the {@link FrequencyCapFilters.AdEventType} for the ad counter histogram override.
+ *
+ * <p>The ad event type is used with the ad counter key from {@link #getAdCounterKey()} and the
+ * buyer adtech from {@link #getBuyer()} to specify which histogram to use in ad selection
+ * filtering. The ad event type would normally be specified by an app/SDK after a
+ * FLEDGE-selected ad is rendered.
+ */
+ @FrequencyCapFilters.AdEventType
+ public int getAdEventType() {
+ return mAdEventType;
+ }
+
+ /**
+ * Gets the ad counter key for the ad counter histogram override.
+ *
+ * <p>The ad counter key is used with the ad event type from {@link #getAdEventType()} and the
+ * buyer adtech from {@link #getBuyer()} to specify which histogram to use in ad selection
+ * filtering. The ad counter key would normally be specified by a custom audience ad to
+ * represent a grouping to filter on.
+ */
+ @NonNull
+ public int getAdCounterKey() {
+ return mAdCounterKey;
+ }
+
+ /**
+ * Gets the {@link AdTechIdentifier} for the buyer which owns the ad counter histogram.
+ *
+ * <p>During filtering in FLEDGE ad selection, ads can only use ad counter histogram data
+ * generated by the same buyer. For {@link FrequencyCapFilters#AD_EVENT_TYPE_WIN}, ad counter
+ * histogram data is further restricted to ads from the same custom audience, which is
+ * identified by the buyer, the custom audience's owner app package name, and the custom
+ * audience name.
+ */
+ @NonNull
+ public AdTechIdentifier getBuyer() {
+ return mBuyer;
+ }
+
+ @Override
+ public String toString() {
+ return "RemoveAdCounterHistogramOverrideRequest{"
+ + "mAdEventType="
+ + mAdEventType
+ + ", mAdCounterKey="
+ + mAdCounterKey
+ + ", mBuyer="
+ + mBuyer
+ + '}';
+ }
+
+ /** Builder for {@link RemoveAdCounterHistogramOverrideRequest} objects. */
+ public static final class Builder {
+ @FrequencyCapFilters.AdEventType private int mAdEventType = AD_EVENT_TYPE_INVALID;
+ private int mAdCounterKey;
+ @Nullable private AdTechIdentifier mBuyer;
+
+ public Builder() {}
+
+ /**
+ * Sets the {@link FrequencyCapFilters.AdEventType} for the ad counter histogram override.
+ *
+ * <p>See {@link #getAdEventType()} for more information.
+ */
+ @NonNull
+ public Builder setAdEventType(@FrequencyCapFilters.AdEventType int adEventType) {
+ mAdEventType = adEventType;
+ return this;
+ }
+
+ /**
+ * Sets the ad counter key for the ad counter histogram override.
+ *
+ * <p>See {@link #getAdCounterKey()} for more information.
+ */
+ @NonNull
+ public Builder setAdCounterKey(int adCounterKey) {
+ mAdCounterKey = adCounterKey;
+ return this;
+ }
+
+ /**
+ * Sets the {@link AdTechIdentifier} for the buyer which owns the ad counter histogram.
+ *
+ * <p>See {@link #getBuyer()} for more information.
+ */
+ @NonNull
+ public Builder setBuyer(@NonNull AdTechIdentifier buyer) {
+ Objects.requireNonNull(buyer, NULL_BUYER_MESSAGE);
+ mBuyer = buyer;
+ return this;
+ }
+
+ /**
+ * Builds the {@link RemoveAdCounterHistogramOverrideRequest} object.
+ *
+ * @throws NullPointerException if any parameters are not set
+ * @throws IllegalArgumentException if the ad event type is invalid
+ */
+ @NonNull
+ public RemoveAdCounterHistogramOverrideRequest build()
+ throws NullPointerException, IllegalArgumentException {
+ Preconditions.checkArgument(
+ mAdEventType != AD_EVENT_TYPE_INVALID, UNSET_AD_EVENT_TYPE_MESSAGE);
+ Objects.requireNonNull(mBuyer, NULL_BUYER_MESSAGE);
+
+ return new RemoveAdCounterHistogramOverrideRequest(this);
+ }
+ }
+}
diff --git a/android-35/android/adservices/adselection/RemoveAdSelectionFromOutcomesOverrideRequest.java b/android-35/android/adservices/adselection/RemoveAdSelectionFromOutcomesOverrideRequest.java
new file mode 100644
index 0000000..8775b54
--- /dev/null
+++ b/android-35/android/adservices/adselection/RemoveAdSelectionFromOutcomesOverrideRequest.java
@@ -0,0 +1,44 @@
+/*
+ * 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.adselection;
+
+import android.annotation.NonNull;
+
+/**
+ * This POJO represents the {@link TestAdSelectionManager
+ * #removeAdSelectionFromOutcomesConfigRemoteInfoOverride(
+ * RemoveAdSelectionFromOutcomesOverrideRequest, Executor, OutcomeReceiver)} request
+ *
+ * <p>It contains one field, a {@link AdSelectionFromOutcomesConfig} which serves as the identifier
+ * of the override to be removed
+ *
+ */
+public class RemoveAdSelectionFromOutcomesOverrideRequest {
+ @NonNull private final AdSelectionFromOutcomesConfig mAdSelectionFromOutcomesConfig;
+
+ /** Builds a {@link RemoveAdSelectionOverrideRequest} instance. */
+ public RemoveAdSelectionFromOutcomesOverrideRequest(
+ @NonNull AdSelectionFromOutcomesConfig config) {
+ mAdSelectionFromOutcomesConfig = config;
+ }
+
+ /** @return AdSelectionConfig, the configuration of the ad selection process. */
+ @NonNull
+ public AdSelectionFromOutcomesConfig getAdSelectionFromOutcomesConfig() {
+ return mAdSelectionFromOutcomesConfig;
+ }
+}
diff --git a/android-35/android/adservices/adselection/RemoveAdSelectionOverrideRequest.java b/android-35/android/adservices/adselection/RemoveAdSelectionOverrideRequest.java
new file mode 100644
index 0000000..79e8792
--- /dev/null
+++ b/android-35/android/adservices/adselection/RemoveAdSelectionOverrideRequest.java
@@ -0,0 +1,46 @@
+/*
+ * 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.adselection;
+
+import android.annotation.NonNull;
+import android.os.OutcomeReceiver;
+
+import java.util.concurrent.Executor;
+
+/**
+ * This POJO represents the {@link TestAdSelectionManager#removeAdSelectionConfigRemoteInfoOverride(
+ * RemoveAdSelectionOverrideRequest, Executor, OutcomeReceiver)} request
+ *
+ * <p>It contains one field, a {@link AdSelectionConfig} which serves as the identifier of the
+ * override to be removed
+ */
+public class RemoveAdSelectionOverrideRequest {
+ @NonNull private final AdSelectionConfig mAdSelectionConfig;
+
+ /** Builds a {@link RemoveAdSelectionOverrideRequest} instance. */
+ public RemoveAdSelectionOverrideRequest(@NonNull AdSelectionConfig adSelectionConfig) {
+ mAdSelectionConfig = adSelectionConfig;
+ }
+
+ /**
+ * @return AdSelectionConfig, the configuration of the ad selection process.
+ */
+ @NonNull
+ public AdSelectionConfig getAdSelectionConfig() {
+ return mAdSelectionConfig;
+ }
+}
diff --git a/android-35/android/adservices/adselection/ReportEventRequest.java b/android-35/android/adservices/adselection/ReportEventRequest.java
new file mode 100644
index 0000000..6581ab1
--- /dev/null
+++ b/android-35/android/adservices/adselection/ReportEventRequest.java
@@ -0,0 +1,269 @@
+/*
+ * 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.adselection;
+
+import static android.adservices.adselection.AdSelectionOutcome.UNSET_AD_SELECTION_ID;
+import static android.adservices.adselection.AdSelectionOutcome.UNSET_AD_SELECTION_ID_MESSAGE;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.view.InputEvent;
+
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.nio.charset.StandardCharsets;
+import java.util.Locale;
+import java.util.Objects;
+
+/**
+ * Request object wrapping the required arguments needed to report an ad event.
+ */
+public class ReportEventRequest {
+ public static final int FLAG_REPORTING_DESTINATION_SELLER = 1 << 0;
+ public static final int FLAG_REPORTING_DESTINATION_BUYER = 1 << 1;
+ private static final int UNSET_REPORTING_DESTINATIONS = 0;
+ private static final String UNSET_REPORTING_DESTINATIONS_MESSAGE =
+ "Reporting destinations bitfield not set.";
+ private static final String INVALID_REPORTING_DESTINATIONS_MESSAGE =
+ "Invalid reporting destinations bitfield!";
+
+ /** @hide */
+ public static final long REPORT_EVENT_MAX_INTERACTION_DATA_SIZE_B = 64 * 1024; // 64 KB
+
+ private static final String EVENT_DATA_SIZE_MAX_EXCEEDED =
+ "Event data should not exceed " + REPORT_EVENT_MAX_INTERACTION_DATA_SIZE_B + " bytes";
+
+ private final long mAdSelectionId;
+ @NonNull private final String mEventKey;
+ @Nullable private final InputEvent mInputEvent;
+ @NonNull private final String mEventData;
+ @ReportingDestination private final int mReportingDestinations; // buyer, seller, or both
+
+ private ReportEventRequest(@NonNull Builder builder) {
+ Objects.requireNonNull(builder);
+
+ Preconditions.checkArgument(
+ builder.mAdSelectionId != UNSET_AD_SELECTION_ID, UNSET_AD_SELECTION_ID_MESSAGE);
+ Preconditions.checkArgument(
+ builder.mReportingDestinations != UNSET_REPORTING_DESTINATIONS,
+ UNSET_REPORTING_DESTINATIONS_MESSAGE);
+ Preconditions.checkArgument(
+ isValidDestination(builder.mReportingDestinations),
+ INVALID_REPORTING_DESTINATIONS_MESSAGE);
+ Preconditions.checkArgument(
+ builder.mEventData.getBytes(StandardCharsets.UTF_8).length
+ <= REPORT_EVENT_MAX_INTERACTION_DATA_SIZE_B,
+ EVENT_DATA_SIZE_MAX_EXCEEDED);
+
+ this.mAdSelectionId = builder.mAdSelectionId;
+ this.mEventKey = builder.mEventKey;
+ this.mInputEvent = builder.mInputEvent;
+ this.mEventData = builder.mEventData;
+ this.mReportingDestinations = builder.mReportingDestinations;
+ }
+
+ /**
+ * Returns the adSelectionId, the primary identifier of an ad selection process.
+ */
+ public long getAdSelectionId() {
+ return mAdSelectionId;
+ }
+
+ /**
+ * Returns the event key, the type of ad event to be reported.
+ *
+ * <p>This field will be used to fetch the {@code reportingUri} associated with the {@code
+ * eventKey} registered in {@code registerAdBeacon} after ad selection.
+ *
+ * <p>This field should be an exact match to the {@code eventKey} registered in {@code
+ * registerAdBeacon}. Specific details about {@code registerAdBeacon} can be found at the
+ * documentation of {@link AdSelectionManager#reportImpression}
+ *
+ * <p>The event key (when inspecting its byte array with {@link String#getBytes()}) in {@code
+ * UTF-8} format should not exceed 40 bytes. Any key exceeding this limit will not be registered
+ * during the {@code registerAdBeacon} call.
+ */
+ @NonNull
+ public String getKey() {
+ return mEventKey;
+ }
+
+ /**
+ * Returns the input event associated with the user interaction.
+ *
+ * <p>This field is either {@code null}, representing a <em>view</em> event, or has an {@link
+ * InputEvent} object, representing a <em>click</em> event.
+ */
+ @Nullable
+ public InputEvent getInputEvent() {
+ return mInputEvent;
+ }
+
+ /**
+ * Returns the ad event data.
+ *
+ * <p>After ad selection, this data is generated by the caller. The caller can then call {@link
+ * AdSelectionManager#reportEvent}. This data will be attached in a POST request to the {@code
+ * reportingUri} registered in {@code registerAdBeacon}.
+ *
+ * <p>The size of {@link String#getBytes()} in {@code UTF-8} format should be below 64KB.
+ */
+ @NonNull
+ public String getData() {
+ return mEventData;
+ }
+
+ /**
+ * Returns the bitfield of reporting destinations to report to (buyer, seller, or both).
+ *
+ * <p>To create this bitfield, place an {@code |} bitwise operator between each {@code
+ * reportingDestination} to be reported to. For example to only report to buyer, set the
+ * reportingDestinations field to {@link #FLAG_REPORTING_DESTINATION_BUYER} To only report to
+ * seller, set the reportingDestinations field to {@link #FLAG_REPORTING_DESTINATION_SELLER} To
+ * report to both buyers and sellers, set the reportingDestinations field to {@link
+ * #FLAG_REPORTING_DESTINATION_BUYER} | {@link #FLAG_REPORTING_DESTINATION_SELLER}
+ */
+ @ReportingDestination
+ public int getReportingDestinations() {
+ return mReportingDestinations;
+ }
+
+ /** @hide */
+ @IntDef(
+ flag = true,
+ prefix = {"FLAG_REPORTING_DESTINATION"},
+ value = {FLAG_REPORTING_DESTINATION_SELLER, FLAG_REPORTING_DESTINATION_BUYER})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ReportingDestination {}
+
+ private static boolean isValidDestination(@ReportingDestination int reportingDestinations) {
+ return 0 < reportingDestinations
+ && reportingDestinations
+ <= (FLAG_REPORTING_DESTINATION_SELLER | FLAG_REPORTING_DESTINATION_BUYER);
+ }
+
+ /** Builder for {@link ReportEventRequest} objects. */
+ public static final class Builder {
+
+ private long mAdSelectionId;
+ @NonNull private String mEventKey;
+ @Nullable private InputEvent mInputEvent;
+ @NonNull private String mEventData;
+ @ReportingDestination private int mReportingDestinations; // buyer, seller, or both
+
+ public Builder(
+ long adSelectionId,
+ @NonNull String eventKey,
+ @NonNull String eventData,
+ @ReportingDestination int reportingDestinations) {
+ Objects.requireNonNull(eventKey);
+ Objects.requireNonNull(eventData);
+
+ Preconditions.checkArgument(
+ adSelectionId != UNSET_AD_SELECTION_ID, UNSET_AD_SELECTION_ID_MESSAGE);
+ Preconditions.checkArgument(
+ reportingDestinations != UNSET_REPORTING_DESTINATIONS,
+ UNSET_REPORTING_DESTINATIONS_MESSAGE);
+ Preconditions.checkArgument(
+ isValidDestination(reportingDestinations),
+ INVALID_REPORTING_DESTINATIONS_MESSAGE);
+ Preconditions.checkArgument(
+ eventData.getBytes(StandardCharsets.UTF_8).length
+ <= REPORT_EVENT_MAX_INTERACTION_DATA_SIZE_B,
+ EVENT_DATA_SIZE_MAX_EXCEEDED);
+
+ this.mAdSelectionId = adSelectionId;
+ this.mEventKey = eventKey;
+ this.mEventData = eventData;
+ this.mReportingDestinations = reportingDestinations;
+ }
+
+ /**
+ * Sets the ad selection ID with which the rendered ad's events are associated.
+ *
+ * <p>See {@link #getAdSelectionId()} for more information.
+ */
+ @NonNull
+ public Builder setAdSelectionId(long adSelectionId) {
+ Preconditions.checkArgument(
+ adSelectionId != UNSET_AD_SELECTION_ID, UNSET_AD_SELECTION_ID_MESSAGE);
+ mAdSelectionId = adSelectionId;
+ return this;
+ }
+
+ /**
+ * Sets the event key, the type of ad event to be reported.
+ *
+ * <p>See {@link #getKey()} for more information.
+ */
+ @NonNull
+ public Builder setKey(@NonNull String eventKey) {
+ Objects.requireNonNull(eventKey);
+
+ mEventKey = eventKey;
+ return this;
+ }
+
+ /**
+ * Sets the input event associated with the user interaction.
+ *
+ * <p>See {@link #getInputEvent()} for more information.
+ */
+ @NonNull
+ public Builder setInputEvent(@Nullable InputEvent inputEvent) {
+ mInputEvent = inputEvent;
+ return this;
+ }
+
+ /**
+ * Sets the ad event data.
+ *
+ * <p>See {@link #getData()} for more information.
+ */
+ @NonNull
+ public Builder setData(@NonNull String eventData) {
+ Objects.requireNonNull(eventData);
+
+ mEventData = eventData;
+ return this;
+ }
+
+ /**
+ * Sets the bitfield of reporting destinations to report to (buyer, seller, or both).
+ *
+ * <p>See {@link #getReportingDestinations()} for more information.
+ */
+ @NonNull
+ public Builder setReportingDestinations(@ReportingDestination int reportingDestinations) {
+ Preconditions.checkArgument(
+ isValidDestination(reportingDestinations),
+ INVALID_REPORTING_DESTINATIONS_MESSAGE);
+
+ mReportingDestinations = reportingDestinations;
+ return this;
+ }
+
+ /** Builds the {@link ReportEventRequest} object. */
+ @NonNull
+ public ReportEventRequest build() {
+ return new ReportEventRequest(this);
+ }
+ }
+}
diff --git a/android-35/android/adservices/adselection/ReportImpressionInput.java b/android-35/android/adservices/adselection/ReportImpressionInput.java
new file mode 100644
index 0000000..3e09b17
--- /dev/null
+++ b/android-35/android/adservices/adselection/ReportImpressionInput.java
@@ -0,0 +1,161 @@
+/*
+ * 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.adselection;
+
+import static android.adservices.adselection.AdSelectionOutcome.UNSET_AD_SELECTION_ID;
+import static android.adservices.adselection.AdSelectionOutcome.UNSET_AD_SELECTION_ID_MESSAGE;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.Objects;
+
+/**
+ * Represent input params to the reportImpression API.
+ *
+ * @hide
+ */
+public final class ReportImpressionInput implements Parcelable {
+ private final long mAdSelectionId;
+ @NonNull private final AdSelectionConfig mAdSelectionConfig;
+ @NonNull private final String mCallerPackageName;
+
+ @NonNull
+ public static final Parcelable.Creator<ReportImpressionInput> CREATOR =
+ new Parcelable.Creator<ReportImpressionInput>() {
+ public ReportImpressionInput createFromParcel(Parcel in) {
+ return new ReportImpressionInput(in);
+ }
+
+ public ReportImpressionInput[] newArray(int size) {
+ return new ReportImpressionInput[size];
+ }
+ };
+
+ private ReportImpressionInput(
+ long adSelectionId,
+ @NonNull AdSelectionConfig adSelectionConfig,
+ @NonNull String callerPackageName) {
+ Objects.requireNonNull(adSelectionConfig);
+
+ this.mAdSelectionId = adSelectionId;
+ this.mAdSelectionConfig = adSelectionConfig;
+ this.mCallerPackageName = callerPackageName;
+ }
+
+ private ReportImpressionInput(@NonNull Parcel in) {
+ Objects.requireNonNull(in);
+
+ this.mAdSelectionId = in.readLong();
+ this.mAdSelectionConfig = AdSelectionConfig.CREATOR.createFromParcel(in);
+ this.mCallerPackageName = in.readString();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ Objects.requireNonNull(dest);
+
+ dest.writeLong(mAdSelectionId);
+ mAdSelectionConfig.writeToParcel(dest, flags);
+ dest.writeString(mCallerPackageName);
+ }
+
+ /**
+ * Returns the adSelectionId, one of the inputs to {@link ReportImpressionInput} as noted in
+ * {@code AdSelectionService}.
+ */
+ public long getAdSelectionId() {
+ return mAdSelectionId;
+ }
+
+ /**
+ * Returns the adSelectionConfig, one of the inputs to {@link ReportImpressionInput} as noted in
+ * {@code AdSelectionService}.
+ */
+ @NonNull
+ public AdSelectionConfig getAdSelectionConfig() {
+ return mAdSelectionConfig;
+ }
+
+ /** @return the caller package name */
+ @NonNull
+ public String getCallerPackageName() {
+ return mCallerPackageName;
+ }
+
+ /**
+ * Builder for {@link ReportImpressionInput} objects.
+ *
+ * @hide
+ */
+ public static final class Builder {
+ private long mAdSelectionId = UNSET_AD_SELECTION_ID;
+ @Nullable private AdSelectionConfig mAdSelectionConfig;
+ private String mCallerPackageName;
+
+ public Builder() {}
+
+ /** Set the mAdSelectionId. */
+ @NonNull
+ public ReportImpressionInput.Builder setAdSelectionId(long adSelectionId) {
+ this.mAdSelectionId = adSelectionId;
+ return this;
+ }
+
+ /** Set the AdSelectionConfig. */
+ @NonNull
+ public ReportImpressionInput.Builder setAdSelectionConfig(
+ @NonNull AdSelectionConfig adSelectionConfig) {
+ Objects.requireNonNull(adSelectionConfig);
+
+ this.mAdSelectionConfig = adSelectionConfig;
+ return this;
+ }
+
+ /** Sets the caller's package name. */
+ @NonNull
+ public ReportImpressionInput.Builder setCallerPackageName(
+ @NonNull String callerPackageName) {
+ Objects.requireNonNull(callerPackageName);
+
+ this.mCallerPackageName = callerPackageName;
+ return this;
+ }
+
+ /** Builds a {@link ReportImpressionInput} instance. */
+ @NonNull
+ public ReportImpressionInput build() {
+ Objects.requireNonNull(mAdSelectionConfig);
+ Objects.requireNonNull(mCallerPackageName);
+
+ Preconditions.checkArgument(
+ mAdSelectionId != UNSET_AD_SELECTION_ID, UNSET_AD_SELECTION_ID_MESSAGE);
+
+ return new ReportImpressionInput(
+ mAdSelectionId, mAdSelectionConfig, mCallerPackageName);
+ }
+ }
+}
diff --git a/android-35/android/adservices/adselection/ReportImpressionRequest.java b/android-35/android/adservices/adselection/ReportImpressionRequest.java
new file mode 100644
index 0000000..3d576ff
--- /dev/null
+++ b/android-35/android/adservices/adselection/ReportImpressionRequest.java
@@ -0,0 +1,89 @@
+/*
+ * 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.adselection;
+
+import static android.adservices.adselection.AdSelectionOutcome.UNSET_AD_SELECTION_ID;
+import static android.adservices.adselection.AdSelectionOutcome.UNSET_AD_SELECTION_ID_MESSAGE;
+
+import android.annotation.NonNull;
+import android.os.OutcomeReceiver;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/**
+ * Represent input parameters to the reportImpression API.
+ */
+public class ReportImpressionRequest {
+ private final long mAdSelectionId;
+ @NonNull private final AdSelectionConfig mAdSelectionConfig;
+
+ /**
+ * Ctor for on-device ad selection reporting request.
+ *
+ * <p>If your {@code adSelectionId} is for a on-device auction run using {@link
+ * AdSelectionManager#selectAds(AdSelectionConfig, Executor, OutcomeReceiver)} then your
+ * impression reporting request must include your {@link AdSelectionConfig}.
+ *
+ * @param adSelectionId received from {@link AdSelectionManager#selectAds(AdSelectionConfig,
+ * Executor, OutcomeReceiver)}
+ * @param adSelectionConfig same {@link AdSelectionConfig} used to trigger {@link
+ * AdSelectionManager#selectAds(AdSelectionConfig, Executor, OutcomeReceiver)}
+ */
+ public ReportImpressionRequest(
+ long adSelectionId, @NonNull AdSelectionConfig adSelectionConfig) {
+ Objects.requireNonNull(adSelectionConfig);
+ Preconditions.checkArgument(
+ adSelectionId != UNSET_AD_SELECTION_ID, UNSET_AD_SELECTION_ID_MESSAGE);
+
+ mAdSelectionId = adSelectionId;
+ mAdSelectionConfig = adSelectionConfig;
+ }
+
+ /**
+ * Ctor for auction server ad selection reporting request.
+ *
+ * <p>If your {@code adSelectionId} is for a server auction run where device info collected by
+ * {@link AdSelectionManager#getAdSelectionData} then your impression reporting request should
+ * only include the ad selection id.
+ *
+ * <p>{@link AdSelectionManager#persistAdSelectionResult} must be called with the encrypted
+ * result blob from servers before making impression reporting request.
+ *
+ * @param adSelectionId received from {@link AdSelectionManager#getAdSelectionData}
+ */
+ public ReportImpressionRequest(long adSelectionId) {
+ Preconditions.checkArgument(
+ adSelectionId != UNSET_AD_SELECTION_ID, UNSET_AD_SELECTION_ID_MESSAGE);
+
+ mAdSelectionId = adSelectionId;
+ mAdSelectionConfig = AdSelectionConfig.EMPTY;
+ }
+
+ /** Returns the adSelectionId, one of the inputs to {@link ReportImpressionRequest} */
+ public long getAdSelectionId() {
+ return mAdSelectionId;
+ }
+
+ /** Returns the adSelectionConfig, one of the inputs to {@link ReportImpressionRequest} */
+ @NonNull
+ public AdSelectionConfig getAdSelectionConfig() {
+ return mAdSelectionConfig;
+ }
+}
diff --git a/android-35/android/adservices/adselection/ReportInteractionInput.java b/android-35/android/adservices/adselection/ReportInteractionInput.java
new file mode 100644
index 0000000..2c6dae9
--- /dev/null
+++ b/android-35/android/adservices/adselection/ReportInteractionInput.java
@@ -0,0 +1,305 @@
+/*
+ * 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.adselection;
+
+import static android.adservices.adselection.AdSelectionOutcome.UNSET_AD_SELECTION_ID;
+import static android.adservices.adselection.AdSelectionOutcome.UNSET_AD_SELECTION_ID_MESSAGE;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.InputEvent;
+
+import com.android.adservices.AdServicesParcelableUtil;
+import com.android.internal.util.Preconditions;
+
+import java.util.Objects;
+
+/**
+ * Input object wrapping the required arguments needed to report an interaction.
+ *
+ * @hide
+ */
+public final class ReportInteractionInput implements Parcelable {
+
+ private static final int UNSET_REPORTING_DESTINATIONS = 0;
+ private static final String UNSET_REPORTING_DESTINATIONS_MESSAGE =
+ "Reporting Destinations bitfield not set.";
+
+ private final long mAdSelectionId;
+ @NonNull private final String mInteractionKey;
+ @NonNull private final String mInteractionData;
+ @NonNull private final String mCallerPackageName;
+ private final int mReportingDestinations; // buyer, seller, or both
+ @Nullable private final InputEvent mInputEvent;
+ @Nullable private final String mAdId;
+ @Nullable private final String mCallerSdkName;
+
+ @NonNull
+ public static final Creator<ReportInteractionInput> CREATOR =
+ new Creator<ReportInteractionInput>() {
+ @Override
+ public ReportInteractionInput createFromParcel(@NonNull Parcel in) {
+ Objects.requireNonNull(in);
+
+ return new ReportInteractionInput(in);
+ }
+
+ @Override
+ public ReportInteractionInput[] newArray(int size) {
+ return new ReportInteractionInput[size];
+ }
+ };
+
+ private ReportInteractionInput(
+ long adSelectionId,
+ @NonNull String interactionKey,
+ @NonNull String interactionData,
+ @NonNull String callerPackageName,
+ int reportingDestinations,
+ @Nullable InputEvent inputEvent,
+ @Nullable String adId,
+ @Nullable String callerSdkName) {
+ Objects.requireNonNull(interactionKey);
+ Objects.requireNonNull(interactionData);
+ Objects.requireNonNull(callerPackageName);
+
+ this.mAdSelectionId = adSelectionId;
+ this.mInteractionKey = interactionKey;
+ this.mInteractionData = interactionData;
+ this.mCallerPackageName = callerPackageName;
+ this.mReportingDestinations = reportingDestinations;
+ this.mInputEvent = inputEvent;
+ this.mAdId = adId;
+ this.mCallerSdkName = callerSdkName;
+ }
+
+ private ReportInteractionInput(@NonNull Parcel in) {
+ this.mAdSelectionId = in.readLong();
+ this.mInteractionKey = in.readString();
+ this.mInteractionData = in.readString();
+ this.mCallerPackageName = in.readString();
+ this.mReportingDestinations = in.readInt();
+ this.mInputEvent =
+ AdServicesParcelableUtil.readNullableFromParcel(
+ in, InputEvent.CREATOR::createFromParcel);
+ this.mAdId =
+ AdServicesParcelableUtil.readNullableFromParcel(
+ in, (sourceParcel -> in.readString()));
+ this.mCallerSdkName =
+ AdServicesParcelableUtil.readNullableFromParcel(
+ in, (sourceParcel -> in.readString()));
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ Objects.requireNonNull(dest);
+ dest.writeLong(mAdSelectionId);
+ dest.writeString(mInteractionKey);
+ dest.writeString(mInteractionData);
+ dest.writeString(mCallerPackageName);
+ dest.writeInt(mReportingDestinations);
+ AdServicesParcelableUtil.writeNullableToParcel(
+ dest,
+ mInputEvent,
+ (targetParcel, sourceInputEvent) ->
+ sourceInputEvent.writeToParcel(targetParcel, flags));
+ AdServicesParcelableUtil.writeNullableToParcel(dest, mAdId, Parcel::writeString);
+ AdServicesParcelableUtil.writeNullableToParcel(dest, mCallerSdkName, Parcel::writeString);
+ }
+
+ /** Returns the adSelectionId, the primary identifier of an ad selection process. */
+ public long getAdSelectionId() {
+ return mAdSelectionId;
+ }
+
+ /**
+ * Returns the interaction key, the type of interaction to be reported.
+ *
+ * <p>This will be used to fetch the {@code interactionReportingUri} associated with the {@code
+ * interactionKey} registered in {@code registerAdBeacon} after ad selection.
+ */
+ @NonNull
+ public String getInteractionKey() {
+ return mInteractionKey;
+ }
+
+ /**
+ * Returns the interaction data.
+ *
+ * <p>After ad selection, this data is generated by the caller, and will be attached in a POST
+ * request to the {@code interactionReportingUri} registered in {@code registerAdBeacon}.
+ */
+ @NonNull
+ public String getInteractionData() {
+ return mInteractionData;
+ }
+
+ /** Returns the caller package name */
+ @NonNull
+ public String getCallerPackageName() {
+ return mCallerPackageName;
+ }
+
+ /** Returns the bitfield of reporting destinations to report to (buyer, seller, or both) */
+ public int getReportingDestinations() {
+ return mReportingDestinations;
+ }
+
+ /**
+ * Returns the input event associated with the user interaction.
+ *
+ * <p>This field is either {@code null}, representing a <em>view</em> event, or has an {@link
+ * InputEvent} object, representing a <em>click</em> event.
+ */
+ @Nullable
+ public InputEvent getInputEvent() {
+ return mInputEvent;
+ }
+
+ /**
+ * Returns the {@code AdId} to enable event-level debug reporting.
+ *
+ * <p>This field is either set and non-{@code null}, representing a valid and enabled {@code
+ * AdId} or {@code null}, representing an invalid or disabled {@code AdId}.
+ */
+ @Nullable
+ public String getAdId() {
+ return mAdId;
+ }
+
+ /** Returns the caller's sdk name. */
+ @Nullable
+ public String getCallerSdkName() {
+ return mCallerSdkName;
+ }
+
+ /**
+ * Builder for {@link ReportInteractionInput} objects.
+ *
+ * @hide
+ */
+ public static final class Builder {
+ private long mAdSelectionId = UNSET_AD_SELECTION_ID;
+ @Nullable private String mInteractionKey;
+ @Nullable private String mInteractionData;
+ @Nullable private String mCallerPackageName;
+ @Nullable private String mCallerSdkName;
+ private int mReportingDestinations = UNSET_REPORTING_DESTINATIONS;
+ @Nullable private InputEvent mInputEvent;
+ @Nullable private String mAdId;
+
+ public Builder() {}
+
+ /** Sets the adSelectionId. */
+ @NonNull
+ public ReportInteractionInput.Builder setAdSelectionId(long adSelectionId) {
+ mAdSelectionId = adSelectionId;
+ return this;
+ }
+
+ /** Sets the interactionKey. */
+ @NonNull
+ public ReportInteractionInput.Builder setInteractionKey(@NonNull String interactionKey) {
+ Objects.requireNonNull(interactionKey);
+
+ mInteractionKey = interactionKey;
+ return this;
+ }
+
+ /** Sets the interactionData. */
+ @NonNull
+ public ReportInteractionInput.Builder setInteractionData(@NonNull String interactionData) {
+ Objects.requireNonNull(interactionData);
+
+ mInteractionData = interactionData;
+ return this;
+ }
+
+ /** Sets the caller's package name. */
+ @NonNull
+ public ReportInteractionInput.Builder setCallerPackageName(
+ @NonNull String callerPackageName) {
+ Objects.requireNonNull(callerPackageName);
+
+ mCallerPackageName = callerPackageName;
+ return this;
+ }
+
+ /** Sets the bitfield of reporting destinations. */
+ @NonNull
+ public ReportInteractionInput.Builder setReportingDestinations(int reportingDestinations) {
+ Preconditions.checkArgument(
+ reportingDestinations != UNSET_REPORTING_DESTINATIONS,
+ UNSET_REPORTING_DESTINATIONS_MESSAGE);
+
+ mReportingDestinations = reportingDestinations;
+ return this;
+ }
+
+ /** Sets the input event associated with the user interaction. */
+ @NonNull
+ public ReportInteractionInput.Builder setInputEvent(@Nullable InputEvent inputEvent) {
+ mInputEvent = inputEvent;
+ return this;
+ }
+
+ /** Sets the {@code AdId}. */
+ @NonNull
+ public ReportInteractionInput.Builder setAdId(@Nullable String adId) {
+ mAdId = adId;
+ return this;
+ }
+
+ /** Sets the caller's sdk name. */
+ @NonNull
+ public ReportInteractionInput.Builder setCallerSdkName(@Nullable String callerSdkName) {
+ mCallerSdkName = callerSdkName;
+ return this;
+ }
+
+ /** Builds a {@link ReportInteractionInput} instance. */
+ @NonNull
+ public ReportInteractionInput build() {
+ Objects.requireNonNull(mInteractionKey);
+ Objects.requireNonNull(mInteractionData);
+ Objects.requireNonNull(mCallerPackageName);
+
+ Preconditions.checkArgument(
+ mAdSelectionId != UNSET_AD_SELECTION_ID, UNSET_AD_SELECTION_ID_MESSAGE);
+ Preconditions.checkArgument(
+ mReportingDestinations != UNSET_REPORTING_DESTINATIONS,
+ UNSET_REPORTING_DESTINATIONS_MESSAGE);
+
+ return new ReportInteractionInput(
+ mAdSelectionId,
+ mInteractionKey,
+ mInteractionData,
+ mCallerPackageName,
+ mReportingDestinations,
+ mInputEvent,
+ mAdId,
+ mCallerSdkName);
+ }
+ }
+}
diff --git a/android-35/android/adservices/adselection/SetAdCounterHistogramOverrideInput.java b/android-35/android/adservices/adselection/SetAdCounterHistogramOverrideInput.java
new file mode 100644
index 0000000..8c16f0d
--- /dev/null
+++ b/android-35/android/adservices/adselection/SetAdCounterHistogramOverrideInput.java
@@ -0,0 +1,313 @@
+/*
+ * 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.adselection;
+
+import static android.adservices.adselection.SetAdCounterHistogramOverrideRequest.NULL_BUYER_MESSAGE;
+import static android.adservices.adselection.SetAdCounterHistogramOverrideRequest.NULL_CUSTOM_AUDIENCE_NAME_MESSAGE;
+import static android.adservices.adselection.SetAdCounterHistogramOverrideRequest.NULL_CUSTOM_AUDIENCE_OWNER_MESSAGE;
+import static android.adservices.adselection.SetAdCounterHistogramOverrideRequest.NULL_HISTOGRAM_TIMESTAMPS_MESSAGE;
+import static android.adservices.adselection.UpdateAdCounterHistogramRequest.UNSET_AD_EVENT_TYPE_MESSAGE;
+import static android.adservices.common.FrequencyCapFilters.AD_EVENT_TYPE_INVALID;
+
+import android.adservices.common.AdTechIdentifier;
+import android.adservices.common.FrequencyCapFilters;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.adservices.AdServicesParcelableUtil;
+import com.android.internal.util.Preconditions;
+
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Input object for setting ad counter histogram overrides.
+ *
+ * <p>Histogram overrides replace actual ad counter histograms used in ad selection. Overrides may
+ * only be set in debuggable apps on phones running a debuggable OS build with developer options
+ * enabled. Overrides are only available from the calling app.
+ *
+ * @hide
+ */
+public final class SetAdCounterHistogramOverrideInput implements Parcelable {
+ @FrequencyCapFilters.AdEventType private final int mAdEventType;
+ private final int mAdCounterKey;
+ @NonNull private final List<Instant> mHistogramTimestamps;
+ @NonNull private final AdTechIdentifier mBuyer;
+ @NonNull private final String mCustomAudienceOwner;
+ @NonNull private final String mCustomAudienceName;
+
+ @NonNull
+ public static final Creator<SetAdCounterHistogramOverrideInput> CREATOR =
+ new Creator<SetAdCounterHistogramOverrideInput>() {
+ @Override
+ public SetAdCounterHistogramOverrideInput createFromParcel(@NonNull Parcel in) {
+ Objects.requireNonNull(in);
+
+ return new SetAdCounterHistogramOverrideInput(in);
+ }
+
+ @Override
+ public SetAdCounterHistogramOverrideInput[] newArray(int size) {
+ return new SetAdCounterHistogramOverrideInput[size];
+ }
+ };
+
+ private SetAdCounterHistogramOverrideInput(@NonNull Builder builder) {
+ Objects.requireNonNull(builder);
+
+ mAdEventType = builder.mAdEventType;
+ mAdCounterKey = builder.mAdCounterKey;
+ mHistogramTimestamps = builder.mHistogramTimestamps;
+ mBuyer = builder.mBuyer;
+ mCustomAudienceOwner = builder.mCustomAudienceOwner;
+ mCustomAudienceName = builder.mCustomAudienceName;
+ }
+
+ private SetAdCounterHistogramOverrideInput(@NonNull Parcel in) {
+ Objects.requireNonNull(in);
+
+ mAdEventType = in.readInt();
+ mAdCounterKey = in.readInt();
+ mHistogramTimestamps = AdServicesParcelableUtil.readInstantListFromParcel(in);
+ mBuyer = AdTechIdentifier.fromString(in.readString());
+ mCustomAudienceOwner = in.readString();
+ mCustomAudienceName = in.readString();
+ }
+
+ /**
+ * Gets the {@link FrequencyCapFilters.AdEventType} for the ad counter histogram override.
+ *
+ * <p>The ad event type is used with the ad counter key from {@link #getAdCounterKey()} and the
+ * buyer adtech from {@link #getBuyer()} to specify which histogram to use in ad selection
+ * filtering. The ad event type would normally be specified by an app/SDK after a
+ * FLEDGE-selected ad is rendered.
+ */
+ @FrequencyCapFilters.AdEventType
+ public int getAdEventType() {
+ return mAdEventType;
+ }
+
+ /**
+ * Gets the ad counter key for the ad counter histogram override.
+ *
+ * <p>The ad counter key is used with the ad event type from {@link #getAdEventType()} and the
+ * buyer adtech from {@link #getBuyer()} to specify which histogram to use in ad selection
+ * filtering. The ad counter key would normally be specified by a custom audience ad to
+ * represent a grouping to filter on.
+ */
+ @NonNull
+ public int getAdCounterKey() {
+ return mAdCounterKey;
+ }
+
+ /**
+ * Gets the list of {@link Instant} objects for the ad counter histogram override.
+ *
+ * <p>When set, this list of timestamps is used to populate the override histogram, which is
+ * used instead of actual histograms for ad selection filtering.
+ */
+ @NonNull
+ public List<Instant> getHistogramTimestamps() {
+ return mHistogramTimestamps;
+ }
+
+ /**
+ * Gets the {@link AdTechIdentifier} for the buyer which owns the ad counter histogram.
+ *
+ * <p>During filtering in FLEDGE ad selection, ads can only use ad counter histogram data
+ * generated by the same buyer. For {@link FrequencyCapFilters#AD_EVENT_TYPE_WIN}, ad counter
+ * histogram data is further restricted to ads from the same custom audience, which is
+ * identified by the buyer, the custom audience's owner app package name from {@link
+ * #getCustomAudienceOwner()}, and the custom audience name from {@link
+ * #getCustomAudienceName()}.
+ */
+ @NonNull
+ public AdTechIdentifier getBuyer() {
+ return mBuyer;
+ }
+
+ /**
+ * Gets the package name for the app which generated the custom audience which is associated
+ * with the overridden ad counter histogram data.
+ *
+ * <p>For {@link FrequencyCapFilters#AD_EVENT_TYPE_WIN}, ad counter histogram data is restricted
+ * to ads from the same custom audience, which is identified by the buyer from {@link
+ * #getBuyer()}, the custom audience's owner app package name, and the custom audience name from
+ * {@link #getCustomAudienceName()}.
+ */
+ @NonNull
+ public String getCustomAudienceOwner() {
+ return mCustomAudienceOwner;
+ }
+
+ /**
+ * Gets the buyer-generated name for the custom audience which is associated with the overridden
+ * ad counter histogram data.
+ *
+ * <p>For {@link FrequencyCapFilters#AD_EVENT_TYPE_WIN}, ad counter histogram data is restricted
+ * to ads from the same custom audience, which is identified by the buyer from {@link
+ * #getBuyer()}, the custom audience's owner app package name from {@link
+ * #getCustomAudienceOwner()}, and the custom audience name.
+ */
+ @NonNull
+ public String getCustomAudienceName() {
+ return mCustomAudienceName;
+ }
+
+ /** @hide */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ return "SetAdCounterHistogramOverrideInput{"
+ + "mAdEventType="
+ + mAdEventType
+ + ", mAdCounterKey="
+ + mAdCounterKey
+ + ", mHistogramTimestamps="
+ + mHistogramTimestamps
+ + ", mBuyer="
+ + mBuyer
+ + ", mCustomAudienceOwner='"
+ + mCustomAudienceOwner
+ + "', mCustomAudienceName='"
+ + mCustomAudienceName
+ + "'}";
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ Objects.requireNonNull(dest);
+
+ dest.writeInt(mAdEventType);
+ dest.writeInt(mAdCounterKey);
+ AdServicesParcelableUtil.writeInstantListToParcel(dest, mHistogramTimestamps);
+ dest.writeString(mBuyer.toString());
+ dest.writeString(mCustomAudienceOwner);
+ dest.writeString(mCustomAudienceName);
+ }
+
+ /** Builder for {@link SetAdCounterHistogramOverrideInput} objects. */
+ public static final class Builder {
+ @FrequencyCapFilters.AdEventType private int mAdEventType = AD_EVENT_TYPE_INVALID;
+ private int mAdCounterKey;
+ @NonNull private List<Instant> mHistogramTimestamps = new ArrayList<>();
+ @Nullable private AdTechIdentifier mBuyer;
+ @Nullable private String mCustomAudienceOwner;
+ @Nullable private String mCustomAudienceName;
+
+ public Builder() {}
+
+ /**
+ * Sets the {@link FrequencyCapFilters.AdEventType} for the ad counter histogram override.
+ *
+ * <p>See {@link #getAdEventType()} for more information.
+ */
+ @NonNull
+ public Builder setAdEventType(@FrequencyCapFilters.AdEventType int adEventType) {
+ mAdEventType = adEventType;
+ return this;
+ }
+
+ /**
+ * Sets the ad counter key for the ad counter histogram override.
+ *
+ * <p>See {@link #getAdCounterKey()} for more information.
+ */
+ @NonNull
+ public Builder setAdCounterKey(int adCounterKey) {
+ mAdCounterKey = adCounterKey;
+ return this;
+ }
+
+ /**
+ * Sets the list of {@link Instant} objects for the ad counter histogram override.
+ *
+ * <p>See {@link #getHistogramTimestamps()} for more information.
+ */
+ @NonNull
+ public Builder setHistogramTimestamps(@NonNull List<Instant> histogramTimestamps) {
+ Objects.requireNonNull(histogramTimestamps, NULL_HISTOGRAM_TIMESTAMPS_MESSAGE);
+ mHistogramTimestamps = histogramTimestamps;
+ return this;
+ }
+
+ /**
+ * Sets the {@link AdTechIdentifier} for the buyer which owns the ad counter histogram.
+ *
+ * <p>See {@link #getBuyer()} for more information.
+ */
+ @NonNull
+ public Builder setBuyer(@NonNull AdTechIdentifier buyer) {
+ Objects.requireNonNull(buyer, NULL_BUYER_MESSAGE);
+ mBuyer = buyer;
+ return this;
+ }
+
+ /**
+ * Sets the package name for the app which generated the custom audience which is associated
+ * with the overridden ad counter histogram data.
+ *
+ * <p>See {@link #getCustomAudienceOwner()} for more information.
+ */
+ @NonNull
+ public Builder setCustomAudienceOwner(@NonNull String customAudienceOwner) {
+ Objects.requireNonNull(customAudienceOwner, NULL_CUSTOM_AUDIENCE_OWNER_MESSAGE);
+ mCustomAudienceOwner = customAudienceOwner;
+ return this;
+ }
+
+ /**
+ * Sets the buyer-generated name for the custom audience which is associated with the
+ * overridden ad counter histogram data.
+ *
+ * <p>See {@link #getCustomAudienceName()} for more information.
+ */
+ @NonNull
+ public Builder setCustomAudienceName(@NonNull String customAudienceName) {
+ Objects.requireNonNull(customAudienceName, NULL_CUSTOM_AUDIENCE_NAME_MESSAGE);
+ mCustomAudienceName = customAudienceName;
+ return this;
+ }
+
+ /**
+ * Builds the {@link SetAdCounterHistogramOverrideInput} object.
+ *
+ * @throws NullPointerException if any parameters are not set
+ * @throws IllegalArgumentException if the ad event type is invalid
+ */
+ @NonNull
+ public SetAdCounterHistogramOverrideInput build()
+ throws NullPointerException, IllegalArgumentException {
+ Preconditions.checkArgument(
+ mAdEventType != AD_EVENT_TYPE_INVALID, UNSET_AD_EVENT_TYPE_MESSAGE);
+ Objects.requireNonNull(mBuyer, NULL_BUYER_MESSAGE);
+ Objects.requireNonNull(mCustomAudienceOwner, NULL_CUSTOM_AUDIENCE_OWNER_MESSAGE);
+ Objects.requireNonNull(mCustomAudienceName, NULL_CUSTOM_AUDIENCE_NAME_MESSAGE);
+
+ return new SetAdCounterHistogramOverrideInput(this);
+ }
+ }
+}
diff --git a/android-35/android/adservices/adselection/SetAdCounterHistogramOverrideRequest.java b/android-35/android/adservices/adselection/SetAdCounterHistogramOverrideRequest.java
new file mode 100644
index 0000000..e189b6f
--- /dev/null
+++ b/android-35/android/adservices/adselection/SetAdCounterHistogramOverrideRequest.java
@@ -0,0 +1,277 @@
+/*
+ * 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.adselection;
+
+import static android.adservices.adselection.UpdateAdCounterHistogramRequest.UNSET_AD_EVENT_TYPE_MESSAGE;
+import static android.adservices.common.FrequencyCapFilters.AD_EVENT_TYPE_INVALID;
+
+import android.adservices.common.AdTechIdentifier;
+import android.adservices.common.FrequencyCapFilters;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.internal.util.Preconditions;
+
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Request object for setting ad counter histogram overrides.
+ *
+ * <p>Histogram overrides replace actual ad counter histograms used in ad selection. Overrides may
+ * only be set in debuggable apps on phones running a debuggable OS build with developer options
+ * enabled. Overrides are only available from the calling app.
+ *
+ * @hide
+ */
+// TODO(b/265204820): Unhide for frequency cap dev override API review
+public class SetAdCounterHistogramOverrideRequest {
+ /** @hide */
+ public static final String NULL_HISTOGRAM_TIMESTAMPS_MESSAGE =
+ "List of histogram timestamps must not be null";
+
+ /** @hide */
+ public static final String NULL_BUYER_MESSAGE = "Buyer must not be null";
+
+ /** @hide */
+ public static final String NULL_CUSTOM_AUDIENCE_OWNER_MESSAGE =
+ "Custom audience owner must not be null";
+
+ /** @hide */
+ public static final String NULL_CUSTOM_AUDIENCE_NAME_MESSAGE =
+ "Custom audience name must not be null";
+
+ @FrequencyCapFilters.AdEventType private final int mAdEventType;
+ private final int mAdCounterKey;
+ @NonNull private final List<Instant> mHistogramTimestamps;
+ @NonNull private final AdTechIdentifier mBuyer;
+ @NonNull private final String mCustomAudienceOwner;
+ @NonNull private final String mCustomAudienceName;
+
+ private SetAdCounterHistogramOverrideRequest(@NonNull Builder builder) {
+ Objects.requireNonNull(builder);
+
+ mAdEventType = builder.mAdEventType;
+ mAdCounterKey = builder.mAdCounterKey;
+ mHistogramTimestamps = builder.mHistogramTimestamps;
+ mBuyer = builder.mBuyer;
+ mCustomAudienceOwner = builder.mCustomAudienceOwner;
+ mCustomAudienceName = builder.mCustomAudienceName;
+ }
+
+ /**
+ * Gets the {@link FrequencyCapFilters.AdEventType} for the ad counter histogram override.
+ *
+ * <p>The ad event type is used with the ad counter key from {@link #getAdCounterKey()} and the
+ * buyer adtech from {@link #getBuyer()} to specify which histogram to use in ad selection
+ * filtering. The ad event type would normally be specified by an app/SDK after a
+ * FLEDGE-selected ad is rendered.
+ */
+ @FrequencyCapFilters.AdEventType
+ public int getAdEventType() {
+ return mAdEventType;
+ }
+
+ /**
+ * Gets the ad counter key for the ad counter histogram override.
+ *
+ * <p>The ad counter key is used with the ad event type from {@link #getAdEventType()} and the
+ * buyer adtech from {@link #getBuyer()} to specify which histogram to use in ad selection
+ * filtering. The ad counter key would normally be specified by a custom audience ad to
+ * represent a grouping to filter on.
+ */
+ @NonNull
+ public int getAdCounterKey() {
+ return mAdCounterKey;
+ }
+
+ /**
+ * Gets the list of {@link Instant} objects for the ad counter histogram override.
+ *
+ * <p>When set, this list of timestamps is used to populate the override histogram, which is
+ * used instead of actual histograms for ad selection filtering.
+ */
+ @NonNull
+ public List<Instant> getHistogramTimestamps() {
+ return mHistogramTimestamps;
+ }
+
+ /**
+ * Gets the {@link AdTechIdentifier} for the buyer which owns the ad counter histogram.
+ *
+ * <p>During filtering in FLEDGE ad selection, ads can only use ad counter histogram data
+ * generated by the same buyer. For {@link FrequencyCapFilters#AD_EVENT_TYPE_WIN}, ad counter
+ * histogram data is further restricted to ads from the same custom audience, which is
+ * identified by the buyer, the custom audience's owner app package name from {@link
+ * #getCustomAudienceOwner()}, and the custom audience name from {@link
+ * #getCustomAudienceName()}.
+ */
+ @NonNull
+ public AdTechIdentifier getBuyer() {
+ return mBuyer;
+ }
+
+ /**
+ * Gets the package name for the app which generated the custom audience which is associated
+ * with the overridden ad counter histogram data.
+ *
+ * <p>For {@link FrequencyCapFilters#AD_EVENT_TYPE_WIN}, ad counter histogram data is restricted
+ * to ads from the same custom audience, which is identified by the buyer from {@link
+ * #getBuyer()}, the custom audience's owner app package name, and the custom audience name from
+ * {@link #getCustomAudienceName()}.
+ */
+ @NonNull
+ public String getCustomAudienceOwner() {
+ return mCustomAudienceOwner;
+ }
+
+ /**
+ * Gets the buyer-generated name for the custom audience which is associated with the overridden
+ * ad counter histogram data.
+ *
+ * <p>For {@link FrequencyCapFilters#AD_EVENT_TYPE_WIN}, ad counter histogram data is restricted
+ * to ads from the same custom audience, which is identified by the buyer from {@link
+ * #getBuyer()}, the custom audience's owner app package name from {@link
+ * #getCustomAudienceOwner()}, and the custom audience name.
+ */
+ @NonNull
+ public String getCustomAudienceName() {
+ return mCustomAudienceName;
+ }
+
+ @Override
+ public String toString() {
+ return "SetAdCounterHistogramOverrideRequest{"
+ + "mAdEventType="
+ + mAdEventType
+ + ", mAdCounterKey="
+ + mAdCounterKey
+ + ", mHistogramTimestamps="
+ + mHistogramTimestamps
+ + ", mBuyer="
+ + mBuyer
+ + ", mCustomAudienceOwner='"
+ + mCustomAudienceOwner
+ + "', mCustomAudienceName='"
+ + mCustomAudienceName
+ + "'}";
+ }
+
+ /** Builder for {@link SetAdCounterHistogramOverrideRequest} objects. */
+ public static final class Builder {
+ @FrequencyCapFilters.AdEventType private int mAdEventType = AD_EVENT_TYPE_INVALID;
+ private int mAdCounterKey;
+ @NonNull private List<Instant> mHistogramTimestamps = new ArrayList<>();
+ @Nullable private AdTechIdentifier mBuyer;
+ @Nullable private String mCustomAudienceOwner;
+ @Nullable private String mCustomAudienceName;
+
+ public Builder() {}
+
+ /**
+ * Sets the {@link FrequencyCapFilters.AdEventType} for the ad counter histogram override.
+ *
+ * <p>See {@link #getAdEventType()} for more information.
+ */
+ @NonNull
+ public Builder setAdEventType(@FrequencyCapFilters.AdEventType int adEventType) {
+ mAdEventType = adEventType;
+ return this;
+ }
+
+ /**
+ * Sets the ad counter key for the ad counter histogram override.
+ *
+ * <p>See {@link #getAdCounterKey()} for more information.
+ */
+ @NonNull
+ public Builder setAdCounterKey(int adCounterKey) {
+ mAdCounterKey = adCounterKey;
+ return this;
+ }
+
+ /**
+ * Sets the list of {@link Instant} objects for the ad counter histogram override.
+ *
+ * <p>See {@link #getHistogramTimestamps()} for more information.
+ */
+ @NonNull
+ public Builder setHistogramTimestamps(@NonNull List<Instant> histogramTimestamps) {
+ Objects.requireNonNull(histogramTimestamps, NULL_HISTOGRAM_TIMESTAMPS_MESSAGE);
+ mHistogramTimestamps = histogramTimestamps;
+ return this;
+ }
+
+ /**
+ * Sets the {@link AdTechIdentifier} for the buyer which owns the ad counter histogram.
+ *
+ * <p>See {@link #getBuyer()} for more information.
+ */
+ @NonNull
+ public Builder setBuyer(@NonNull AdTechIdentifier buyer) {
+ Objects.requireNonNull(buyer, NULL_BUYER_MESSAGE);
+ mBuyer = buyer;
+ return this;
+ }
+
+ /**
+ * Sets the package name for the app which generated the custom audience which is associated
+ * with the overridden ad counter histogram data.
+ *
+ * <p>See {@link #getCustomAudienceOwner()} for more information.
+ */
+ @NonNull
+ public Builder setCustomAudienceOwner(@NonNull String customAudienceOwner) {
+ Objects.requireNonNull(customAudienceOwner, NULL_CUSTOM_AUDIENCE_OWNER_MESSAGE);
+ mCustomAudienceOwner = customAudienceOwner;
+ return this;
+ }
+
+ /**
+ * Sets the buyer-generated name for the custom audience which is associated with the
+ * overridden ad counter histogram data.
+ *
+ * <p>See {@link #getCustomAudienceName()} for more information.
+ */
+ @NonNull
+ public Builder setCustomAudienceName(@NonNull String customAudienceName) {
+ Objects.requireNonNull(customAudienceName, NULL_CUSTOM_AUDIENCE_NAME_MESSAGE);
+ mCustomAudienceName = customAudienceName;
+ return this;
+ }
+
+ /**
+ * Builds the {@link SetAdCounterHistogramOverrideRequest} object.
+ *
+ * @throws NullPointerException if any parameters are not set
+ * @throws IllegalArgumentException if the ad event type is invalid
+ */
+ @NonNull
+ public SetAdCounterHistogramOverrideRequest build()
+ throws NullPointerException, IllegalArgumentException {
+ Preconditions.checkArgument(
+ mAdEventType != AD_EVENT_TYPE_INVALID, UNSET_AD_EVENT_TYPE_MESSAGE);
+ Objects.requireNonNull(mBuyer, NULL_BUYER_MESSAGE);
+ Objects.requireNonNull(mCustomAudienceOwner, NULL_CUSTOM_AUDIENCE_OWNER_MESSAGE);
+ Objects.requireNonNull(mCustomAudienceName, NULL_CUSTOM_AUDIENCE_NAME_MESSAGE);
+
+ return new SetAdCounterHistogramOverrideRequest(this);
+ }
+ }
+}
diff --git a/android-35/android/adservices/adselection/SetAppInstallAdvertisersInput.java b/android-35/android/adservices/adselection/SetAppInstallAdvertisersInput.java
new file mode 100644
index 0000000..938c96e
--- /dev/null
+++ b/android-35/android/adservices/adselection/SetAppInstallAdvertisersInput.java
@@ -0,0 +1,140 @@
+/*
+ * 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.adselection;
+
+import android.adservices.common.AdTechIdentifier;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.adservices.AdServicesParcelableUtil;
+
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Represent input params to the setAppInstallAdvertisers API.
+ *
+ * @hide
+ */
+public final class SetAppInstallAdvertisersInput implements Parcelable {
+ @NonNull private final Set<AdTechIdentifier> mAdvertisers;
+ @NonNull private final String mCallerPackageName;
+
+ @NonNull
+ public static final Creator<SetAppInstallAdvertisersInput> CREATOR =
+ new Creator<SetAppInstallAdvertisersInput>() {
+ @NonNull
+ @Override
+ public SetAppInstallAdvertisersInput createFromParcel(@NonNull Parcel in) {
+ Objects.requireNonNull(in);
+ return new SetAppInstallAdvertisersInput(in);
+ }
+
+ @NonNull
+ @Override
+ public SetAppInstallAdvertisersInput[] newArray(int size) {
+ return new SetAppInstallAdvertisersInput[size];
+ }
+ };
+
+ private SetAppInstallAdvertisersInput(
+ @NonNull Set<AdTechIdentifier> advertisers, @NonNull String callerPackageName) {
+ Objects.requireNonNull(advertisers);
+ Objects.requireNonNull(callerPackageName);
+
+ this.mAdvertisers = advertisers;
+ this.mCallerPackageName = callerPackageName;
+ }
+
+ private SetAppInstallAdvertisersInput(@NonNull Parcel in) {
+ Objects.requireNonNull(in);
+
+ this.mAdvertisers =
+ AdServicesParcelableUtil.readSetFromParcel(in, AdTechIdentifier.CREATOR);
+ this.mCallerPackageName = in.readString();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ Objects.requireNonNull(dest);
+
+ AdServicesParcelableUtil.writeSetToParcel(dest, mAdvertisers);
+ dest.writeString(mCallerPackageName);
+ }
+
+ /**
+ * Returns the advertisers, one of the inputs to {@link SetAppInstallAdvertisersInput} as noted
+ * in {@code AdSelectionService}.
+ */
+ @NonNull
+ public Set<AdTechIdentifier> getAdvertisers() {
+ return mAdvertisers;
+ }
+
+ /** @return the caller package name */
+ @NonNull
+ public String getCallerPackageName() {
+ return mCallerPackageName;
+ }
+
+ /**
+ * Builder for {@link SetAppInstallAdvertisersInput} objects.
+ *
+ * @hide
+ */
+ public static final class Builder {
+ @Nullable private Set<AdTechIdentifier> mAdvertisers;
+ @Nullable private String mCallerPackageName;
+
+ public Builder() {}
+
+ /** Set the advertisers. */
+ @NonNull
+ public SetAppInstallAdvertisersInput.Builder setAdvertisers(
+ @NonNull Set<AdTechIdentifier> advertisers) {
+ Objects.requireNonNull(advertisers);
+ this.mAdvertisers = advertisers;
+ return this;
+ }
+
+ /** Sets the caller's package name. */
+ @NonNull
+ public SetAppInstallAdvertisersInput.Builder setCallerPackageName(
+ @NonNull String callerPackageName) {
+ Objects.requireNonNull(callerPackageName);
+
+ this.mCallerPackageName = callerPackageName;
+ return this;
+ }
+
+ /** Builds a {@link SetAppInstallAdvertisersInput} instance. */
+ @NonNull
+ public SetAppInstallAdvertisersInput build() {
+ Objects.requireNonNull(mAdvertisers);
+ Objects.requireNonNull(mCallerPackageName);
+
+ return new SetAppInstallAdvertisersInput(mAdvertisers, mCallerPackageName);
+ }
+ }
+}
diff --git a/android-35/android/adservices/adselection/SetAppInstallAdvertisersRequest.java b/android-35/android/adservices/adselection/SetAppInstallAdvertisersRequest.java
new file mode 100644
index 0000000..8cbcb5e
--- /dev/null
+++ b/android-35/android/adservices/adselection/SetAppInstallAdvertisersRequest.java
@@ -0,0 +1,75 @@
+/*
+ * 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.adselection;
+
+import android.adservices.common.AdTechIdentifier;
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.adservices.flags.Flags;
+
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
+
+/** Represents input parameters to the setAppInstallAdvertiser API. */
+@FlaggedApi(Flags.FLAG_FLEDGE_AD_SELECTION_FILTERING_ENABLED)
+public class SetAppInstallAdvertisersRequest {
+ @NonNull private final Set<AdTechIdentifier> mAdvertisers;
+
+ private SetAppInstallAdvertisersRequest(@NonNull Set<AdTechIdentifier> advertisers) {
+ Objects.requireNonNull(advertisers);
+
+ mAdvertisers = new HashSet<>(advertisers);
+ }
+
+ /**
+ * Returns the set of advertisers that will be able to run app install filters based on this
+ * app's presence on the device after a call to SetAppInstallAdvertisers is made with this as
+ * input.
+ */
+ @NonNull
+ public Set<AdTechIdentifier> getAdvertisers() {
+ return mAdvertisers;
+ }
+
+ public static final class Builder {
+ @Nullable private Set<AdTechIdentifier> mAdvertisers;
+
+ public Builder() {}
+
+ /**
+ * Sets list of allowed advertisers. See {@link SetAppInstallAdvertisersRequest
+ * #getAdvertisers()}
+ */
+ @NonNull
+ public SetAppInstallAdvertisersRequest.Builder setAdvertisers(
+ @NonNull Set<AdTechIdentifier> advertisers) {
+ Objects.requireNonNull(advertisers);
+
+ mAdvertisers = new HashSet<>(advertisers);
+ return this;
+ }
+
+ /** Builds a {@link SetAppInstallAdvertisersRequest} instance. */
+ @NonNull
+ public SetAppInstallAdvertisersRequest build() {
+ return new SetAppInstallAdvertisersRequest(mAdvertisers);
+ }
+ }
+}
diff --git a/android-35/android/adservices/adselection/SignedContextualAds.java b/android-35/android/adservices/adselection/SignedContextualAds.java
new file mode 100644
index 0000000..45a7a89
--- /dev/null
+++ b/android-35/android/adservices/adselection/SignedContextualAds.java
@@ -0,0 +1,269 @@
+/*
+ * 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.adselection;
+
+import android.adservices.common.AdTechIdentifier;
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.adservices.flags.Flags;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Contains a list of buyer supplied {@link AdWithBid} bundle and its signature.
+ *
+ * <p>Instances of this class are created by SDKs to be injected as part of {@link
+ * AdSelectionConfig} and passed to {@link AdSelectionManager#selectAds}
+ *
+ * <p>SignedContextualAds are signed using ECDSA algorithm with SHA256 hashing algorithm (aka
+ * SHA256withECDSA). Keys used should belong to P-256 curve (aka “secp256r1” or “prime256v1”).
+ *
+ * <p>Signature should include the buyer, decisionLogicUri and adsWithBid fields.
+ *
+ * <p>While creating the signature a specific serialization rules must be followed as it's outlined
+ * here:
+ *
+ * <ul>
+ * <li>{@code Objects} concatenate the serialized values of their fields with the {@code |} (pipe)
+ * in between each field
+ * <li>{@code All fields} are sorted by alphabetical order within the object
+ * <li>{@code Nullable fields} are skipped if they are null/unset
+ * <li>{@code Doubles} are converted to String preserving precision
+ * <li>{@code Integers} are converted to string values
+ * <li>{@code Sets} are sorted alphabetically
+ * <li>{@code Lists} keep the same order
+ * <li>{@code Strings} get encoded into byte[] using UTF-8 encoding
+ * </ul>
+ */
+@FlaggedApi(Flags.FLAG_FLEDGE_AD_SELECTION_FILTERING_ENABLED)
+public final class SignedContextualAds implements Parcelable {
+ private static final String BUYER_CANNOT_BE_NULL = "Buyer cannot be null.";
+ private static final String DECISION_LOGIC_URI_CANNOT_BE_NULL =
+ "DecisionLogicUri cannot be null.";
+ private static final String ADS_WITH_BID_CANNOT_BE_NULL = "AdsWithBid cannot be null.";
+ private static final String SIGNATURE_CANNOT_BE_NULL = "Signature cannot be null.";
+ @NonNull private final AdTechIdentifier mBuyer;
+ @NonNull private final Uri mDecisionLogicUri;
+ @NonNull private final List<AdWithBid> mAdsWithBid;
+ @NonNull private final byte[] mSignature;
+
+ @NonNull
+ public static final Creator<SignedContextualAds> CREATOR =
+ new Creator<>() {
+ @Override
+ public SignedContextualAds createFromParcel(@NonNull Parcel in) {
+ Objects.requireNonNull(in);
+ return new SignedContextualAds(in);
+ }
+
+ @Override
+ public SignedContextualAds[] newArray(int size) {
+ return new SignedContextualAds[0];
+ }
+ };
+
+ private SignedContextualAds(
+ @NonNull AdTechIdentifier buyer,
+ @NonNull Uri decisionLogicUri,
+ @NonNull List<AdWithBid> adsWithBid,
+ @NonNull byte[] signature) {
+ this.mBuyer = buyer;
+ this.mDecisionLogicUri = decisionLogicUri;
+ this.mAdsWithBid = adsWithBid;
+ this.mSignature = signature;
+ }
+
+ private SignedContextualAds(@NonNull Parcel in) {
+ Objects.requireNonNull(in);
+ mBuyer = AdTechIdentifier.CREATOR.createFromParcel(in);
+ mDecisionLogicUri = Uri.CREATOR.createFromParcel(in);
+ mAdsWithBid = in.createTypedArrayList(AdWithBid.CREATOR);
+ mSignature = in.createByteArray();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ Objects.requireNonNull(dest);
+
+ mBuyer.writeToParcel(dest, flags);
+ mDecisionLogicUri.writeToParcel(dest, flags);
+ dest.writeTypedList(mAdsWithBid);
+ dest.writeByteArray(mSignature);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof SignedContextualAds)) return false;
+ SignedContextualAds that = (SignedContextualAds) o;
+ return Objects.equals(mBuyer, that.mBuyer)
+ && Objects.equals(mDecisionLogicUri, that.mDecisionLogicUri)
+ && Objects.equals(mAdsWithBid, that.mAdsWithBid)
+ && Arrays.equals(mSignature, that.mSignature);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mBuyer, mDecisionLogicUri, mAdsWithBid, Arrays.hashCode(mSignature));
+ }
+
+ /**
+ * @return the Ad tech identifier from which this contextual Ad would have been downloaded
+ */
+ @NonNull
+ public AdTechIdentifier getBuyer() {
+ return mBuyer;
+ }
+
+ /**
+ * @return the URI used to retrieve the updateBid() and reportWin() function used during the ad
+ * selection and reporting process
+ */
+ @NonNull
+ public Uri getDecisionLogicUri() {
+ return mDecisionLogicUri;
+ }
+
+ /**
+ * @return the Ad data with bid value associated with this ad
+ */
+ @NonNull
+ public List<AdWithBid> getAdsWithBid() {
+ return mAdsWithBid;
+ }
+
+ /**
+ * Returns a copy of the signature for the contextual ads object.
+ *
+ * <p>See {@link SignedContextualAds} for more details.
+ *
+ * @return the signature
+ */
+ @NonNull
+ public byte[] getSignature() {
+ return Arrays.copyOf(mSignature, mSignature.length);
+ }
+
+ @Override
+ public String toString() {
+ return "SignedContextualAds{"
+ + "mBuyer="
+ + mBuyer
+ + ", mDecisionLogicUri="
+ + mDecisionLogicUri
+ + ", mAdsWithBid="
+ + mAdsWithBid
+ + ", mSignature="
+ + Arrays.toString(mSignature)
+ + '}';
+ }
+
+ /** Builder for {@link SignedContextualAds} object */
+ public static final class Builder {
+ @Nullable private AdTechIdentifier mBuyer;
+ @Nullable private Uri mDecisionLogicUri;
+ @Nullable private List<AdWithBid> mAdsWithBid;
+ @Nullable private byte[] mSignature;
+
+ public Builder() {}
+
+ /** Returns a {@link SignedContextualAds.Builder} from a {@link SignedContextualAds}. */
+ public Builder(@NonNull SignedContextualAds signedContextualAds) {
+ Objects.requireNonNull(signedContextualAds);
+
+ this.mBuyer = signedContextualAds.getBuyer();
+ this.mDecisionLogicUri = signedContextualAds.getDecisionLogicUri();
+ this.mAdsWithBid = signedContextualAds.getAdsWithBid();
+ this.mSignature = signedContextualAds.getSignature();
+ }
+
+ /**
+ * Sets the buyer Ad tech Identifier
+ *
+ * <p>See {@link #getBuyer()} for more details
+ */
+ @NonNull
+ public SignedContextualAds.Builder setBuyer(@NonNull AdTechIdentifier buyer) {
+ Objects.requireNonNull(buyer, BUYER_CANNOT_BE_NULL);
+
+ this.mBuyer = buyer;
+ return this;
+ }
+
+ /**
+ * Sets the URI to fetch the decision logic used in ad selection and reporting
+ *
+ * <p>See {@link #getDecisionLogicUri()} for more details
+ */
+ @NonNull
+ public SignedContextualAds.Builder setDecisionLogicUri(@NonNull Uri decisionLogicUri) {
+ Objects.requireNonNull(decisionLogicUri, DECISION_LOGIC_URI_CANNOT_BE_NULL);
+
+ this.mDecisionLogicUri = decisionLogicUri;
+ return this;
+ }
+
+ /**
+ * Sets the Ads with pre-defined bid values
+ *
+ * <p>See {@link #getAdsWithBid()} for more details
+ */
+ @NonNull
+ public SignedContextualAds.Builder setAdsWithBid(@NonNull List<AdWithBid> adsWithBid) {
+ Objects.requireNonNull(adsWithBid, ADS_WITH_BID_CANNOT_BE_NULL);
+
+ this.mAdsWithBid = adsWithBid;
+ return this;
+ }
+
+ /** Sets the copied signature */
+ @NonNull
+ public SignedContextualAds.Builder setSignature(@NonNull byte[] signature) {
+ Objects.requireNonNull(signature, SIGNATURE_CANNOT_BE_NULL);
+
+ this.mSignature = Arrays.copyOf(signature, signature.length);
+ return this;
+ }
+
+ /**
+ * Builds a {@link SignedContextualAds} instance.
+ *
+ * @throws NullPointerException if any required params are null
+ */
+ @NonNull
+ public SignedContextualAds build() {
+ Objects.requireNonNull(mBuyer, BUYER_CANNOT_BE_NULL);
+ Objects.requireNonNull(mDecisionLogicUri, DECISION_LOGIC_URI_CANNOT_BE_NULL);
+ Objects.requireNonNull(mAdsWithBid, ADS_WITH_BID_CANNOT_BE_NULL);
+ Objects.requireNonNull(mSignature, SIGNATURE_CANNOT_BE_NULL);
+
+ return new SignedContextualAds(mBuyer, mDecisionLogicUri, mAdsWithBid, mSignature);
+ }
+ }
+}
diff --git a/android-35/android/adservices/adselection/TestAdSelectionManager.java b/android-35/android/adservices/adselection/TestAdSelectionManager.java
new file mode 100644
index 0000000..c45775c
--- /dev/null
+++ b/android-35/android/adservices/adselection/TestAdSelectionManager.java
@@ -0,0 +1,572 @@
+/*
+ * 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.adselection;
+
+import static android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_AD_SELECTION;
+import static android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE;
+import static android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_PROTECTED_SIGNALS;
+
+import android.adservices.common.AdServicesStatusUtils;
+import android.adservices.common.FledgeErrorResponse;
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.os.Build;
+import android.os.OutcomeReceiver;
+import android.os.RemoteException;
+
+import androidx.annotation.RequiresApi;
+
+import com.android.adservices.LoggerFactory;
+
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/**
+ * {@link TestAdSelectionManager} provides APIs for apps and ad SDKs to test ad selection processes.
+ *
+ * <p>These APIs are intended to be used for end-to-end testing. They are enabled only for
+ * debuggable apps on phones running a debuggable OS build with developer options enabled.
+ */
+@RequiresApi(Build.VERSION_CODES.S)
+public class TestAdSelectionManager {
+ private static final LoggerFactory.Logger sLogger = LoggerFactory.getFledgeLogger();
+
+ private final AdSelectionManager mAdSelectionManager;
+
+ TestAdSelectionManager(@NonNull AdSelectionManager adSelectionManager) {
+ Objects.requireNonNull(adSelectionManager);
+
+ mAdSelectionManager = adSelectionManager;
+ }
+
+ // TODO(b/289362476): Add override APIs for server auction key fetch
+
+ /**
+ * Overrides the AdSelection API for a given {@link AdSelectionConfig} to avoid fetching data
+ * from remote servers and use the data provided in {@link AddAdSelectionOverrideRequest}
+ * instead. The {@link AddAdSelectionOverrideRequest} is provided by the Ads SDK.
+ *
+ * <p>This method is intended to be used for end-to-end testing. This API is enabled only for
+ * apps in debug mode with developer options enabled.
+ *
+ * @throws IllegalStateException if this API is not enabled for the caller
+ * <p>The receiver either returns a {@code void} for a successful run, or an {@link
+ * Exception} indicates the error.
+ */
+ @RequiresPermission(
+ anyOf = {
+ ACCESS_ADSERVICES_CUSTOM_AUDIENCE,
+ ACCESS_ADSERVICES_PROTECTED_SIGNALS,
+ ACCESS_ADSERVICES_AD_SELECTION
+ })
+ public void overrideAdSelectionConfigRemoteInfo(
+ @NonNull AddAdSelectionOverrideRequest request,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OutcomeReceiver<Object, Exception> receiver) {
+ Objects.requireNonNull(request);
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(receiver);
+
+ try {
+ final AdSelectionService service =
+ mAdSelectionManager.getServiceProvider().getService();
+ service.overrideAdSelectionConfigRemoteInfo(
+ request.getAdSelectionConfig(),
+ request.getDecisionLogicJs(),
+ request.getTrustedScoringSignals(),
+ request.getPerBuyerDecisionLogic(),
+ new AdSelectionOverrideCallback.Stub() {
+ @Override
+ public void onSuccess() {
+ executor.execute(() -> receiver.onResult(new Object()));
+ }
+
+ @Override
+ public void onFailure(FledgeErrorResponse failureParcel) {
+ executor.execute(
+ () ->
+ receiver.onError(
+ AdServicesStatusUtils.asException(
+ failureParcel)));
+ }
+ });
+ } catch (NullPointerException e) {
+ sLogger.e(e, "Unable to find the AdSelection service.");
+ receiver.onError(
+ new IllegalStateException("Unable to find the AdSelection service.", e));
+ } catch (RemoteException e) {
+ sLogger.e(e, "Exception");
+ receiver.onError(new IllegalStateException("Failure of AdSelection service.", e));
+ }
+ }
+
+ /**
+ * Removes an override for {@link AdSelectionConfig} in the Ad Selection API with associated the
+ * data in {@link RemoveAdSelectionOverrideRequest}. The {@link
+ * RemoveAdSelectionOverrideRequest} is provided by the Ads SDK.
+ *
+ * <p>This method is intended to be used for end-to-end testing. This API is enabled only for
+ * apps in debug mode with developer options enabled.
+ *
+ * @throws IllegalStateException if this API is not enabled for the caller
+ * <p>The receiver either returns a {@code void} for a successful run, or an {@link
+ * Exception} indicates the error.
+ */
+ @RequiresPermission(
+ anyOf = {
+ ACCESS_ADSERVICES_CUSTOM_AUDIENCE,
+ ACCESS_ADSERVICES_PROTECTED_SIGNALS,
+ ACCESS_ADSERVICES_AD_SELECTION
+ })
+ public void removeAdSelectionConfigRemoteInfoOverride(
+ @NonNull RemoveAdSelectionOverrideRequest request,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OutcomeReceiver<Object, Exception> receiver) {
+ Objects.requireNonNull(request);
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(receiver);
+
+ try {
+ final AdSelectionService service =
+ mAdSelectionManager.getServiceProvider().getService();
+ service.removeAdSelectionConfigRemoteInfoOverride(
+ request.getAdSelectionConfig(),
+ new AdSelectionOverrideCallback.Stub() {
+ @Override
+ public void onSuccess() {
+ executor.execute(() -> receiver.onResult(new Object()));
+ }
+
+ @Override
+ public void onFailure(FledgeErrorResponse failureParcel) {
+ executor.execute(
+ () ->
+ receiver.onError(
+ AdServicesStatusUtils.asException(
+ failureParcel)));
+ }
+ });
+ } catch (NullPointerException e) {
+ sLogger.e(e, "Unable to find the AdSelection service.");
+ receiver.onError(
+ new IllegalStateException("Unable to find the AdSelection service.", e));
+ } catch (RemoteException e) {
+ sLogger.e(e, "Exception");
+ receiver.onError(new IllegalStateException("Failure of AdSelection service.", e));
+ }
+ }
+
+ /**
+ * Removes all override data for {@link AdSelectionConfig} in the Ad Selection API.
+ *
+ * <p>This method is intended to be used for end-to-end testing. This API is enabled only for
+ * apps in debug mode with developer options enabled.
+ *
+ * @throws IllegalStateException if this API is not enabled for the caller
+ * <p>The receiver either returns a {@code void} for a successful run, or an {@link
+ * Exception} indicates the error.
+ */
+ @RequiresPermission(
+ anyOf = {
+ ACCESS_ADSERVICES_CUSTOM_AUDIENCE,
+ ACCESS_ADSERVICES_PROTECTED_SIGNALS,
+ ACCESS_ADSERVICES_AD_SELECTION
+ })
+ public void resetAllAdSelectionConfigRemoteOverrides(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OutcomeReceiver<Object, Exception> receiver) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(receiver);
+
+ try {
+ final AdSelectionService service =
+ mAdSelectionManager.getServiceProvider().getService();
+ service.resetAllAdSelectionConfigRemoteOverrides(
+ new AdSelectionOverrideCallback.Stub() {
+ @Override
+ public void onSuccess() {
+ executor.execute(() -> receiver.onResult(new Object()));
+ }
+
+ @Override
+ public void onFailure(FledgeErrorResponse failureParcel) {
+ executor.execute(
+ () ->
+ receiver.onError(
+ AdServicesStatusUtils.asException(
+ failureParcel)));
+ }
+ });
+ } catch (NullPointerException e) {
+ sLogger.e(e, "Unable to find the AdSelection service.");
+ receiver.onError(
+ new IllegalStateException("Unable to find the AdSelection service.", e));
+ } catch (RemoteException e) {
+ sLogger.e(e, "Exception");
+ receiver.onError(new IllegalStateException("Failure of AdSelection service.", e));
+ }
+ }
+
+ /**
+ * Overrides the AdSelection API for {@link AdSelectionFromOutcomesConfig} to avoid fetching
+ * data from remote servers and use the data provided in {@link
+ * AddAdSelectionFromOutcomesOverrideRequest} instead. The {@link
+ * AddAdSelectionFromOutcomesOverrideRequest} is provided by the Ads SDK.
+ *
+ * <p>This method is intended to be used for end-to-end testing. This API is enabled only for
+ * apps in debug mode with developer options enabled.
+ *
+ * @throws IllegalStateException if this API is not enabled for the caller
+ * <p>The receiver either returns a {@code void} for a successful run, or an {@link
+ * Exception} indicates the error.
+ */
+ @RequiresPermission(
+ anyOf = {
+ ACCESS_ADSERVICES_CUSTOM_AUDIENCE,
+ ACCESS_ADSERVICES_PROTECTED_SIGNALS,
+ ACCESS_ADSERVICES_AD_SELECTION
+ })
+ public void overrideAdSelectionFromOutcomesConfigRemoteInfo(
+ @NonNull AddAdSelectionFromOutcomesOverrideRequest request,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OutcomeReceiver<Object, Exception> receiver) {
+ Objects.requireNonNull(request);
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(receiver);
+
+ try {
+ final AdSelectionService service =
+ mAdSelectionManager.getServiceProvider().getService();
+ service.overrideAdSelectionFromOutcomesConfigRemoteInfo(
+ request.getAdSelectionFromOutcomesConfig(),
+ request.getOutcomeSelectionLogicJs(),
+ request.getOutcomeSelectionTrustedSignals(),
+ new AdSelectionOverrideCallback.Stub() {
+ @Override
+ public void onSuccess() {
+ executor.execute(() -> receiver.onResult(new Object()));
+ }
+
+ @Override
+ public void onFailure(FledgeErrorResponse failureParcel) {
+ executor.execute(
+ () ->
+ receiver.onError(
+ AdServicesStatusUtils.asException(
+ failureParcel)));
+ }
+ });
+ } catch (NullPointerException e) {
+ sLogger.e(e, "Unable to find the AdSelection service.");
+ receiver.onError(
+ new IllegalStateException("Unable to find the AdSelection service.", e));
+ } catch (RemoteException e) {
+ sLogger.e(e, "Exception");
+ receiver.onError(new IllegalStateException("Failure of AdSelection service.", e));
+ }
+ }
+
+ /**
+ * Removes an override for {@link AdSelectionFromOutcomesConfig} in th Ad Selection API with
+ * associated the data in {@link RemoveAdSelectionOverrideRequest}. The {@link
+ * RemoveAdSelectionOverrideRequest} is provided by the Ads SDK.
+ *
+ * <p>This method is intended to be used for end-to-end testing. This API is enabled only for
+ * apps in debug mode with developer options enabled.
+ *
+ * @throws IllegalStateException if this API is not enabled for the caller
+ * <p>The receiver either returns a {@code void} for a successful run, or an {@link
+ * Exception} indicates the error.
+ */
+ @RequiresPermission(
+ anyOf = {
+ ACCESS_ADSERVICES_CUSTOM_AUDIENCE,
+ ACCESS_ADSERVICES_PROTECTED_SIGNALS,
+ ACCESS_ADSERVICES_AD_SELECTION
+ })
+ public void removeAdSelectionFromOutcomesConfigRemoteInfoOverride(
+ @NonNull RemoveAdSelectionFromOutcomesOverrideRequest request,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OutcomeReceiver<Object, Exception> receiver) {
+ Objects.requireNonNull(request);
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(receiver);
+
+ try {
+ final AdSelectionService service =
+ mAdSelectionManager.getServiceProvider().getService();
+ service.removeAdSelectionFromOutcomesConfigRemoteInfoOverride(
+ request.getAdSelectionFromOutcomesConfig(),
+ new AdSelectionOverrideCallback.Stub() {
+ @Override
+ public void onSuccess() {
+ executor.execute(() -> receiver.onResult(new Object()));
+ }
+
+ @Override
+ public void onFailure(FledgeErrorResponse failureParcel) {
+ executor.execute(
+ () ->
+ receiver.onError(
+ AdServicesStatusUtils.asException(
+ failureParcel)));
+ }
+ });
+ } catch (NullPointerException e) {
+ sLogger.e(e, "Unable to find the AdSelection service.");
+ receiver.onError(
+ new IllegalStateException("Unable to find the AdSelection service.", e));
+ } catch (RemoteException e) {
+ sLogger.e(e, "Exception");
+ receiver.onError(new IllegalStateException("Failure of AdSelection service.", e));
+ }
+ }
+
+ /**
+ * Removes all override data for {@link AdSelectionFromOutcomesConfig} in the Ad Selection API.
+ *
+ * <p>This method is intended to be used for end-to-end testing. This API is enabled only for
+ * apps in debug mode with developer options enabled.
+ *
+ * @throws IllegalStateException if this API is not enabled for the caller
+ * <p>The receiver either returns a {@code void} for a successful run, or an {@link
+ * Exception} indicates the error.
+ */
+ @RequiresPermission(
+ anyOf = {
+ ACCESS_ADSERVICES_CUSTOM_AUDIENCE,
+ ACCESS_ADSERVICES_PROTECTED_SIGNALS,
+ ACCESS_ADSERVICES_AD_SELECTION
+ })
+ public void resetAllAdSelectionFromOutcomesConfigRemoteOverrides(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OutcomeReceiver<Object, Exception> receiver) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(receiver);
+
+ try {
+ final AdSelectionService service =
+ mAdSelectionManager.getServiceProvider().getService();
+ service.resetAllAdSelectionFromOutcomesConfigRemoteOverrides(
+ new AdSelectionOverrideCallback.Stub() {
+ @Override
+ public void onSuccess() {
+ executor.execute(() -> receiver.onResult(new Object()));
+ }
+
+ @Override
+ public void onFailure(FledgeErrorResponse failureParcel) {
+ executor.execute(
+ () ->
+ receiver.onError(
+ AdServicesStatusUtils.asException(
+ failureParcel)));
+ }
+ });
+ } catch (NullPointerException e) {
+ sLogger.e(e, "Unable to find the AdSelection service.");
+ receiver.onError(
+ new IllegalStateException("Unable to find the AdSelection service.", e));
+ } catch (RemoteException e) {
+ sLogger.e(e, "Exception");
+ receiver.onError(new IllegalStateException("Failure of AdSelection service.", e));
+ }
+ }
+
+ /**
+ * Sets the override for event histogram data, which is used in frequency cap filtering during
+ * ad selection.
+ *
+ * <p>This method is intended to be used for end-to-end testing. This API is enabled only for
+ * apps in debug mode with developer options enabled.
+ *
+ * <p>The given {@code outcomeReceiver} either returns an empty {@link Object} if successful or
+ * an {@link Exception} which indicates the error.
+ *
+ * @throws IllegalStateException if this API is not enabled for the caller
+ * @hide
+ */
+ // TODO(b/265204820): Unhide for frequency cap dev override API review
+ @RequiresPermission(
+ anyOf = {
+ ACCESS_ADSERVICES_CUSTOM_AUDIENCE,
+ ACCESS_ADSERVICES_PROTECTED_SIGNALS,
+ ACCESS_ADSERVICES_AD_SELECTION
+ })
+ public void setAdCounterHistogramOverride(
+ @NonNull SetAdCounterHistogramOverrideRequest setRequest,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OutcomeReceiver<Object, Exception> outcomeReceiver) {
+ Objects.requireNonNull(setRequest, "Request must not be null");
+ Objects.requireNonNull(executor, "Executor must not be null");
+ Objects.requireNonNull(outcomeReceiver, "Outcome receiver must not be null");
+
+ try {
+ final AdSelectionService service =
+ Objects.requireNonNull(mAdSelectionManager.getServiceProvider().getService());
+ service.setAdCounterHistogramOverride(
+ new SetAdCounterHistogramOverrideInput.Builder()
+ .setAdEventType(setRequest.getAdEventType())
+ .setAdCounterKey(setRequest.getAdCounterKey())
+ .setHistogramTimestamps(setRequest.getHistogramTimestamps())
+ .setBuyer(setRequest.getBuyer())
+ .setCustomAudienceOwner(setRequest.getCustomAudienceOwner())
+ .setCustomAudienceName(setRequest.getCustomAudienceName())
+ .build(),
+ new AdSelectionOverrideCallback.Stub() {
+ @Override
+ public void onSuccess() {
+ executor.execute(() -> outcomeReceiver.onResult(new Object()));
+ }
+
+ @Override
+ public void onFailure(FledgeErrorResponse failureParcel) {
+ executor.execute(
+ () ->
+ outcomeReceiver.onError(
+ AdServicesStatusUtils.asException(
+ failureParcel)));
+ }
+ });
+ } catch (NullPointerException e) {
+ sLogger.e(e, "Unable to find the AdSelection service");
+ outcomeReceiver.onError(
+ new IllegalStateException("Unable to find the AdSelection service", e));
+ } catch (RemoteException e) {
+ sLogger.e(e, "Remote exception encountered while updating ad counter histogram");
+ outcomeReceiver.onError(new IllegalStateException("Failure of AdSelection service", e));
+ }
+ }
+
+ /**
+ * Removes an override for event histogram data, which is used in frequency cap filtering during
+ * ad selection.
+ *
+ * <p>This method is intended to be used for end-to-end testing. This API is enabled only for
+ * apps in debug mode with developer options enabled.
+ *
+ * <p>The given {@code outcomeReceiver} either returns an empty {@link Object} if successful or
+ * an {@link Exception} which indicates the error.
+ *
+ * @throws IllegalStateException if this API is not enabled for the caller
+ * @hide
+ */
+ // TODO(b/265204820): Unhide for frequency cap dev override API review
+ @RequiresPermission(
+ anyOf = {
+ ACCESS_ADSERVICES_CUSTOM_AUDIENCE,
+ ACCESS_ADSERVICES_PROTECTED_SIGNALS,
+ ACCESS_ADSERVICES_AD_SELECTION
+ })
+ public void removeAdCounterHistogramOverride(
+ @NonNull RemoveAdCounterHistogramOverrideRequest removeRequest,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OutcomeReceiver<Object, Exception> outcomeReceiver) {
+ Objects.requireNonNull(removeRequest, "Request must not be null");
+ Objects.requireNonNull(executor, "Executor must not be null");
+ Objects.requireNonNull(outcomeReceiver, "Outcome receiver must not be null");
+
+ try {
+ final AdSelectionService service =
+ Objects.requireNonNull(mAdSelectionManager.getServiceProvider().getService());
+ service.removeAdCounterHistogramOverride(
+ new RemoveAdCounterHistogramOverrideInput.Builder()
+ .setAdEventType(removeRequest.getAdEventType())
+ .setAdCounterKey(removeRequest.getAdCounterKey())
+ .setBuyer(removeRequest.getBuyer())
+ .build(),
+ new AdSelectionOverrideCallback.Stub() {
+ @Override
+ public void onSuccess() {
+ executor.execute(() -> outcomeReceiver.onResult(new Object()));
+ }
+
+ @Override
+ public void onFailure(FledgeErrorResponse failureParcel) {
+ executor.execute(
+ () ->
+ outcomeReceiver.onError(
+ AdServicesStatusUtils.asException(
+ failureParcel)));
+ }
+ });
+ } catch (NullPointerException e) {
+ sLogger.e(e, "Unable to find the AdSelection service");
+ outcomeReceiver.onError(
+ new IllegalStateException("Unable to find the AdSelection service", e));
+ } catch (RemoteException e) {
+ sLogger.e(e, "Remote exception encountered while updating ad counter histogram");
+ outcomeReceiver.onError(new IllegalStateException("Failure of AdSelection service", e));
+ }
+ }
+
+ /**
+ * Removes all previously set histogram overrides used in ad selection which were set by the
+ * caller application.
+ *
+ * <p>This method is intended to be used for end-to-end testing. This API is enabled only for
+ * apps in debug mode with developer options enabled.
+ *
+ * <p>The given {@code outcomeReceiver} either returns an empty {@link Object} if successful or
+ * an {@link Exception} which indicates the error.
+ *
+ * @throws IllegalStateException if this API is not enabled for the caller
+ * @hide
+ */
+ // TODO(b/265204820): Unhide for frequency cap dev override API review
+ @RequiresPermission(
+ anyOf = {
+ ACCESS_ADSERVICES_CUSTOM_AUDIENCE,
+ ACCESS_ADSERVICES_PROTECTED_SIGNALS,
+ ACCESS_ADSERVICES_AD_SELECTION
+ })
+ public void resetAllAdCounterHistogramOverrides(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OutcomeReceiver<Object, Exception> outcomeReceiver) {
+ Objects.requireNonNull(executor, "Executor must not be null");
+ Objects.requireNonNull(outcomeReceiver, "Outcome receiver must not be null");
+
+ try {
+ final AdSelectionService service =
+ Objects.requireNonNull(mAdSelectionManager.getServiceProvider().getService());
+ service.resetAllAdCounterHistogramOverrides(
+ new AdSelectionOverrideCallback.Stub() {
+ @Override
+ public void onSuccess() {
+ executor.execute(() -> outcomeReceiver.onResult(new Object()));
+ }
+
+ @Override
+ public void onFailure(FledgeErrorResponse failureParcel) {
+ executor.execute(
+ () ->
+ outcomeReceiver.onError(
+ AdServicesStatusUtils.asException(
+ failureParcel)));
+ }
+ });
+ } catch (NullPointerException e) {
+ sLogger.e(e, "Unable to find the AdSelection service");
+ outcomeReceiver.onError(
+ new IllegalStateException("Unable to find the AdSelection service", e));
+ } catch (RemoteException e) {
+ sLogger.e(e, "Remote exception encountered while updating ad counter histogram");
+ outcomeReceiver.onError(new IllegalStateException("Failure of AdSelection service", e));
+ }
+ }
+}
diff --git a/android-35/android/adservices/adselection/UpdateAdCounterHistogramInput.java b/android-35/android/adservices/adselection/UpdateAdCounterHistogramInput.java
new file mode 100644
index 0000000..8581bf0
--- /dev/null
+++ b/android-35/android/adservices/adselection/UpdateAdCounterHistogramInput.java
@@ -0,0 +1,278 @@
+/*
+ * 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.adselection;
+
+import static android.adservices.adselection.AdSelectionOutcome.UNSET_AD_SELECTION_ID;
+import static android.adservices.adselection.AdSelectionOutcome.UNSET_AD_SELECTION_ID_MESSAGE;
+import static android.adservices.adselection.UpdateAdCounterHistogramRequest.DISALLOW_AD_EVENT_TYPE_WIN_MESSAGE;
+import static android.adservices.adselection.UpdateAdCounterHistogramRequest.INVALID_AD_EVENT_TYPE_MESSAGE;
+import static android.adservices.adselection.UpdateAdCounterHistogramRequest.UNSET_CALLER_ADTECH_MESSAGE;
+import static android.adservices.common.FrequencyCapFilters.AD_EVENT_TYPE_WIN;
+
+import android.adservices.common.AdTechIdentifier;
+import android.adservices.common.FrequencyCapFilters;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.Objects;
+
+/**
+ * Input object wrapping the required arguments needed to update an ad counter histogram.
+ *
+ * <p>The ad counter histograms, which are historical logs of events which are associated with an ad
+ * counter key and an ad event type, are used to inform frequency cap filtering when using the
+ * Protected Audience APIs.
+ *
+ * @hide
+ */
+public final class UpdateAdCounterHistogramInput implements Parcelable {
+ private static final String UNSET_CALLER_PACKAGE_NAME_MESSAGE =
+ "Caller package name must not be null";
+
+ private final long mAdSelectionId;
+ @FrequencyCapFilters.AdEventType private final int mAdEventType;
+ @NonNull private final AdTechIdentifier mCallerAdTech;
+ @NonNull private final String mCallerPackageName;
+
+ @NonNull
+ public static final Creator<UpdateAdCounterHistogramInput> CREATOR =
+ new Creator<UpdateAdCounterHistogramInput>() {
+ @Override
+ public UpdateAdCounterHistogramInput createFromParcel(@NonNull Parcel in) {
+ Objects.requireNonNull(in);
+
+ return new UpdateAdCounterHistogramInput(in);
+ }
+
+ @Override
+ public UpdateAdCounterHistogramInput[] newArray(int size) {
+ return new UpdateAdCounterHistogramInput[size];
+ }
+ };
+
+ private UpdateAdCounterHistogramInput(@NonNull Builder builder) {
+ Objects.requireNonNull(builder);
+
+ mAdSelectionId = builder.mAdSelectionId;
+ mAdEventType = builder.mAdEventType;
+ mCallerAdTech = builder.mCallerAdTech;
+ mCallerPackageName = builder.mCallerPackageName;
+ }
+
+ private UpdateAdCounterHistogramInput(@NonNull Parcel in) {
+ Objects.requireNonNull(in);
+
+ mAdSelectionId = in.readLong();
+ mAdEventType = in.readInt();
+ mCallerAdTech = AdTechIdentifier.CREATOR.createFromParcel(in);
+ mCallerPackageName = in.readString();
+ }
+
+ /**
+ * Gets the ad selection ID with which the rendered ad's events are associated.
+ *
+ * <p>The ad must have been selected from Protected Audience ad selection in the last 24 hours,
+ * and the ad selection call must have been initiated from the same app as the current calling
+ * app. Event histograms for all ad counter keys associated with the ad specified by the ad
+ * selection ID will be updated for the ad event type from {@link #getAdEventType()}, to be used
+ * in Protected Audience frequency cap filtering.
+ */
+ public long getAdSelectionId() {
+ return mAdSelectionId;
+ }
+
+ /**
+ * Gets the {@link android.adservices.common.FrequencyCapFilters.AdEventType} which, along with
+ * an ad's counter keys, identifies which histogram should be updated.
+ *
+ * <p>See {@link android.adservices.common.FrequencyCapFilters.AdEventType} for more
+ * information.
+ */
+ @FrequencyCapFilters.AdEventType
+ public int getAdEventType() {
+ return mAdEventType;
+ }
+
+ /**
+ * Gets the caller adtech entity's {@link AdTechIdentifier}.
+ *
+ * <p>The adtech using this {@link UpdateAdCounterHistogramInput} object must have enrolled with
+ * the Privacy Sandbox and be allowed to act on behalf of the calling app. The specified adtech
+ * is not required to be the same adtech as either the buyer which owns the rendered ad or the
+ * seller which initiated the ad selection associated with the ID returned by {@link
+ * #getAdSelectionId()}.
+ */
+ @NonNull
+ public AdTechIdentifier getCallerAdTech() {
+ return mCallerAdTech;
+ }
+
+ /**
+ * Gets the caller app's package name.
+ *
+ * <p>The package name must match the caller package name for the Protected Audience ad
+ * selection represented by the ID returned by {@link #getAdSelectionId()}.
+ */
+ @NonNull
+ public String getCallerPackageName() {
+ return mCallerPackageName;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeLong(mAdSelectionId);
+ dest.writeInt(mAdEventType);
+ mCallerAdTech.writeToParcel(dest, flags);
+ dest.writeString(mCallerPackageName);
+ }
+
+ /** @hide */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Checks whether the {@link UpdateAdCounterHistogramInput} objects contain the same
+ * information.
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof UpdateAdCounterHistogramInput)) return false;
+ UpdateAdCounterHistogramInput that = (UpdateAdCounterHistogramInput) o;
+ return mAdSelectionId == that.mAdSelectionId
+ && mAdEventType == that.mAdEventType
+ && mCallerAdTech.equals(that.mCallerAdTech)
+ && mCallerPackageName.equals(that.mCallerPackageName);
+ }
+
+ /** Returns the hash of the {@link UpdateAdCounterHistogramInput} object's data. */
+ @Override
+ public int hashCode() {
+ return Objects.hash(mAdSelectionId, mAdEventType, mCallerAdTech, mCallerPackageName);
+ }
+
+ @Override
+ public String toString() {
+ return "UpdateAdCounterHistogramInput{"
+ + "mAdSelectionId="
+ + mAdSelectionId
+ + ", mAdEventType="
+ + mAdEventType
+ + ", mCallerAdTech="
+ + mCallerAdTech
+ + ", mCallerPackageName='"
+ + mCallerPackageName
+ + '\''
+ + '}';
+ }
+
+ /** Builder for {@link UpdateAdCounterHistogramInput} objects. */
+ public static final class Builder {
+ private long mAdSelectionId;
+ @FrequencyCapFilters.AdEventType private int mAdEventType;
+ @NonNull private AdTechIdentifier mCallerAdTech;
+ @NonNull private String mCallerPackageName;
+
+ public Builder(
+ long adSelectionId,
+ int adEventType,
+ @NonNull AdTechIdentifier callerAdTech,
+ @NonNull String callerPackageName) {
+ Preconditions.checkArgument(
+ adSelectionId != UNSET_AD_SELECTION_ID, UNSET_AD_SELECTION_ID_MESSAGE);
+ Preconditions.checkArgument(
+ adEventType != AD_EVENT_TYPE_WIN, DISALLOW_AD_EVENT_TYPE_WIN_MESSAGE);
+ Preconditions.checkArgument(
+ adEventType >= FrequencyCapFilters.AD_EVENT_TYPE_MIN
+ && adEventType <= FrequencyCapFilters.AD_EVENT_TYPE_MAX,
+ INVALID_AD_EVENT_TYPE_MESSAGE);
+ Objects.requireNonNull(callerAdTech, UNSET_CALLER_ADTECH_MESSAGE);
+ Objects.requireNonNull(callerPackageName, UNSET_CALLER_PACKAGE_NAME_MESSAGE);
+
+ mAdSelectionId = adSelectionId;
+ mAdEventType = adEventType;
+ mCallerAdTech = callerAdTech;
+ mCallerPackageName = callerPackageName;
+ }
+
+ /**
+ * Sets the ad selection ID with which the rendered ad's events are associated.
+ *
+ * <p>See {@link #getAdSelectionId()} for more information.
+ */
+ @NonNull
+ public Builder setAdSelectionId(long adSelectionId) {
+ Preconditions.checkArgument(
+ adSelectionId != UNSET_AD_SELECTION_ID, UNSET_AD_SELECTION_ID_MESSAGE);
+ mAdSelectionId = adSelectionId;
+ return this;
+ }
+
+ /**
+ * Sets the {@link android.adservices.common.FrequencyCapFilters.AdEventType} which, along
+ * with an ad's counter keys, identifies which histogram should be updated.
+ *
+ * <p>See {@link #getAdEventType()} for more information.
+ */
+ @NonNull
+ public Builder setAdEventType(@FrequencyCapFilters.AdEventType int adEventType) {
+ Preconditions.checkArgument(
+ adEventType != AD_EVENT_TYPE_WIN, DISALLOW_AD_EVENT_TYPE_WIN_MESSAGE);
+ Preconditions.checkArgument(
+ adEventType >= FrequencyCapFilters.AD_EVENT_TYPE_MIN
+ && adEventType <= FrequencyCapFilters.AD_EVENT_TYPE_MAX,
+ INVALID_AD_EVENT_TYPE_MESSAGE);
+ mAdEventType = adEventType;
+ return this;
+ }
+
+ /**
+ * Sets the caller adtech entity's {@link AdTechIdentifier}.
+ *
+ * <p>See {@link #getCallerAdTech()} for more information.
+ */
+ @NonNull
+ public Builder setCallerAdTech(@NonNull AdTechIdentifier callerAdTech) {
+ Objects.requireNonNull(callerAdTech, UNSET_CALLER_ADTECH_MESSAGE);
+ mCallerAdTech = callerAdTech;
+ return this;
+ }
+
+ /**
+ * Sets the caller app's package name.
+ *
+ * <p>See {@link #getCallerPackageName()} for more information.
+ */
+ @NonNull
+ public Builder setCallerPackageName(@NonNull String callerPackageName) {
+ Objects.requireNonNull(callerPackageName, UNSET_CALLER_PACKAGE_NAME_MESSAGE);
+ mCallerPackageName = callerPackageName;
+ return this;
+ }
+
+ /** Builds the {@link UpdateAdCounterHistogramInput} object. */
+ @NonNull
+ public UpdateAdCounterHistogramInput build() {
+ return new UpdateAdCounterHistogramInput(this);
+ }
+ }
+}
diff --git a/android-35/android/adservices/adselection/UpdateAdCounterHistogramRequest.java b/android-35/android/adservices/adselection/UpdateAdCounterHistogramRequest.java
new file mode 100644
index 0000000..b661648
--- /dev/null
+++ b/android-35/android/adservices/adselection/UpdateAdCounterHistogramRequest.java
@@ -0,0 +1,214 @@
+/*
+ * 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.adselection;
+
+import static android.adservices.adselection.AdSelectionOutcome.UNSET_AD_SELECTION_ID;
+import static android.adservices.adselection.AdSelectionOutcome.UNSET_AD_SELECTION_ID_MESSAGE;
+import static android.adservices.common.FrequencyCapFilters.AD_EVENT_TYPE_WIN;
+
+import android.adservices.common.AdTechIdentifier;
+import android.adservices.common.FrequencyCapFilters;
+import android.annotation.NonNull;
+import android.os.OutcomeReceiver;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/**
+ * Request object wrapping the required arguments needed to update an ad counter histogram.
+ *
+ * <p>The ad counter histograms, which are historical logs of events which are associated with an ad
+ * counter key and an ad event type, are used to inform frequency cap filtering when using the
+ * Protected Audience APIs.
+ */
+public class UpdateAdCounterHistogramRequest {
+ /** @hide */
+ public static final String UNSET_AD_EVENT_TYPE_MESSAGE = "Ad event type must be set";
+
+ /** @hide */
+ public static final String DISALLOW_AD_EVENT_TYPE_WIN_MESSAGE =
+ "Win event types cannot be manually updated";
+
+ /** @hide */
+ public static final String INVALID_AD_EVENT_TYPE_MESSAGE =
+ "Ad event type must be one of AD_EVENT_TYPE_IMPRESSION, AD_EVENT_TYPE_VIEW, or"
+ + " AD_EVENT_TYPE_CLICK";
+
+ /** @hide */
+ public static final String UNSET_CALLER_ADTECH_MESSAGE = "Caller ad tech must not be null";
+
+ private final long mAdSelectionId;
+ @FrequencyCapFilters.AdEventType private final int mAdEventType;
+ @NonNull private final AdTechIdentifier mCallerAdTech;
+
+ private UpdateAdCounterHistogramRequest(@NonNull Builder builder) {
+ Objects.requireNonNull(builder);
+
+ mAdSelectionId = builder.mAdSelectionId;
+ mAdEventType = builder.mAdEventType;
+ mCallerAdTech = builder.mCallerAdTech;
+ }
+
+ /**
+ * Gets the ad selection ID with which the rendered ad's events are associated.
+ *
+ * <p>For more information about the ad selection ID, see {@link AdSelectionOutcome}.
+ *
+ * <p>The ad must have been selected from Protected Audience ad selection in the last 24 hours,
+ * and the ad selection call must have been initiated from the same app as the current calling
+ * app. Event histograms for all ad counter keys associated with the ad specified by the ad
+ * selection ID will be updated for the ad event type from {@link #getAdEventType()}, to be used
+ * in Protected Audience frequency cap filtering.
+ */
+ public long getAdSelectionId() {
+ return mAdSelectionId;
+ }
+
+ /**
+ * Gets the ad event type which, along with an ad's counter keys, identifies which histogram
+ * should be updated.
+ */
+ @FrequencyCapFilters.AdEventType
+ public int getAdEventType() {
+ return mAdEventType;
+ }
+
+ /**
+ * Gets the caller adtech entity's {@link AdTechIdentifier}.
+ *
+ * <p>The adtech using this {@link UpdateAdCounterHistogramRequest} object must have enrolled
+ * with the Privacy Sandbox and be allowed to act on behalf of the calling app. The specified
+ * adtech is not required to be the same adtech as either the buyer which owns the rendered ad
+ * or the seller which initiated the ad selection associated with the ID returned by {@link
+ * #getAdSelectionId()}.
+ *
+ * <p>For more information about API requirements and exceptions, see {@link
+ * AdSelectionManager#updateAdCounterHistogram(UpdateAdCounterHistogramRequest, Executor,
+ * OutcomeReceiver)}.
+ */
+ @NonNull
+ public AdTechIdentifier getCallerAdTech() {
+ return mCallerAdTech;
+ }
+
+ /**
+ * Checks whether the {@link UpdateAdCounterHistogramRequest} objects contain the same
+ * information.
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof UpdateAdCounterHistogramRequest)) return false;
+ UpdateAdCounterHistogramRequest that = (UpdateAdCounterHistogramRequest) o;
+ return mAdSelectionId == that.mAdSelectionId
+ && mAdEventType == that.mAdEventType
+ && mCallerAdTech.equals(that.mCallerAdTech);
+ }
+
+ /** Returns the hash of the {@link UpdateAdCounterHistogramRequest} object's data. */
+ @Override
+ public int hashCode() {
+ return Objects.hash(mAdSelectionId, mAdEventType, mCallerAdTech);
+ }
+
+ @Override
+ public String toString() {
+ return "UpdateAdCounterHistogramRequest{"
+ + "mAdSelectionId="
+ + mAdSelectionId
+ + ", mAdEventType="
+ + mAdEventType
+ + ", mCallerAdTech="
+ + mCallerAdTech
+ + '}';
+ }
+
+ /** Builder for {@link UpdateAdCounterHistogramRequest} objects. */
+ public static final class Builder {
+ private long mAdSelectionId;
+ @FrequencyCapFilters.AdEventType private int mAdEventType;
+ @NonNull private AdTechIdentifier mCallerAdTech;
+
+ public Builder(
+ long adSelectionId, int adEventType, @NonNull AdTechIdentifier callerAdTech) {
+ Preconditions.checkArgument(
+ adSelectionId != UNSET_AD_SELECTION_ID, UNSET_AD_SELECTION_ID_MESSAGE);
+ Preconditions.checkArgument(
+ adEventType != AD_EVENT_TYPE_WIN, DISALLOW_AD_EVENT_TYPE_WIN_MESSAGE);
+ Preconditions.checkArgument(
+ adEventType >= FrequencyCapFilters.AD_EVENT_TYPE_MIN
+ && adEventType <= FrequencyCapFilters.AD_EVENT_TYPE_MAX,
+ INVALID_AD_EVENT_TYPE_MESSAGE);
+ Objects.requireNonNull(callerAdTech, UNSET_CALLER_ADTECH_MESSAGE);
+
+ mAdSelectionId = adSelectionId;
+ mAdEventType = adEventType;
+ mCallerAdTech = callerAdTech;
+ }
+
+ /**
+ * Sets the ad selection ID with which the rendered ad's events are associated.
+ *
+ * <p>See {@link #getAdSelectionId()} for more information.
+ */
+ @NonNull
+ public Builder setAdSelectionId(long adSelectionId) {
+ Preconditions.checkArgument(
+ adSelectionId != UNSET_AD_SELECTION_ID, UNSET_AD_SELECTION_ID_MESSAGE);
+ mAdSelectionId = adSelectionId;
+ return this;
+ }
+
+ /**
+ * Sets the ad event type which, along with an ad's counter keys, identifies which histogram
+ * should be updated.
+ *
+ * <p>See {@link #getAdEventType()} for more information.
+ */
+ @NonNull
+ public Builder setAdEventType(@FrequencyCapFilters.AdEventType int adEventType) {
+ Preconditions.checkArgument(
+ adEventType != AD_EVENT_TYPE_WIN, DISALLOW_AD_EVENT_TYPE_WIN_MESSAGE);
+ Preconditions.checkArgument(
+ adEventType >= FrequencyCapFilters.AD_EVENT_TYPE_MIN
+ && adEventType <= FrequencyCapFilters.AD_EVENT_TYPE_MAX,
+ INVALID_AD_EVENT_TYPE_MESSAGE);
+ mAdEventType = adEventType;
+ return this;
+ }
+
+ /**
+ * Sets the caller adtech entity's {@link AdTechIdentifier}.
+ *
+ * <p>See {@link #getCallerAdTech()} for more information.
+ */
+ @NonNull
+ public Builder setCallerAdTech(@NonNull AdTechIdentifier callerAdTech) {
+ Objects.requireNonNull(callerAdTech, UNSET_CALLER_ADTECH_MESSAGE);
+ mCallerAdTech = callerAdTech;
+ return this;
+ }
+
+ /** Builds the {@link UpdateAdCounterHistogramRequest} object. */
+ @NonNull
+ public UpdateAdCounterHistogramRequest build() {
+ return new UpdateAdCounterHistogramRequest(this);
+ }
+ }
+}
diff --git a/android-35/android/adservices/appsetid/AppSetId.java b/android-35/android/adservices/appsetid/AppSetId.java
new file mode 100644
index 0000000..247ad6a
--- /dev/null
+++ b/android-35/android/adservices/appsetid/AppSetId.java
@@ -0,0 +1,93 @@
+/*
+ * 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.appsetid;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * A unique, per-device, per developer-account user-resettable ID for non-monetizing advertising
+ * usecases.
+ *
+ * <p>Represents the appSetID and scope of this appSetId from the {@link
+ * AppSetIdManager#getAppSetId(Executor, OutcomeReceiver)} API. The scope of the ID can be per app
+ * or per developer account associated with the user. AppSetId is used for analytics, spam
+ * detection, frequency capping and fraud prevention use cases, on a given device, that one may need
+ * to correlate usage or actions across a set of apps owned by an organization.
+ */
+public class AppSetId {
+ @NonNull private final String mAppSetId;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ SCOPE_APP,
+ SCOPE_DEVELOPER,
+ })
+ public @interface AppSetIdScope {}
+ /** The appSetId is scoped to an app. All apps on a device will have a different appSetId. */
+ public static final int SCOPE_APP = 1;
+
+ /**
+ * The appSetId is scoped to a developer account on an app store. All apps from the same
+ * developer on a device will have the same developer scoped appSetId.
+ */
+ public static final int SCOPE_DEVELOPER = 2;
+
+ private final @AppSetIdScope int mAppSetIdScope;
+
+ /**
+ * Creates an instance of {@link AppSetId}
+ *
+ * @param appSetId generated by the provider service.
+ * @param appSetIdScope scope of the appSetId.
+ */
+ public AppSetId(@NonNull String appSetId, @AppSetIdScope int appSetIdScope) {
+ mAppSetId = appSetId;
+ mAppSetIdScope = appSetIdScope;
+ }
+
+ /** Retrieves the appSetId. The api always returns a non-empty appSetId. */
+ public @NonNull String getId() {
+ return mAppSetId;
+ }
+
+ /** Retrieves the scope of the appSetId. */
+ public @AppSetIdScope int getScope() {
+ return mAppSetIdScope;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof AppSetId)) {
+ return false;
+ }
+ AppSetId that = (AppSetId) o;
+ return mAppSetId.equals(that.mAppSetId) && (mAppSetIdScope == that.mAppSetIdScope);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mAppSetId, mAppSetIdScope);
+ }
+}
diff --git a/android-35/android/adservices/appsetid/AppSetIdManager.java b/android-35/android/adservices/appsetid/AppSetIdManager.java
new file mode 100644
index 0000000..1516356
--- /dev/null
+++ b/android-35/android/adservices/appsetid/AppSetIdManager.java
@@ -0,0 +1,211 @@
+/*
+ * 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.appsetid;
+
+import android.adservices.common.AdServicesStatusUtils;
+import android.adservices.common.CallerMetadata;
+import android.adservices.common.SandboxedSdkContextUtils;
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.app.sdksandbox.SandboxedSdkContext;
+import android.content.Context;
+import android.os.Build;
+import android.os.OutcomeReceiver;
+import android.os.RemoteException;
+import android.os.SystemClock;
+
+import androidx.annotation.RequiresApi;
+
+import com.android.adservices.AdServicesCommon;
+import com.android.adservices.LogUtil;
+import com.android.adservices.ServiceBinder;
+import com.android.adservices.shared.common.ServiceUnavailableException;
+
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/**
+ * AppSetIdManager provides APIs for app and ad-SDKs to access appSetId for non-monetizing purpose.
+ */
+@RequiresApi(Build.VERSION_CODES.S)
+public class AppSetIdManager {
+ /**
+ * Service used for registering AppSetIdManager in the system service registry.
+ *
+ * @hide
+ */
+ public static final String APPSETID_SERVICE = "appsetid_service";
+
+ /* When an app calls the AppSetId API directly, it sets the SDK name to empty string. */
+ static final String EMPTY_SDK = "";
+
+ private Context mContext;
+ private ServiceBinder<IAppSetIdService> mServiceBinder;
+
+ /**
+ * Factory method for creating an instance of AppSetIdManager.
+ *
+ * @param context The {@link Context} to use
+ * @return A {@link AppSetIdManager} instance
+ */
+ @NonNull
+ public static AppSetIdManager get(@NonNull Context context) {
+ // On T+, context.getSystemService() does more than just call constructor.
+ return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
+ ? context.getSystemService(AppSetIdManager.class)
+ : new AppSetIdManager(context);
+ }
+
+ /**
+ * Create AppSetIdManager
+ *
+ * @hide
+ */
+ public AppSetIdManager(Context context) {
+ // In case the AppSetIdManager is initiated from inside a sdk_sandbox process the fields
+ // will be immediately rewritten by the initialize method below.
+ initialize(context);
+ }
+
+ /**
+ * Initializes {@link AppSetIdManager} with the given {@code context}.
+ *
+ * <p>This method is called by the {@link SandboxedSdkContext} to propagate the correct context.
+ * For more information check the javadoc on the {@link
+ * android.app.sdksandbox.SdkSandboxSystemServiceRegistry}.
+ *
+ * @hide
+ * @see android.app.sdksandbox.SdkSandboxSystemServiceRegistry
+ */
+ public AppSetIdManager initialize(Context context) {
+ mContext = context;
+ mServiceBinder =
+ ServiceBinder.getServiceBinder(
+ context,
+ AdServicesCommon.ACTION_APPSETID_SERVICE,
+ IAppSetIdService.Stub::asInterface);
+ return this;
+ }
+
+ @NonNull
+ private IAppSetIdService getService(
+ @CallbackExecutor Executor executor, OutcomeReceiver<AppSetId, Exception> callback) {
+ IAppSetIdService service = null;
+ try {
+ service = mServiceBinder.getService();
+
+ // Throw ServiceUnavailableException and set it to the callback.
+ if (service == null) {
+ throw new ServiceUnavailableException();
+ }
+ } catch (RuntimeException e) {
+ LogUtil.e(e, "Failed binding to AppSetId service");
+ executor.execute(() -> callback.onError(e));
+ }
+
+ return service;
+ }
+
+ @NonNull
+ private Context getContext() {
+ return mContext;
+ }
+
+ /**
+ * Retrieve the AppSetId.
+ *
+ * @param executor The executor to run callback.
+ * @param callback The callback that's called after appsetid are available or an error occurs.
+ */
+ @NonNull
+ public void getAppSetId(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OutcomeReceiver<AppSetId, Exception> callback) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+ CallerMetadata callerMetadata =
+ new CallerMetadata.Builder()
+ .setBinderElapsedTimestamp(SystemClock.elapsedRealtime())
+ .build();
+
+ String appPackageName = "";
+ String sdkPackageName = "";
+ // First check if context is SandboxedSdkContext or not
+ Context getAppSetIdRequestContext = getContext();
+ SandboxedSdkContext requestContext =
+ SandboxedSdkContextUtils.getAsSandboxedSdkContext(getAppSetIdRequestContext);
+ if (requestContext != null) {
+ sdkPackageName = requestContext.getSdkPackageName();
+ appPackageName = requestContext.getClientPackageName();
+ } else { // This is the case without the Sandbox.
+ appPackageName = getAppSetIdRequestContext.getPackageName();
+ }
+ try {
+ IAppSetIdService service = getService(executor, callback);
+ if (service == null) {
+ LogUtil.d("Unable to find AppSetId service");
+ return;
+ }
+
+ service.getAppSetId(
+ new GetAppSetIdParam.Builder()
+ .setAppPackageName(appPackageName)
+ .setSdkPackageName(sdkPackageName)
+ .build(),
+ callerMetadata,
+ new IGetAppSetIdCallback.Stub() {
+ @Override
+ public void onResult(GetAppSetIdResult resultParcel) {
+ executor.execute(
+ () -> {
+ if (resultParcel.isSuccess()) {
+ callback.onResult(
+ new AppSetId(
+ resultParcel.getAppSetId(),
+ resultParcel.getAppSetIdScope()));
+ } else {
+ callback.onError(
+ AdServicesStatusUtils.asException(
+ resultParcel));
+ }
+ });
+ }
+
+ @Override
+ public void onError(int resultCode) {
+ executor.execute(
+ () ->
+ callback.onError(
+ AdServicesStatusUtils.asException(resultCode)));
+ }
+ });
+ } catch (RemoteException e) {
+ LogUtil.e("RemoteException", e);
+ callback.onError(e);
+ }
+ }
+
+ /**
+ * If the service is in an APK (as opposed to the system service), unbind it from the service to
+ * allow the APK process to die.
+ *
+ * @hide
+ */
+ // TODO: change to @VisibleForTesting
+ public void unbindFromService() {
+ mServiceBinder.unbindFromService();
+ }
+}
diff --git a/android-35/android/adservices/appsetid/AppSetIdProviderService.java b/android-35/android/adservices/appsetid/AppSetIdProviderService.java
new file mode 100644
index 0000000..fbe93fa
--- /dev/null
+++ b/android-35/android/adservices/appsetid/AppSetIdProviderService.java
@@ -0,0 +1,84 @@
+/*
+ * 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.appsetid;
+
+import static android.adservices.common.AdServicesStatusUtils.STATUS_SUCCESS;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SdkConstant;
+import android.annotation.SystemApi;
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import java.io.IOException;
+
+/**
+ * Abstract Base class for provider service to implement generation of AppSetId with appropriate
+ * appSetId scope value.
+ *
+ * <p>The implementor of this service needs to override the onGetAppSetIdProvider method and provide
+ * an app-scoped or developer-account scoped unique appSetId.
+ *
+ * @hide
+ */
+@SystemApi
+public abstract class AppSetIdProviderService extends Service {
+
+ /** The intent that the service must respond to. Add it to the intent filter of the service. */
+ @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+ public static final String SERVICE_INTERFACE =
+ "android.adservices.appsetid.AppSetIdProviderService";
+
+ /** Abstract method which will be overridden by provider to provide the appsetid. */
+ @NonNull
+ public abstract AppSetId onGetAppSetId(int clientUid, @NonNull String clientPackageName)
+ throws IOException;
+
+ private final android.adservices.appsetid.IAppSetIdProviderService mInterface =
+ new android.adservices.appsetid.IAppSetIdProviderService.Stub() {
+ @Override
+ public void getAppSetId(
+ int appUID,
+ @NonNull String packageName,
+ @NonNull IGetAppSetIdProviderCallback resultCallback)
+ throws RemoteException {
+ try {
+ AppSetId appsetId = onGetAppSetId(appUID, packageName);
+ GetAppSetIdResult appsetIdInternal =
+ new GetAppSetIdResult.Builder()
+ .setStatusCode(STATUS_SUCCESS)
+ .setErrorMessage("")
+ .setAppSetId(appsetId.getId())
+ .setAppSetIdScope(appsetId.getScope())
+ .build();
+
+ resultCallback.onResult(appsetIdInternal);
+ } catch (Throwable e) {
+ resultCallback.onError(e.getMessage());
+ }
+ }
+ };
+
+ @Nullable
+ @Override
+ public final IBinder onBind(@Nullable Intent intent) {
+ return mInterface.asBinder();
+ }
+}
diff --git a/android-35/android/adservices/appsetid/GetAppSetIdParam.java b/android-35/android/adservices/appsetid/GetAppSetIdParam.java
new file mode 100644
index 0000000..af54f52
--- /dev/null
+++ b/android-35/android/adservices/appsetid/GetAppSetIdParam.java
@@ -0,0 +1,119 @@
+/*
+ * 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.appsetid;
+
+import static android.adservices.appsetid.AppSetIdManager.EMPTY_SDK;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Represent input params to the getAppSetId API.
+ *
+ * @hide
+ */
+public final class GetAppSetIdParam implements Parcelable {
+ private final String mSdkPackageName;
+ private final String mAppPackageName;
+
+ private GetAppSetIdParam(@Nullable String sdkPackageName, @NonNull String appPackageName) {
+ mSdkPackageName = sdkPackageName;
+ mAppPackageName = appPackageName;
+ }
+
+ private GetAppSetIdParam(@NonNull Parcel in) {
+ mSdkPackageName = in.readString();
+ mAppPackageName = in.readString();
+ }
+
+ public static final @NonNull Creator<GetAppSetIdParam> CREATOR =
+ new Parcelable.Creator<GetAppSetIdParam>() {
+ @Override
+ public GetAppSetIdParam createFromParcel(Parcel in) {
+ return new GetAppSetIdParam(in);
+ }
+
+ @Override
+ public GetAppSetIdParam[] newArray(int size) {
+ return new GetAppSetIdParam[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeString(mSdkPackageName);
+ out.writeString(mAppPackageName);
+ }
+
+ /** Get the Sdk Package Name. This is the package name in the Manifest. */
+ @NonNull
+ public String getSdkPackageName() {
+ return mSdkPackageName;
+ }
+
+ /** Get the App PackageName. */
+ @NonNull
+ public String getAppPackageName() {
+ return mAppPackageName;
+ }
+
+ /** Builder for {@link GetAppSetIdParam} objects. */
+ public static final class Builder {
+ private String mSdkPackageName;
+ private String mAppPackageName;
+
+ public Builder() {}
+
+ /**
+ * Set the Sdk Package Name. When the app calls the AppSetId API directly without using an
+ * SDK, don't set this field.
+ */
+ public @NonNull Builder setSdkPackageName(@NonNull String sdkPackageName) {
+ mSdkPackageName = sdkPackageName;
+ return this;
+ }
+
+ /** Set the App PackageName. */
+ public @NonNull Builder setAppPackageName(@NonNull String appPackageName) {
+ mAppPackageName = appPackageName;
+ return this;
+ }
+
+ /** Builds a {@link GetAppSetIdParam} instance. */
+ public @NonNull GetAppSetIdParam build() {
+ if (mSdkPackageName == null) {
+ // When Sdk package name is not set, we assume the App calls the AppSetId API
+ // directly.
+ // We set the Sdk package name to empty to mark this.
+ mSdkPackageName = EMPTY_SDK;
+ }
+
+ if (mAppPackageName == null || mAppPackageName.isEmpty()) {
+ throw new IllegalArgumentException("App PackageName must not be empty or null");
+ }
+
+ return new GetAppSetIdParam(mSdkPackageName, mAppPackageName);
+ }
+ }
+}
diff --git a/android-35/android/adservices/appsetid/GetAppSetIdResult.java b/android-35/android/adservices/appsetid/GetAppSetIdResult.java
new file mode 100644
index 0000000..9a750a6
--- /dev/null
+++ b/android-35/android/adservices/appsetid/GetAppSetIdResult.java
@@ -0,0 +1,210 @@
+/*
+ * 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.appsetid;
+
+import android.adservices.common.AdServicesResponse;
+import android.adservices.common.AdServicesStatusUtils;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * Represent the result from the getAppSetId API.
+ *
+ * @hide
+ */
+public final class GetAppSetIdResult extends AdServicesResponse {
+ @NonNull private final String mAppSetId;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ SCOPE_APP,
+ SCOPE_DEVELOPER,
+ })
+ public @interface AppSetIdScope {}
+ /** The appSetId is scoped to an app. All apps on a device will have a different appSetId. */
+ public static final int SCOPE_APP = 1;
+
+ /**
+ * The appSetId is scoped to a developer account on an app store. All apps from the same
+ * developer on a device will have the same developer scoped appSetId.
+ */
+ public static final int SCOPE_DEVELOPER = 2;
+
+ private final @AppSetIdScope int mAppSetIdScope;
+
+ private GetAppSetIdResult(
+ @AdServicesStatusUtils.StatusCode int resultCode,
+ @Nullable String errorMessage,
+ @NonNull String appSetId,
+ @AppSetIdScope int appSetIdScope) {
+ super(resultCode, errorMessage);
+ mAppSetId = appSetId;
+ mAppSetIdScope = appSetIdScope;
+ }
+
+ private GetAppSetIdResult(@NonNull Parcel in) {
+ super(in);
+ Objects.requireNonNull(in);
+
+ mAppSetId = in.readString();
+ mAppSetIdScope = in.readInt();
+ }
+
+ public static final @NonNull Creator<GetAppSetIdResult> CREATOR =
+ new Parcelable.Creator<GetAppSetIdResult>() {
+ @Override
+ public GetAppSetIdResult createFromParcel(@NonNull Parcel in) {
+ Objects.requireNonNull(in);
+ return new GetAppSetIdResult(in);
+ }
+
+ @Override
+ public GetAppSetIdResult[] newArray(int size) {
+ return new GetAppSetIdResult[size];
+ }
+ };
+
+ /** @hide */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** @hide */
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeInt(mStatusCode);
+ out.writeString(mErrorMessage);
+ out.writeString(mAppSetId);
+ out.writeInt(mAppSetIdScope);
+ }
+
+ /**
+ * Returns the error message associated with this result.
+ *
+ * <p>If {@link #isSuccess} is {@code true}, the error message is always {@code null}. The error
+ * message may be {@code null} even if {@link #isSuccess} is {@code false}.
+ */
+ @Nullable
+ public String getErrorMessage() {
+ return mErrorMessage;
+ }
+
+ /** Returns the AppSetId associated with this result. */
+ @NonNull
+ public String getAppSetId() {
+ return mAppSetId;
+ }
+
+ /** Returns the AppSetId scope associated with this result. */
+ public @AppSetIdScope int getAppSetIdScope() {
+ return mAppSetIdScope;
+ }
+
+ @Override
+ public String toString() {
+ return "GetAppSetIdResult{"
+ + "mResultCode="
+ + mStatusCode
+ + ", mErrorMessage='"
+ + mErrorMessage
+ + '\''
+ + ", mAppSetId="
+ + mAppSetId
+ + ", mAppSetIdScope="
+ + mAppSetIdScope
+ + '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+
+ if (!(o instanceof GetAppSetIdResult)) {
+ return false;
+ }
+
+ GetAppSetIdResult that = (GetAppSetIdResult) o;
+
+ return mStatusCode == that.mStatusCode
+ && Objects.equals(mErrorMessage, that.mErrorMessage)
+ && Objects.equals(mAppSetId, that.mAppSetId)
+ && (mAppSetIdScope == that.mAppSetIdScope);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mStatusCode, mErrorMessage, mAppSetId, mAppSetIdScope);
+ }
+
+ /**
+ * Builder for {@link GetAppSetIdResult} objects.
+ *
+ * @hide
+ */
+ public static final class Builder {
+ private @AdServicesStatusUtils.StatusCode int mStatusCode;
+ @Nullable private String mErrorMessage;
+ @NonNull private String mAppSetId;
+ private @AppSetIdScope int mAppSetIdScope;
+
+ public Builder() {}
+
+ /** Set the Result Code. */
+ public @NonNull Builder setStatusCode(@AdServicesStatusUtils.StatusCode int statusCode) {
+ mStatusCode = statusCode;
+ return this;
+ }
+
+ /** Set the Error Message. */
+ public @NonNull Builder setErrorMessage(@Nullable String errorMessage) {
+ mErrorMessage = errorMessage;
+ return this;
+ }
+
+ /** Set the appSetId. */
+ public @NonNull Builder setAppSetId(@NonNull String appSetId) {
+ mAppSetId = appSetId;
+ return this;
+ }
+
+ /** Set the appSetId scope field. */
+ public @NonNull Builder setAppSetIdScope(@AppSetIdScope int scope) {
+ mAppSetIdScope = scope;
+ return this;
+ }
+
+ /** Builds a {@link GetAppSetIdResult} instance. */
+ public @NonNull GetAppSetIdResult build() {
+ if (mAppSetId == null) {
+ throw new IllegalArgumentException("appSetId is null");
+ }
+
+ return new GetAppSetIdResult(mStatusCode, mErrorMessage, mAppSetId, mAppSetIdScope);
+ }
+ }
+}
diff --git a/android-35/android/adservices/cobalt/AdServicesCobaltUploadService.java b/android-35/android/adservices/cobalt/AdServicesCobaltUploadService.java
new file mode 100644
index 0000000..cfb8787
--- /dev/null
+++ b/android-35/android/adservices/cobalt/AdServicesCobaltUploadService.java
@@ -0,0 +1,68 @@
+/*
+ * 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.cobalt;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SdkConstant;
+import android.annotation.SystemApi;
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+
+/**
+ * Abstract Base class for provider service to implement uploading of data to Cobalt's backend.
+ *
+ * <p>The implementor of this service needs to override the onUploadEncryptedCobaltEnvelope method.
+ *
+ * <p>Cobalt is a telemetry system with built-in support for differential privacy. See
+ * https://fuchsia.googlesource.com/cobalt for a comprehensive overview of the project and the
+ * Fuchsia client implementation.
+ *
+ * @hide
+ */
+@SystemApi
+public abstract class AdServicesCobaltUploadService extends Service {
+ /** The intent that the service must respond to. Add it to the intent filter of the service. */
+ @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+ public static final String SERVICE_INTERFACE =
+ "android.adservices.cobalt.AdServicesCobaltUploadService";
+
+ /** Abstract method which will be overridden by the sender to upload the data */
+ public abstract void onUploadEncryptedCobaltEnvelope(
+ @NonNull EncryptedCobaltEnvelopeParams params);
+
+ private final IAdServicesCobaltUploadService mInterface =
+ new IAdServicesCobaltUploadService.Stub() {
+ /**
+ * Send an encrypted envelope to Cobalt's backend.
+ *
+ * <p>Errors in this method execution, both because of problems within the binder
+ * call and in the service execution, will cause a RuntimeException to be thrown.
+ */
+ @Override
+ public void uploadEncryptedCobaltEnvelope(EncryptedCobaltEnvelopeParams params) {
+ onUploadEncryptedCobaltEnvelope(params);
+ }
+ };
+
+ @Nullable
+ @Override
+ public final IBinder onBind(@Nullable Intent intent) {
+ return mInterface.asBinder();
+ }
+}
diff --git a/android-35/android/adservices/cobalt/EncryptedCobaltEnvelopeParams.java b/android-35/android/adservices/cobalt/EncryptedCobaltEnvelopeParams.java
new file mode 100644
index 0000000..0e985e1
--- /dev/null
+++ b/android-35/android/adservices/cobalt/EncryptedCobaltEnvelopeParams.java
@@ -0,0 +1,132 @@
+/*
+ * 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.cobalt;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * Parameters describing the encrypted Cobalt Envelope being sent.
+ *
+ * @hide
+ */
+@SystemApi
+public final class EncryptedCobaltEnvelopeParams implements Parcelable {
+ /**
+ * Whether data is from a development or production device.
+ *
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ ENVIRONMENT_DEV,
+ ENVIRONMENT_PROD,
+ })
+ public @interface Environment {}
+
+ /** Production environment. */
+ public static final int ENVIRONMENT_PROD = 0;
+
+ /** Development environment. */
+ public static final int ENVIRONMENT_DEV = 1;
+
+ private final @Environment int mEnvironment;
+ private final int mKeyIndex;
+ private final byte[] mCipherText;
+
+ /**
+ * The parameters describing how a Cobalt {@link Envelope} was encrypted and the ciphertext.
+ *
+ * @param environment the environment the {@link Envelope} was encrypted for
+ * @param keyIndex the identifier of the key used for encryption, see
+ * //packages/modules/AdServices/adservices/libraries/cobalt/java/com/android/cobalt/crypto/PublicKeys.java
+ * for key list
+ * @param cipherText an encrypted Cobalt {@link Envelope}, created using a supported encryption
+ * algorithm and an associated key.
+ */
+ public EncryptedCobaltEnvelopeParams(
+ @Environment int environment, @NonNull int keyIndex, @NonNull byte[] cipherText) {
+ mEnvironment = environment;
+ mKeyIndex = keyIndex;
+ mCipherText = Objects.requireNonNull(cipherText);
+ }
+
+ private EncryptedCobaltEnvelopeParams(@NonNull Parcel in) {
+ mEnvironment = in.readInt();
+ mKeyIndex = in.readInt();
+ mCipherText = in.createByteArray();
+ }
+
+ public static final @NonNull Creator<EncryptedCobaltEnvelopeParams> CREATOR =
+ new Parcelable.Creator<EncryptedCobaltEnvelopeParams>() {
+ @Override
+ public EncryptedCobaltEnvelopeParams createFromParcel(Parcel in) {
+ return new EncryptedCobaltEnvelopeParams(in);
+ }
+
+ @Override
+ public EncryptedCobaltEnvelopeParams[] newArray(int size) {
+ return new EncryptedCobaltEnvelopeParams[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeInt(mEnvironment);
+ out.writeInt(mKeyIndex);
+ out.writeByteArray(mCipherText);
+ }
+
+ /** Get the environment. */
+ @NonNull
+ public @Environment int getEnvironment() {
+ return mEnvironment;
+ }
+
+ /**
+ * Get index of the (public, private) key pair used to encrypted the Envelope.
+ *
+ * <p>There are multiple pairs on the server and it's cheaper to send the index than the actual
+ * public key used.
+ */
+ @NonNull
+ public int getKeyIndex() {
+ return mKeyIndex;
+ }
+
+ /**
+ * Get the encrypted Envelope.
+ *
+ * <p>Envelopes are will be on the order of 1KiB in size.
+ */
+ @NonNull
+ public byte[] getCipherText() {
+ return mCipherText;
+ }
+}
diff --git a/android-35/android/adservices/common/AdData.java b/android-35/android/adservices/common/AdData.java
new file mode 100644
index 0000000..f23d03a
--- /dev/null
+++ b/android-35/android/adservices/common/AdData.java
@@ -0,0 +1,307 @@
+/*
+ * 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.common;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.adservices.AdServicesParcelableUtil;
+import com.android.internal.util.Preconditions;
+
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
+
+/** Represents data specific to an ad that is necessary for ad selection and rendering. */
+public final class AdData implements Parcelable {
+ /** @hide */
+ public static final String NUM_AD_COUNTER_KEYS_EXCEEDED_FORMAT =
+ "AdData should have no more than %d ad counter keys";
+ /** @hide */
+ public static final int MAX_NUM_AD_COUNTER_KEYS = 10;
+
+ @NonNull private final Uri mRenderUri;
+ @NonNull private final String mMetadata;
+ @NonNull private final Set<Integer> mAdCounterKeys;
+ @Nullable private final AdFilters mAdFilters;
+ @Nullable private final String mAdRenderId;
+
+ @NonNull
+ public static final Creator<AdData> CREATOR =
+ new Creator<AdData>() {
+ @Override
+ public AdData createFromParcel(@NonNull Parcel in) {
+ Objects.requireNonNull(in);
+
+ return new AdData(in);
+ }
+
+ @Override
+ public AdData[] newArray(int size) {
+ return new AdData[size];
+ }
+ };
+
+ private AdData(@NonNull AdData.Builder builder) {
+ Objects.requireNonNull(builder);
+
+ mRenderUri = builder.mRenderUri;
+ mMetadata = builder.mMetadata;
+ mAdCounterKeys = builder.mAdCounterKeys;
+ mAdFilters = builder.mAdFilters;
+ mAdRenderId = builder.mAdRenderId;
+ }
+
+ private AdData(@NonNull Parcel in) {
+ Objects.requireNonNull(in);
+
+ mRenderUri = Uri.CREATOR.createFromParcel(in);
+ mMetadata = in.readString();
+ mAdCounterKeys =
+ AdServicesParcelableUtil.readNullableFromParcel(
+ in, AdServicesParcelableUtil::readIntegerSetFromParcel);
+ mAdFilters =
+ AdServicesParcelableUtil.readNullableFromParcel(
+ in, AdFilters.CREATOR::createFromParcel);
+ mAdRenderId = in.readString();
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ Objects.requireNonNull(dest);
+
+ mRenderUri.writeToParcel(dest, flags);
+ dest.writeString(mMetadata);
+ AdServicesParcelableUtil.writeNullableToParcel(
+ dest, mAdCounterKeys, AdServicesParcelableUtil::writeIntegerSetToParcel);
+ AdServicesParcelableUtil.writeNullableToParcel(
+ dest,
+ mAdFilters,
+ (targetParcel, sourceFilters) -> sourceFilters.writeToParcel(targetParcel, flags));
+ dest.writeString(mAdRenderId);
+ }
+
+ /** @hide */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Gets the URI that points to the ad's rendering assets. The URI must use HTTPS. */
+ @NonNull
+ public Uri getRenderUri() {
+ return mRenderUri;
+ }
+
+ /**
+ * Gets the buyer ad metadata used during the ad selection process.
+ *
+ * <p>The metadata should be a valid JSON object serialized as a string. Metadata represents
+ * ad-specific bidding information that will be used during ad selection as part of bid
+ * generation and used in buyer JavaScript logic, which is executed in an isolated execution
+ * environment.
+ *
+ * <p>If the metadata is not a valid JSON object that can be consumed by the buyer's JS, the ad
+ * will not be eligible for ad selection.
+ */
+ @NonNull
+ public String getMetadata() {
+ return mMetadata;
+ }
+
+ /**
+ * Gets the set of keys used in counting events.
+ *
+ * <p>No more than 10 ad counter keys may be associated with an ad.
+ *
+ * <p>The keys and counts per key are used in frequency cap filtering during ad selection to
+ * disqualify associated ads from being submitted to bidding.
+ *
+ * <p>Note that these keys can be overwritten along with the ads and other bidding data for a
+ * custom audience during the custom audience's daily update.
+ */
+ @NonNull
+ public Set<Integer> getAdCounterKeys() {
+ return mAdCounterKeys;
+ }
+
+ /**
+ * Gets all {@link AdFilters} associated with the ad.
+ *
+ * <p>The filters, if met or exceeded, exclude the associated ad from participating in ad
+ * selection. They are optional and if {@code null} specify that no filters apply to this ad.
+ */
+ @Nullable
+ public AdFilters getAdFilters() {
+ return mAdFilters;
+ }
+
+ /**
+ * Gets the ad render id for server auctions.
+ *
+ * <p>Ad render id is collected for each {@link AdData} when server auction request is received.
+ *
+ * <p>Any {@link AdData} without ad render id will be ineligible for server-side auction.
+ *
+ * <p>The overall size of the CA is limited. The size of this field is considered using
+ * {@link String#getBytes()} in {@code UTF-8} encoding.
+ */
+ @Nullable
+ public String getAdRenderId() {
+ return mAdRenderId;
+ }
+
+ /** Checks whether two {@link AdData} objects contain the same information. */
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof AdData)) return false;
+ AdData adData = (AdData) o;
+ return mRenderUri.equals(adData.mRenderUri)
+ && mMetadata.equals(adData.mMetadata)
+ && mAdCounterKeys.equals(adData.mAdCounterKeys)
+ && Objects.equals(mAdFilters, adData.mAdFilters)
+ && Objects.equals(mAdRenderId, adData.mAdRenderId);
+ }
+
+ /** Returns the hash of the {@link AdData} object's data. */
+ @Override
+ public int hashCode() {
+ return Objects.hash(mRenderUri, mMetadata, mAdCounterKeys, mAdFilters);
+ }
+
+ @Override
+ public String toString() {
+ return "AdData{"
+ + "mRenderUri="
+ + mRenderUri
+ + ", mMetadata='"
+ + mMetadata
+ + '\''
+ + ", mAdCounterKeys="
+ + mAdCounterKeys
+ + ", mAdFilters="
+ + mAdFilters
+ + ", mAdRenderId='"
+ + mAdRenderId
+ + '\''
+ + '}';
+ }
+
+ /** Builder for {@link AdData} objects. */
+ public static final class Builder {
+ @Nullable private Uri mRenderUri;
+ @Nullable private String mMetadata;
+ @NonNull private Set<Integer> mAdCounterKeys = new HashSet<>();
+ @Nullable private AdFilters mAdFilters;
+ @Nullable private String mAdRenderId;
+
+ // TODO(b/232883403): We may need to add @NonNUll members as args.
+ public Builder() {}
+
+ /**
+ * Sets the URI that points to the ad's rendering assets. The URI must use HTTPS.
+ *
+ * <p>See {@link #getRenderUri()} for detail.
+ */
+ @NonNull
+ public AdData.Builder setRenderUri(@NonNull Uri renderUri) {
+ Objects.requireNonNull(renderUri);
+ mRenderUri = renderUri;
+ return this;
+ }
+
+ /**
+ * Sets the buyer ad metadata used during the ad selection process.
+ *
+ * <p>The metadata should be a valid JSON object serialized as a string. Metadata represents
+ * ad-specific bidding information that will be used during ad selection as part of bid
+ * generation and used in buyer JavaScript logic, which is executed in an isolated execution
+ * environment.
+ *
+ * <p>If the metadata is not a valid JSON object that can be consumed by the buyer's JS, the
+ * ad will not be eligible for ad selection.
+ *
+ * <p>See {@link #getMetadata()} for detail.
+ */
+ @NonNull
+ public AdData.Builder setMetadata(@NonNull String metadata) {
+ Objects.requireNonNull(metadata);
+ mMetadata = metadata;
+ return this;
+ }
+
+ /**
+ * Sets the set of keys used in counting events.
+ *
+ * <p>No more than 10 ad counter keys may be associated with an ad.
+ *
+ * <p>See {@link #getAdCounterKeys()} for more information.
+ */
+ @NonNull
+ public AdData.Builder setAdCounterKeys(@NonNull Set<Integer> adCounterKeys) {
+ Objects.requireNonNull(adCounterKeys);
+ Preconditions.checkArgument(
+ !adCounterKeys.contains(null), "Ad counter keys must not contain null value");
+ Preconditions.checkArgument(
+ adCounterKeys.size() <= MAX_NUM_AD_COUNTER_KEYS,
+ NUM_AD_COUNTER_KEYS_EXCEEDED_FORMAT,
+ MAX_NUM_AD_COUNTER_KEYS);
+ mAdCounterKeys = adCounterKeys;
+ return this;
+ }
+
+ /**
+ * Sets all {@link AdFilters} associated with the ad.
+ *
+ * <p>See {@link #getAdFilters()} for more information.
+ */
+ @NonNull
+ public AdData.Builder setAdFilters(@Nullable AdFilters adFilters) {
+ mAdFilters = adFilters;
+ return this;
+ }
+
+ /**
+ * Sets the ad render id for server auction
+ *
+ * <p>See {@link AdData#getAdRenderId()} for more information.
+ */
+ @NonNull
+ public AdData.Builder setAdRenderId(@Nullable String adRenderId) {
+ mAdRenderId = adRenderId;
+ return this;
+ }
+
+ /**
+ * Builds the {@link AdData} object.
+ *
+ * @throws NullPointerException if any required parameters are {@code null} when built
+ */
+ @NonNull
+ public AdData build() {
+ Objects.requireNonNull(mRenderUri, "The render URI has not been provided");
+ // TODO(b/231997523): Add JSON field validation.
+ Objects.requireNonNull(mMetadata, "The metadata has not been provided");
+
+ return new AdData(this);
+ }
+ }
+}
diff --git a/android-35/android/adservices/common/AdFilters.java b/android-35/android/adservices/common/AdFilters.java
new file mode 100644
index 0000000..0d1b1f8
--- /dev/null
+++ b/android-35/android/adservices/common/AdFilters.java
@@ -0,0 +1,243 @@
+/*
+ * 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.common;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.adservices.AdServicesParcelableUtil;
+import com.android.adservices.flags.Flags;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.Objects;
+
+/**
+ * A container class for filters which are associated with an ad.
+ *
+ * <p>If any of the filters in an {@link AdFilters} instance are not satisfied, the associated ad
+ * will not be eligible for ad selection. Filters are optional ad parameters and are not required as
+ * part of {@link AdData}.
+ */
+public final class AdFilters implements Parcelable {
+ /** @hide */
+ public static final String FREQUENCY_CAP_FIELD_NAME = "frequency_cap";
+ /** @hide */
+ public static final String APP_INSTALL_FIELD_NAME = "app_install";
+ /** @hide */
+ @Nullable private final FrequencyCapFilters mFrequencyCapFilters;
+
+ @Nullable private final AppInstallFilters mAppInstallFilters;
+
+ @NonNull
+ public static final Creator<AdFilters> CREATOR =
+ new Creator<AdFilters>() {
+ @Override
+ public AdFilters createFromParcel(@NonNull Parcel in) {
+ Objects.requireNonNull(in);
+ return new AdFilters(in);
+ }
+
+ @Override
+ public AdFilters[] newArray(int size) {
+ return new AdFilters[size];
+ }
+ };
+
+ private AdFilters(@NonNull Builder builder) {
+ Objects.requireNonNull(builder);
+
+ mFrequencyCapFilters = builder.mFrequencyCapFilters;
+ mAppInstallFilters = builder.mAppInstallFilters;
+ }
+
+ private AdFilters(@NonNull Parcel in) {
+ Objects.requireNonNull(in);
+
+ mFrequencyCapFilters =
+ AdServicesParcelableUtil.readNullableFromParcel(
+ in, FrequencyCapFilters.CREATOR::createFromParcel);
+ mAppInstallFilters =
+ AdServicesParcelableUtil.readNullableFromParcel(
+ in, AppInstallFilters.CREATOR::createFromParcel);
+ }
+
+ /**
+ * Gets the {@link FrequencyCapFilters} instance that represents all frequency cap filters for
+ * the ad.
+ *
+ * <p>If {@code null}, there are no frequency cap filters which apply to the ad.
+ */
+ @Nullable
+ public FrequencyCapFilters getFrequencyCapFilters() {
+ return mFrequencyCapFilters;
+ }
+
+ /**
+ * Gets the {@link AppInstallFilters} instance that represents all app install filters for the
+ * ad.
+ *
+ * <p>If {@code null}, there are no app install filters which apply to the ad.
+ */
+ @FlaggedApi(Flags.FLAG_FLEDGE_AD_SELECTION_FILTERING_ENABLED)
+ @Nullable
+ public AppInstallFilters getAppInstallFilters() {
+ return mAppInstallFilters;
+ }
+
+ /**
+ * @return The estimated size of this object, in bytes.
+ * @hide
+ */
+ public int getSizeInBytes() {
+ int size = 0;
+ if (mFrequencyCapFilters != null) {
+ size += mFrequencyCapFilters.getSizeInBytes();
+ }
+ if (mAppInstallFilters != null) {
+ size += mAppInstallFilters.getSizeInBytes();
+ }
+ return size;
+ }
+
+ /**
+ * A JSON serializer.
+ *
+ * @return A JSON serialization of this object.
+ * @hide
+ */
+ public JSONObject toJson() throws JSONException {
+ JSONObject toReturn = new JSONObject();
+ if (mFrequencyCapFilters != null) {
+ toReturn.put(FREQUENCY_CAP_FIELD_NAME, mFrequencyCapFilters.toJson());
+ }
+ if (mAppInstallFilters != null) {
+ toReturn.put(APP_INSTALL_FIELD_NAME, mAppInstallFilters.toJson());
+ }
+ return toReturn;
+ }
+
+ /**
+ * A JSON de-serializer.
+ *
+ * @param json A JSON representation of an {@link AdFilters} object as would be generated by
+ * {@link #toJson()}.
+ * @return An {@link AdFilters} object generated from the given JSON.
+ * @hide
+ */
+ public static AdFilters fromJson(JSONObject json) throws JSONException {
+ Builder builder = new Builder();
+ if (json.has(FREQUENCY_CAP_FIELD_NAME)) {
+ builder.setFrequencyCapFilters(
+ FrequencyCapFilters.fromJson(json.getJSONObject(FREQUENCY_CAP_FIELD_NAME)));
+ }
+ if (json.has(APP_INSTALL_FIELD_NAME)) {
+ builder.setAppInstallFilters(
+ AppInstallFilters.fromJson(json.getJSONObject(APP_INSTALL_FIELD_NAME)));
+ }
+ return builder.build();
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ Objects.requireNonNull(dest);
+
+ AdServicesParcelableUtil.writeNullableToParcel(
+ dest,
+ mFrequencyCapFilters,
+ (targetParcel, sourceFilters) -> sourceFilters.writeToParcel(targetParcel, flags));
+ AdServicesParcelableUtil.writeNullableToParcel(
+ dest,
+ mAppInstallFilters,
+ (targetParcel, sourceFilters) -> sourceFilters.writeToParcel(targetParcel, flags));
+ }
+
+ /** @hide */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Checks whether the {@link AdFilters} objects represent the same set of filters. */
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof AdFilters)) return false;
+ AdFilters adFilters = (AdFilters) o;
+ return Objects.equals(mFrequencyCapFilters, adFilters.mFrequencyCapFilters)
+ && Objects.equals(mAppInstallFilters, adFilters.mAppInstallFilters);
+ }
+
+ /** Returns the hash of the {@link AdFilters} object's data. */
+ @Override
+ public int hashCode() {
+ return Objects.hash(mFrequencyCapFilters, mAppInstallFilters);
+ }
+
+ @Override
+ public String toString() {
+ return "AdFilters{"
+ + "mFrequencyCapFilters="
+ + mFrequencyCapFilters
+ + ", mAppInstallFilters="
+ + mAppInstallFilters
+ + '}';
+ }
+
+ /** Builder for creating {@link AdFilters} objects. */
+ public static final class Builder {
+ @Nullable private FrequencyCapFilters mFrequencyCapFilters;
+ @Nullable private AppInstallFilters mAppInstallFilters;
+
+ public Builder() {}
+
+ /**
+ * Sets the {@link FrequencyCapFilters} which will apply to the ad.
+ *
+ * <p>If set to {@code null} or not set, no frequency cap filters will be associated with
+ * the ad.
+ */
+ @NonNull
+ public Builder setFrequencyCapFilters(@Nullable FrequencyCapFilters frequencyCapFilters) {
+ mFrequencyCapFilters = frequencyCapFilters;
+ return this;
+ }
+
+ /**
+ * Sets the {@link AppInstallFilters} which will apply to the ad.
+ *
+ * <p>If set to {@code null} or not set, no app install filters will be associated with the
+ * ad.
+ */
+ @FlaggedApi(Flags.FLAG_FLEDGE_AD_SELECTION_FILTERING_ENABLED)
+ @NonNull
+ public Builder setAppInstallFilters(@Nullable AppInstallFilters appInstallFilters) {
+ mAppInstallFilters = appInstallFilters;
+ return this;
+ }
+
+ /** Builds and returns an {@link AdFilters} instance. */
+ @NonNull
+ public AdFilters build() {
+ return new AdFilters(this);
+ }
+ }
+}
diff --git a/android-35/android/adservices/common/AdSelectionSignals.java b/android-35/android/adservices/common/AdSelectionSignals.java
new file mode 100644
index 0000000..41fdb1d
--- /dev/null
+++ b/android-35/android/adservices/common/AdSelectionSignals.java
@@ -0,0 +1,153 @@
+/*
+ * 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.common;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Objects;
+
+/**
+ * This class holds JSON that will be passed into a JavaScript function during ad selection. Its
+ * contents are not used by <a
+ * href="https://developer.android.com/design-for-safety/privacy-sandbox/fledge">FLEDGE</a> platform
+ * code, but are merely validated and then passed to the appropriate JavaScript ad selection
+ * function.
+ */
+public final class AdSelectionSignals implements Parcelable {
+
+ public static final AdSelectionSignals EMPTY = fromString("{}");
+
+ @NonNull private final String mSignals;
+
+ private AdSelectionSignals(@NonNull Parcel in) {
+ this(in.readString());
+ }
+
+ private AdSelectionSignals(@NonNull String adSelectionSignals) {
+ this(adSelectionSignals, true);
+ }
+
+ private AdSelectionSignals(@NonNull String adSelectionSignals, boolean validate) {
+ Objects.requireNonNull(adSelectionSignals);
+ if (validate) {
+ validate(adSelectionSignals);
+ }
+ mSignals = adSelectionSignals;
+ }
+
+ @NonNull
+ public static final Creator<AdSelectionSignals> CREATOR =
+ new Creator<AdSelectionSignals>() {
+ @Override
+ public AdSelectionSignals createFromParcel(Parcel in) {
+ Objects.requireNonNull(in);
+ return new AdSelectionSignals(in);
+ }
+
+ @Override
+ public AdSelectionSignals[] newArray(int size) {
+ return new AdSelectionSignals[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString(mSignals);
+ }
+
+ /**
+ * Compares this AdSelectionSignals to the specified object. The result is true if and only if
+ * the argument is not null and is a AdSelectionSignals object with the same string form
+ * (obtained by calling {@link #toString()}). Note that this method will not perform any JSON
+ * normalization so two AdSelectionSignals objects with the same JSON could be not equal if the
+ * String representations of the objects was not equal.
+ *
+ * @param o The object to compare this AdSelectionSignals against
+ * @return true if the given object represents an AdSelectionSignals equivalent to this
+ * AdSelectionSignals, false otherwise
+ */
+ @Override
+ public boolean equals(Object o) {
+ return o instanceof AdSelectionSignals
+ && mSignals.equals(((AdSelectionSignals) o).toString());
+ }
+
+ /**
+ * Returns a hash code corresponding to the string representation of this class obtained by
+ * calling {@link #toString()}. Note that this method will not perform any JSON normalization so
+ * two AdSelectionSignals objects with the same JSON could have different hash codes if the
+ * underlying string representation was different.
+ *
+ * @return a hash code value for this object.
+ */
+ @Override
+ public int hashCode() {
+ return mSignals.hashCode();
+ }
+
+ /** @return The String form of the JSON wrapped by this class. */
+ @Override
+ @NonNull
+ public String toString() {
+ return mSignals;
+ }
+
+ /**
+ * Creates an AdSelectionSignals from a given JSON in String form.
+ *
+ * @param source Any valid JSON string to create the AdSelectionSignals with.
+ * @return An AdSelectionSignals object wrapping the given String.
+ */
+ @NonNull
+ public static AdSelectionSignals fromString(@NonNull String source) {
+ return new AdSelectionSignals(source, true);
+ }
+
+ /**
+ * Creates an AdSelectionSignals from a given JSON in String form.
+ *
+ * @param source Any valid JSON string to create the AdSelectionSignals with.
+ * @param validate Construction-time validation is run on the string if and only if this is
+ * true.
+ * @return An AdSelectionSignals object wrapping the given String.
+ * @hide
+ */
+ @NonNull
+ public static AdSelectionSignals fromString(@NonNull String source, boolean validate) {
+ return new AdSelectionSignals(source, validate);
+ }
+
+ /**
+ * @return the signal's String form data size in bytes.
+ * @hide
+ */
+ public int getSizeInBytes() {
+ return this.mSignals.getBytes(StandardCharsets.UTF_8).length;
+ }
+
+ private void validate(String inputString) {
+ // TODO(b/238849930) Bring the existing validation function in here
+ }
+}
diff --git a/android-35/android/adservices/common/AdServicesCommonManager.java b/android-35/android/adservices/common/AdServicesCommonManager.java
new file mode 100644
index 0000000..10a7975
--- /dev/null
+++ b/android-35/android/adservices/common/AdServicesCommonManager.java
@@ -0,0 +1,434 @@
+/*
+ * 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.common;
+
+import static android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_STATE;
+import static android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_STATE_COMPAT;
+import static android.adservices.common.AdServicesPermissions.MODIFY_ADSERVICES_STATE;
+import static android.adservices.common.AdServicesPermissions.MODIFY_ADSERVICES_STATE_COMPAT;
+import static android.adservices.common.AdServicesPermissions.UPDATE_PRIVILEGED_AD_ID;
+import static android.adservices.common.AdServicesPermissions.UPDATE_PRIVILEGED_AD_ID_COMPAT;
+
+import android.adservices.adid.AdId;
+import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.app.sdksandbox.SandboxedSdkContext;
+import android.content.Context;
+import android.os.Build;
+import android.os.OutcomeReceiver;
+import android.os.RemoteException;
+import android.os.SystemClock;
+
+import androidx.annotation.RequiresApi;
+
+import com.android.adservices.AdServicesCommon;
+import com.android.adservices.LogUtil;
+import com.android.adservices.ServiceBinder;
+import com.android.adservices.flags.Flags;
+
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/**
+ * AdServicesCommonManager contains APIs common across the various AdServices. It provides two
+ * SystemApis:
+ *
+ * <ul>
+ * <li>isAdServicesEnabled - allows to get AdServices state.
+ * <li>setAdServicesEntryPointEnabled - allows to control AdServices state.
+ * </ul>
+ *
+ * <p>The instance of the {@link AdServicesCommonManager} can be obtained using {@link
+ * Context#getSystemService} and {@link AdServicesCommonManager} class.
+ *
+ * @hide
+ */
+@SystemApi
+public class AdServicesCommonManager {
+ /** @hide */
+ public static final String AD_SERVICES_COMMON_SERVICE = "ad_services_common_service";
+
+ private final Context mContext;
+ private final ServiceBinder<IAdServicesCommonService> mAdServicesCommonServiceBinder;
+
+ /**
+ * Create AdServicesCommonManager.
+ *
+ * @hide
+ */
+ public AdServicesCommonManager(@NonNull Context context) {
+ mContext = context;
+ mAdServicesCommonServiceBinder =
+ ServiceBinder.getServiceBinder(
+ context,
+ AdServicesCommon.ACTION_AD_SERVICES_COMMON_SERVICE,
+ IAdServicesCommonService.Stub::asInterface);
+ }
+
+ /**
+ * Factory method for creating an instance of AdServicesCommonManager.
+ *
+ * @param context The {@link Context} to use
+ * @return A {@link AdServicesCommonManager} instance
+ */
+ @NonNull
+ public static AdServicesCommonManager get(@NonNull Context context) {
+ // On T+, context.getSystemService() does more than just call constructor.
+ return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
+ ? context.getSystemService(AdServicesCommonManager.class)
+ : new AdServicesCommonManager(context);
+ }
+
+ @NonNull
+ private IAdServicesCommonService getService() {
+ IAdServicesCommonService service = mAdServicesCommonServiceBinder.getService();
+ if (service == null) {
+ throw new IllegalStateException("Unable to find the service");
+ }
+ return service;
+ }
+
+ /**
+ * Get the AdService's enablement state which represents whether AdServices feature is enabled
+ * or not. This API is for Android S+, which has the OutcomeReceiver class available.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {ACCESS_ADSERVICES_STATE, ACCESS_ADSERVICES_STATE_COMPAT})
+ @RequiresApi(Build.VERSION_CODES.S)
+ public void isAdServicesEnabled(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OutcomeReceiver<Boolean, Exception> callback) {
+ isAdServicesEnabled(
+ executor, OutcomeReceiverConverter.toAdServicesOutcomeReceiver(callback));
+ }
+
+ /**
+ * Get the AdService's enablement state which represents whether AdServices feature is enabled
+ * or not. This API is for Android R, and uses the AdServicesOutcomeReceiver class because
+ * OutcomeReceiver is not available.
+ *
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_ADSERVICES_ENABLEMENT_CHECK_ENABLED)
+ @RequiresPermission(anyOf = {ACCESS_ADSERVICES_STATE, ACCESS_ADSERVICES_STATE_COMPAT})
+ public void isAdServicesEnabled(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull AdServicesOutcomeReceiver<Boolean, Exception> callback) {
+ final IAdServicesCommonService service = getService();
+ try {
+ service.isAdServicesEnabled(
+ new IAdServicesCommonCallback.Stub() {
+ @Override
+ public void onResult(IsAdServicesEnabledResult result) {
+ executor.execute(
+ () -> {
+ callback.onResult(result.getAdServicesEnabled());
+ });
+ }
+
+ @Override
+ public void onFailure(int statusCode) {
+ executor.execute(
+ () ->
+ callback.onError(
+ AdServicesStatusUtils.asException(statusCode)));
+ }
+ });
+ } catch (RemoteException e) {
+ LogUtil.e(e, "RemoteException");
+ executor.execute(
+ () -> callback.onError(new IllegalStateException("Internal Error!", e)));
+ }
+ }
+
+ /**
+ * Sets the AdService's enablement state based on the provided parameters.
+ *
+ * <p>As a result of the AdServices state, {@code adServicesEntryPointEnabled}, {@code
+ * adIdEnabled}, appropriate notification may be displayed to the user. It's displayed only once
+ * when all the following conditions are met:
+ *
+ * <ul>
+ * <li>AdServices state - enabled.
+ * <li>adServicesEntryPointEnabled - true.
+ * </ul>
+ *
+ * @param adServicesEntryPointEnabled indicate entry point enabled or not
+ * @param adIdEnabled indicate user opt-out of adid or not
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {MODIFY_ADSERVICES_STATE, MODIFY_ADSERVICES_STATE_COMPAT})
+ public void setAdServicesEnabled(boolean adServicesEntryPointEnabled, boolean adIdEnabled) {
+ final IAdServicesCommonService service = getService();
+ try {
+ service.setAdServicesEnabled(adServicesEntryPointEnabled, adIdEnabled);
+ } catch (RemoteException e) {
+ LogUtil.e(e, "RemoteException");
+ }
+ }
+
+ /**
+ * Enable AdServices based on the AdServicesStates input parameter. This API is for Android S+,
+ * which has the OutcomeReceiver class available.
+ *
+ * <p>Based on the provided {@code AdServicesStates}, AdServices may be enabled. Specifically,
+ * users will be provided with an enrollment channel (such as notification) to become privacy
+ * sandbox users when:
+ *
+ * <ul>
+ * <li>isAdServicesUiEnabled - true.
+ * <li>isU18Account | isAdultAccount - true.
+ * </ul>
+ *
+ * @param {@code AdServicesStates} parcel containing relevant AdServices state variables.
+ * @return false if API is disabled, true if the API call completed successfully. Otherwise, it
+ * would return one of the following exceptions to the user:
+ * <ul>
+ * <li>IllegalStateException - the default exception thrown when service crashes
+ * unexpectedly.
+ * <li>SecurityException - when the caller is not authorized to call this API.
+ * <li>TimeoutException - when the services takes too long to respond.
+ * </ul>
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {MODIFY_ADSERVICES_STATE, MODIFY_ADSERVICES_STATE_COMPAT})
+ @RequiresApi(Build.VERSION_CODES.S)
+ public void enableAdServices(
+ @NonNull AdServicesStates adServicesStates,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OutcomeReceiver<Boolean, Exception> callback) {
+ enableAdServices(
+ adServicesStates,
+ executor,
+ OutcomeReceiverConverter.toAdServicesOutcomeReceiver(callback));
+ }
+
+ /**
+ * Enable AdServices based on the AdServicesStates input parameter. This API is for Android R,
+ * and uses the AdServicesOutcomeReceiver class because OutcomeReceiver is not available.
+ *
+ * <p>Based on the provided {@code AdServicesStates}, AdServices may be enabled. Specifically,
+ * users will be provided with an enrollment channel (such as notification) to become privacy
+ * sandbox users when:
+ *
+ * <ul>
+ * <li>isAdServicesUiEnabled - true.
+ * <li>isU18Account | isAdultAccount - true.
+ * </ul>
+ *
+ * @param adServicesStates parcel containing relevant AdServices state variables.
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_ENABLE_ADSERVICES_API_ENABLED)
+ @RequiresPermission(anyOf = {MODIFY_ADSERVICES_STATE, MODIFY_ADSERVICES_STATE_COMPAT})
+ public void enableAdServices(
+ @NonNull AdServicesStates adServicesStates,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull AdServicesOutcomeReceiver<Boolean, Exception> callback) {
+ Objects.requireNonNull(adServicesStates);
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+
+ final IAdServicesCommonService service = getService();
+ try {
+ service.enableAdServices(
+ adServicesStates,
+ new IEnableAdServicesCallback.Stub() {
+ @Override
+ public void onResult(EnableAdServicesResponse response) {
+ executor.execute(
+ () -> {
+ if (!response.isApiEnabled()) {
+ callback.onResult(false);
+ return;
+ }
+
+ if (response.isSuccess()) {
+ callback.onResult(true);
+ } else {
+ callback.onError(
+ AdServicesStatusUtils.asException(
+ response.getStatusCode()));
+ }
+ });
+ }
+
+ @Override
+ public void onFailure(int statusCode) {
+ executor.execute(
+ () ->
+ callback.onError(
+ AdServicesStatusUtils.asException(statusCode)));
+ }
+ });
+ } catch (RemoteException e) {
+ LogUtil.e(e, "RemoteException");
+ executor.execute(
+ () -> callback.onError(new IllegalStateException("Internal Error!", e)));
+ }
+ }
+
+ /**
+ * Updates {@link AdId} in Adservices when the device changes {@link AdId}. This API is used by
+ * AdIdProvider.
+ *
+ * @param updateAdIdRequest the request that contains {@link AdId} information to update.
+ * @param executor the executor for the callback.
+ * @param callback the callback in type {@link AdServicesOutcomeReceiver}, available on Android
+ * R and above.
+ * @throws IllegalStateException when service is not available or the feature is not enabled, or
+ * if there is any {@code Binder} invocation error.
+ * @throws SecurityException when the caller is not authorized to call this API.
+ * @hide
+ */
+ // TODO(b/295205476): Move exceptions into the callback.
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_AD_ID_CACHE_ENABLED)
+ @RequiresPermission(anyOf = {UPDATE_PRIVILEGED_AD_ID, UPDATE_PRIVILEGED_AD_ID_COMPAT})
+ public void updateAdId(
+ @NonNull UpdateAdIdRequest updateAdIdRequest,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull AdServicesOutcomeReceiver<Boolean, Exception> callback) {
+ Objects.requireNonNull(updateAdIdRequest);
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+
+ IAdServicesCommonService service = getService();
+ try {
+ service.updateAdIdCache(
+ updateAdIdRequest,
+ new IUpdateAdIdCallback.Stub() {
+ @Override
+ public void onResult(String message) {
+ executor.execute(() -> callback.onResult(true));
+ }
+
+ @Override
+ public void onFailure(int statusCode) {
+ executor.execute(
+ () ->
+ callback.onError(
+ AdServicesStatusUtils.asException(statusCode)));
+ }
+ });
+ } catch (RemoteException e) {
+ LogUtil.e(e, "RemoteException calling updateAdIdCache with %s", updateAdIdRequest);
+ executor.execute(
+ () -> callback.onError(new IllegalStateException("Internal Error!", e)));
+ }
+ }
+
+ /**
+ * Updates {@link AdId} in Adservices when the device changes {@link AdId}. This API is used by
+ * AdIdProvider.
+ *
+ * @param updateAdIdRequest the request that contains {@link AdId} information to update.
+ * @param executor the executor for the callback.
+ * @param callback the callback in type {@link OutcomeReceiver}, available on Android S and
+ * above.
+ * @throws IllegalStateException when service is not available or the feature is not enabled, or
+ * if there is any {@code Binder} invocation error.
+ * @throws SecurityException when the caller is not authorized to call this API.
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_AD_ID_CACHE_ENABLED)
+ @RequiresPermission(anyOf = {UPDATE_PRIVILEGED_AD_ID, UPDATE_PRIVILEGED_AD_ID_COMPAT})
+ @RequiresApi(Build.VERSION_CODES.S)
+ public void updateAdId(
+ @NonNull UpdateAdIdRequest updateAdIdRequest,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OutcomeReceiver<Boolean, Exception> callback) {
+ updateAdId(
+ updateAdIdRequest,
+ executor,
+ OutcomeReceiverConverter.toAdServicesOutcomeReceiver(callback));
+ }
+
+ /**
+ * Get the AdService's common states.
+ *
+ * @param executor the executor for the callback.
+ * @param callback the callback in type {@link AdServicesOutcomeReceiver}, available on Android
+ * R and above.
+ * @throws IllegalStateException if there is any {@code Binder} invocation error.
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_GET_ADSERVICES_COMMON_STATES_API_ENABLED)
+ @RequiresPermission(anyOf = {ACCESS_ADSERVICES_STATE, ACCESS_ADSERVICES_STATE_COMPAT})
+ public void getAdservicesCommonStates(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull
+ AdServicesOutcomeReceiver<AdServicesCommonStatesResponse, Exception> callback) {
+ final IAdServicesCommonService service = getService();
+ CallerMetadata callerMetadata =
+ new CallerMetadata.Builder()
+ .setBinderElapsedTimestamp(SystemClock.elapsedRealtime())
+ .build();
+ String appPackageName = "";
+ String sdkPackageName = "";
+ // First check if context is SandboxedSdkContext or not
+ SandboxedSdkContext sandboxedSdkContext =
+ SandboxedSdkContextUtils.getAsSandboxedSdkContext(mContext);
+ if (sandboxedSdkContext != null) {
+ // This is the case with the Sandbox.
+ sdkPackageName = sandboxedSdkContext.getSdkPackageName();
+ appPackageName = sandboxedSdkContext.getClientPackageName();
+ } else {
+ // This is the case without the Sandbox.
+ appPackageName = mContext.getPackageName();
+ }
+ try {
+ service.getAdServicesCommonStates(
+ new GetAdServicesCommonStatesParams.Builder(appPackageName, sdkPackageName)
+ .build(),
+ callerMetadata,
+ new IAdServicesCommonStatesCallback.Stub() {
+ @Override
+ public void onResult(AdServicesCommonStatesResponse result) {
+ executor.execute(
+ () -> {
+ callback.onResult(result);
+ });
+ }
+
+ @Override
+ public void onFailure(int statusCode) {
+ executor.execute(
+ () ->
+ callback.onError(
+ AdServicesStatusUtils.asException(statusCode)));
+ }
+ });
+ } catch (RemoteException e) {
+ LogUtil.e(e, "RemoteException");
+ executor.execute(
+ () -> callback.onError(new IllegalStateException("Internal Error!", e)));
+ }
+ }
+}
diff --git a/android-35/android/adservices/common/AdServicesCommonStates.java b/android-35/android/adservices/common/AdServicesCommonStates.java
new file mode 100644
index 0000000..32515da
--- /dev/null
+++ b/android-35/android/adservices/common/AdServicesCommonStates.java
@@ -0,0 +1,153 @@
+/*
+ * 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.common;
+
+import static android.adservices.common.ConsentStatus.ConsentStatusCode;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.adservices.flags.Flags;
+
+import java.util.Objects;
+
+/**
+ * Represent the common states from the getAdservicesCommonStates API.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_GET_ADSERVICES_COMMON_STATES_API_ENABLED)
+public final class AdServicesCommonStates implements Parcelable {
+ @ConsentStatusCode private final int mMeasurementState;
+ @ConsentStatusCode private final int mPaState;
+
+ /**
+ * Creates an object which represents the result from the getAdservicesCommonStates API.
+ *
+ * @param measurementState a {@link ConsentStatusCode} int indicating whether meansurement is
+ * allowed
+ * @param paState a {@link ConsentStatusCode} indicating whether fledge is allowed
+ */
+ private AdServicesCommonStates(
+ @ConsentStatusCode int measurementState, @ConsentStatusCode int paState) {
+ this.mMeasurementState = measurementState;
+ this.mPaState = paState;
+ }
+
+ private AdServicesCommonStates(@NonNull Parcel in) {
+ this.mMeasurementState = in.readInt();
+ this.mPaState = in.readInt();
+ }
+
+ @NonNull
+ public static final Creator<AdServicesCommonStates> CREATOR =
+ new Creator<AdServicesCommonStates>() {
+ @Override
+ public AdServicesCommonStates createFromParcel(Parcel in) {
+ Objects.requireNonNull(in);
+ return new AdServicesCommonStates(in);
+ }
+
+ @Override
+ public AdServicesCommonStates[] newArray(int size) {
+ return new AdServicesCommonStates[size];
+ }
+ };
+
+ /** describe contents for parcel */
+ public int describeContents() {
+ return 0;
+ }
+
+ /** write contents for parcel */
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeInt(mMeasurementState);
+ out.writeInt(mPaState);
+ }
+
+ /** Get the measurement allowed state. */
+ @ConsentStatusCode
+ public int getMeasurementState() {
+ return mMeasurementState;
+ }
+
+ /** Get the fledge allowed state. */
+ @ConsentStatusCode
+ public int getPaState() {
+ return mPaState;
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ if (this == object) return true;
+ if (!(object instanceof AdServicesCommonStates)) return false;
+ AdServicesCommonStates adservicesCommonStates = (AdServicesCommonStates) object;
+ return getMeasurementState() == adservicesCommonStates.getMeasurementState()
+ && getPaState() == adservicesCommonStates.getPaState();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(getMeasurementState(), getPaState());
+ }
+
+ @Override
+ public String toString() {
+ return "AdservicesCommonStates{"
+ + "mMeasurementState="
+ + mMeasurementState
+ + ", mPaState="
+ + mPaState
+ + '}';
+ }
+
+ /**
+ * Builder for {@link AdServicesCommonStates} objects.
+ *
+ * @hide
+ */
+ public static final class Builder {
+ @ConsentStatusCode private int mMeasurementState;
+ @ConsentStatusCode private int mPaState;
+
+ public Builder() {}
+
+ /** Set the measurement allowed by the getAdServicesCommonStates API */
+ @NonNull
+ public AdServicesCommonStates.Builder setMeasurementState(
+ @ConsentStatusCode int measurementState) {
+ mMeasurementState = measurementState;
+ return this;
+ }
+
+ /** Set the pa allowed by the getAdServicesCommonStates API. */
+ @NonNull
+ public AdServicesCommonStates.Builder setPaState(@ConsentStatusCode int paState) {
+ mPaState = paState;
+ return this;
+ }
+
+ /** Builds a {@link AdServicesCommonStates} instance. */
+ @NonNull
+ public AdServicesCommonStates build() {
+ return new AdServicesCommonStates(mMeasurementState, mPaState);
+ }
+ }
+}
diff --git a/android-35/android/adservices/common/AdServicesCommonStatesResponse.java b/android-35/android/adservices/common/AdServicesCommonStatesResponse.java
new file mode 100644
index 0000000..ff5ace5
--- /dev/null
+++ b/android-35/android/adservices/common/AdServicesCommonStatesResponse.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 android.adservices.common;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.adservices.flags.Flags;
+
+import java.util.Objects;
+
+/**
+ * Response parcel of the getAdservicesCommonStates API.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_GET_ADSERVICES_COMMON_STATES_API_ENABLED)
+public final class AdServicesCommonStatesResponse implements Parcelable {
+
+ private AdServicesCommonStates mAdServicesCommonStates;
+
+ private AdServicesCommonStatesResponse(AdServicesCommonStates adservicesCommonStates) {
+ mAdServicesCommonStates = adservicesCommonStates;
+ }
+
+ private AdServicesCommonStatesResponse(@NonNull Parcel in) {
+ mAdServicesCommonStates = in.readParcelable(AdServicesCommonStates.class.getClassLoader());
+ }
+
+ @NonNull
+ public AdServicesCommonStates getAdServicesCommonStates() {
+ return mAdServicesCommonStates;
+ }
+
+ @NonNull
+ public static final Creator<AdServicesCommonStatesResponse> CREATOR =
+ new Creator<AdServicesCommonStatesResponse>() {
+ @Override
+ public AdServicesCommonStatesResponse createFromParcel(@NonNull Parcel in) {
+ Objects.requireNonNull(in);
+ return new AdServicesCommonStatesResponse(in);
+ }
+
+ @Override
+ public AdServicesCommonStatesResponse[] newArray(int size) {
+ return new AdServicesCommonStatesResponse[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** @hide */
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ Objects.requireNonNull(dest);
+
+ dest.writeParcelable(mAdServicesCommonStates, flags);
+ }
+
+ @Override
+ public String toString() {
+ return "EnableAdServicesResponse{"
+ + "mAdservicesCommonStates="
+ + mAdServicesCommonStates
+ + "'}";
+ }
+
+ /**
+ * Builder for {@link AdServicesCommonStatesResponse} objects.
+ *
+ * @hide
+ */
+ public static final class Builder {
+ private AdServicesCommonStates mAdServicesCommonStates;
+
+ public Builder(@NonNull AdServicesCommonStates adservicesCommonStates) {
+ mAdServicesCommonStates = adservicesCommonStates;
+ }
+
+ /** Set the enableAdServices API response status Code. */
+ @NonNull
+ public AdServicesCommonStatesResponse.Builder setAdservicesCommonStates(
+ AdServicesCommonStates adservicesCommonStates) {
+ mAdServicesCommonStates = adservicesCommonStates;
+ return this;
+ }
+
+ /**
+ * Builds a {@link AdServicesCommonStatesResponse} instance.
+ *
+ * <p>throws IllegalArgumentException if any of the status code is null or error message is
+ * not set for an unsuccessful status.
+ */
+ @NonNull
+ public AdServicesCommonStatesResponse build() {
+ return new AdServicesCommonStatesResponse(mAdServicesCommonStates);
+ }
+ }
+}
diff --git a/android-35/android/adservices/common/AdServicesOutcomeReceiver.java b/android-35/android/adservices/common/AdServicesOutcomeReceiver.java
new file mode 100644
index 0000000..10aa1a4
--- /dev/null
+++ b/android-35/android/adservices/common/AdServicesOutcomeReceiver.java
@@ -0,0 +1,47 @@
+/*
+ * 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.common;
+
+import android.annotation.NonNull;
+
+/**
+ * Callback interface intended for use when an asynchronous operation may result in a failure. Exact
+ * copy of the {@link android.os.OutcomeReceiver} class, re-defined in the AdServices package for
+ * backwards compatibility to Android R.
+ *
+ * <p>This interface may be used in cases where an asynchronous API may complete either with a value
+ * or with a {@link Throwable} that indicates an error.
+ *
+ * @param <R> The type of the result that's being sent.
+ * @param <E> The type of the {@link Throwable} that contains more information about the error.
+ */
+public interface AdServicesOutcomeReceiver<R, E extends Throwable> {
+ /**
+ * Called when the asynchronous operation succeeds and delivers a result value.
+ *
+ * @param result The value delivered by the asynchronous operation.
+ */
+ void onResult(R result);
+
+ /**
+ * Called when the asynchronous operation fails. The mode of failure is indicated by the {@link
+ * Throwable} passed as an argument to this method.
+ *
+ * @param error A subclass of {@link Throwable} with more details about the error that occurred.
+ */
+ default void onError(@NonNull E error) {}
+}
diff --git a/android-35/android/adservices/common/AdServicesPermissions.java b/android-35/android/adservices/common/AdServicesPermissions.java
new file mode 100644
index 0000000..ca21c9b
--- /dev/null
+++ b/android-35/android/adservices/common/AdServicesPermissions.java
@@ -0,0 +1,151 @@
+/*
+ * 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.common;
+
+import android.annotation.FlaggedApi;
+import android.annotation.SystemApi;
+
+import com.android.adservices.flags.Flags;
+
+/** Permissions used by the AdServices APIs. */
+public class AdServicesPermissions {
+ private AdServicesPermissions() {}
+
+ /** This permission needs to be declared by the caller of Topics APIs. */
+ public static final String ACCESS_ADSERVICES_TOPICS =
+ "android.permission.ACCESS_ADSERVICES_TOPICS";
+
+ /** This permission needs to be declared by the caller of Attribution APIs. */
+ public static final String ACCESS_ADSERVICES_ATTRIBUTION =
+ "android.permission.ACCESS_ADSERVICES_ATTRIBUTION";
+
+ /** This permission needs to be declared by the caller of Custom Audiences APIs. */
+ public static final String ACCESS_ADSERVICES_CUSTOM_AUDIENCE =
+ "android.permission.ACCESS_ADSERVICES_CUSTOM_AUDIENCE";
+
+ /** This permission needs to be declared by the caller of Protected Signals APIs. */
+ @FlaggedApi(Flags.FLAG_PROTECTED_SIGNALS_ENABLED)
+ public static final String ACCESS_ADSERVICES_PROTECTED_SIGNALS =
+ "android.permission.ACCESS_ADSERVICES_PROTECTED_SIGNALS";
+
+ /** This permission needs to be declared by the caller of Protected Signals APIs. */
+ @SuppressWarnings("FlaggedApi") // aconfig not available on this branch
+ @FlaggedApi(Flags.FLAG_PROTECTED_SIGNALS_ENABLED)
+ public static final String ACCESS_ADSERVICES_AD_SELECTION =
+ "android.permission.ACCESS_ADSERVICES_AD_SELECTION";
+
+ /** This permission needs to be declared by the caller of Advertising ID APIs. */
+ public static final String ACCESS_ADSERVICES_AD_ID =
+ "android.permission.ACCESS_ADSERVICES_AD_ID";
+
+ /**
+ * This is a signature permission that needs to be declared by the AdServices apk to access API
+ * for AdID provided by another provider service. The signature permission is required to make
+ * sure that only AdServices is permitted to access this api.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String ACCESS_PRIVILEGED_AD_ID =
+ "android.permission.ACCESS_PRIVILEGED_AD_ID";
+
+ /**
+ * This is a signature permission needs to be declared by the AdServices apk to access API for
+ * AppSetId provided by another provider service. The signature permission is required to make
+ * sure that only AdServices is permitted to access this api.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String ACCESS_PRIVILEGED_APP_SET_ID =
+ "android.permission.ACCESS_PRIVILEGED_APP_SET_ID";
+
+ /**
+ * The permission that lets it modify AdService's enablement state modification API.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String MODIFY_ADSERVICES_STATE =
+ "android.permission.MODIFY_ADSERVICES_STATE";
+
+ /**
+ * The permission that lets it modify AdService's enablement state modification API on S-.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String MODIFY_ADSERVICES_STATE_COMPAT =
+ "android.permission.MODIFY_ADSERVICES_STATE_COMPAT";
+
+ /**
+ * The permission that lets it access AdService's enablement state modification API.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String ACCESS_ADSERVICES_STATE =
+ "android.permission.ACCESS_ADSERVICES_STATE";
+
+ /**
+ * The permission that lets it access AdService's enablement state modification API on S-.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String ACCESS_ADSERVICES_STATE_COMPAT =
+ "android.permission.ACCESS_ADSERVICES_STATE_COMPAT";
+
+ /**
+ * The permission needed to call AdServicesManager APIs
+ *
+ * @hide
+ */
+ public static final String ACCESS_ADSERVICES_MANAGER =
+ "android.permission.ACCESS_ADSERVICES_MANAGER";
+
+ /**
+ * This is a signature permission needs to be declared by the AdServices apk to access API for
+ * AdServices Cobalt upload service provided by another provider service. The signature
+ * permission is required to make sure that only AdServices is permitted to access this api.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String ACCESS_PRIVILEGED_ADSERVICES_COBALT_UPLOAD =
+ "android.permission.ACCESS_PRIVILEGED_AD_SERVICES_COBALT_UPLOAD";
+
+ /**
+ * The permission that allows calling updating AdId Cache API via Common Service.
+ *
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_AD_ID_CACHE_ENABLED)
+ public static final String UPDATE_PRIVILEGED_AD_ID =
+ "android.permission.UPDATE_PRIVILEGED_AD_ID";
+
+ /**
+ * The permission that allows calling updating AdId Cache API via Common Service on S-.
+ *
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_AD_ID_CACHE_ENABLED)
+ public static final String UPDATE_PRIVILEGED_AD_ID_COMPAT =
+ "android.permission.UPDATE_PRIVILEGED_AD_ID_COMPAT";
+}
diff --git a/android-35/android/adservices/common/AdServicesResponse.java b/android-35/android/adservices/common/AdServicesResponse.java
new file mode 100644
index 0000000..017fbbe
--- /dev/null
+++ b/android-35/android/adservices/common/AdServicesResponse.java
@@ -0,0 +1,135 @@
+/*
+ * 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.common;
+
+import static android.adservices.common.AdServicesStatusUtils.STATUS_SUCCESS;
+import static android.adservices.common.AdServicesStatusUtils.StatusCode;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Represents an abstract, generic response for AdServices APIs.
+ *
+ * @hide
+ */
+public class AdServicesResponse implements Parcelable {
+ @NonNull
+ public static final Creator<AdServicesResponse> CREATOR =
+ new Parcelable.Creator<AdServicesResponse>() {
+ @Override
+ public AdServicesResponse createFromParcel(@NonNull Parcel in) {
+ Objects.requireNonNull(in);
+ return new AdServicesResponse(in);
+ }
+
+ @Override
+ public AdServicesResponse[] newArray(int size) {
+ return new AdServicesResponse[size];
+ }
+ };
+
+ @StatusCode protected final int mStatusCode;
+ @Nullable protected final String mErrorMessage;
+
+ protected AdServicesResponse(@NonNull Builder builder) {
+ mStatusCode = builder.mStatusCode;
+ mErrorMessage = builder.mErrorMessage;
+ }
+
+ protected AdServicesResponse(@NonNull Parcel in) {
+ Objects.requireNonNull(in);
+
+ mStatusCode = in.readInt();
+ mErrorMessage = in.readString();
+ }
+
+ protected AdServicesResponse(@StatusCode int statusCode, @Nullable String errorMessage) {
+ mStatusCode = statusCode;
+ mErrorMessage = errorMessage;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** @hide */
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ Objects.requireNonNull(dest);
+
+ dest.writeInt(mStatusCode);
+ dest.writeString(mErrorMessage);
+ }
+
+ /** Returns one of the {@code STATUS} constants defined in {@link StatusCode}. */
+ @StatusCode
+ public int getStatusCode() {
+ return mStatusCode;
+ }
+
+ /**
+ * Returns {@code true} if {@link #getStatusCode} is {@link
+ * AdServicesStatusUtils#STATUS_SUCCESS}.
+ */
+ public boolean isSuccess() {
+ return getStatusCode() == STATUS_SUCCESS;
+ }
+
+ /** Returns the error message associated with this response. */
+ @Nullable
+ public String getErrorMessage() {
+ return mErrorMessage;
+ }
+
+ /**
+ * Builder for {@link AdServicesResponse} objects.
+ *
+ * @hide
+ */
+ public static final class Builder {
+ @StatusCode private int mStatusCode = STATUS_SUCCESS;
+ @Nullable private String mErrorMessage;
+
+ public Builder() {}
+
+ /** Set the Status Code. */
+ @NonNull
+ public AdServicesResponse.Builder setStatusCode(@StatusCode int statusCode) {
+ mStatusCode = statusCode;
+ return this;
+ }
+
+ /** Set the Error Message. */
+ @NonNull
+ public AdServicesResponse.Builder setErrorMessage(@Nullable String errorMessage) {
+ mErrorMessage = errorMessage;
+ return this;
+ }
+
+ /** Builds a {@link AdServicesResponse} instance. */
+ @NonNull
+ public AdServicesResponse build() {
+ return new AdServicesResponse(this);
+ }
+ }
+}
diff --git a/android-35/android/adservices/common/AdServicesStates.java b/android-35/android/adservices/common/AdServicesStates.java
new file mode 100644
index 0000000..3a26ccb
--- /dev/null
+++ b/android-35/android/adservices/common/AdServicesStates.java
@@ -0,0 +1,181 @@
+/*
+ * 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.common;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * AdServicesStates exposed to system apps/services through the enableAdServices API. The bits
+ * stored in this parcel can change frequently based on user interaction with the Ads settings page.
+ *
+ * @hide
+ */
+@SystemApi
+public final class AdServicesStates implements Parcelable {
+
+ public static final @NonNull Creator<AdServicesStates> CREATOR =
+ new Parcelable.Creator<AdServicesStates>() {
+ @Override
+ public AdServicesStates createFromParcel(Parcel in) {
+ return new AdServicesStates(in);
+ }
+
+ @Override
+ public AdServicesStates[] newArray(int size) {
+ return new AdServicesStates[size];
+ }
+ };
+
+ private boolean mIsPrivacySandboxUiEnabled;
+ private boolean mIsPrivacySandboxUiRequest;
+ private boolean mIsU18Account;
+ private boolean mIsAdultAccount;
+ private boolean mIsAdIdEnabled;
+
+ private AdServicesStates(
+ boolean isPrivacySandboxUiEnabled,
+ boolean isPrivacySandboxUiRequest,
+ boolean isU18Account,
+ boolean isAdultAccount,
+ boolean isAdIdEnabled) {
+ mIsPrivacySandboxUiEnabled = isPrivacySandboxUiEnabled;
+ mIsPrivacySandboxUiRequest = isPrivacySandboxUiRequest;
+ mIsU18Account = isU18Account;
+ mIsAdultAccount = isAdultAccount;
+ mIsAdIdEnabled = isAdIdEnabled;
+ }
+
+ private AdServicesStates(@NonNull Parcel in) {
+ mIsPrivacySandboxUiEnabled = in.readBoolean();
+ mIsPrivacySandboxUiRequest = in.readBoolean();
+ mIsU18Account = in.readBoolean();
+ mIsAdultAccount = in.readBoolean();
+ mIsAdIdEnabled = in.readBoolean();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeBoolean(mIsPrivacySandboxUiEnabled);
+ out.writeBoolean(mIsPrivacySandboxUiRequest);
+ out.writeBoolean(mIsU18Account);
+ out.writeBoolean(mIsAdultAccount);
+ out.writeBoolean(mIsAdIdEnabled);
+ }
+
+ /** Returns whether the privacy sandbox UI is visible from the settings app. */
+ @NonNull
+ public boolean isPrivacySandboxUiEnabled() {
+ return mIsPrivacySandboxUiEnabled;
+ }
+
+ /**
+ * Returns whether the API call was the byproduct of a privacy sandbox UI request from the
+ * settings app.
+ */
+ @NonNull
+ public boolean isPrivacySandboxUiRequest() {
+ return mIsPrivacySandboxUiRequest;
+ }
+
+ /** Returns whether Advertising ID is enabled. */
+ @NonNull
+ public boolean isAdIdEnabled() {
+ return mIsAdIdEnabled;
+ }
+
+ /**
+ * Determines whether the user account is eligible for the U18 (under 18) privacy sandbox, in
+ * which all ads relevancepersonalized Ads APIs are * permanently disabled and the ad
+ * measurement API can be enabled/disabled by the user. An account is considered a U18 account
+ * if privacy sandbox has received signals that the user is a minor.
+ */
+ @NonNull
+ public boolean isU18Account() {
+ return mIsU18Account;
+ }
+
+ /**
+ * Determines whether the user account is eligible for the adult or full-fledged privacy
+ * sandbox, in which all Ads APIs can be * enabled/disabled by the user. An account is
+ * considered an adult account if privacy sandbox has received signals that the user is an
+ * adult.
+ */
+ @NonNull
+ public boolean isAdultAccount() {
+ return mIsAdultAccount;
+ }
+
+ /** Builder for {@link AdServicesStates} objects. */
+ public static final class Builder {
+ private boolean mIsPrivacySandboxUiEnabled;
+ private boolean mIsPrivacySandboxUiRequest;
+ private boolean mIsU18Account;
+ private boolean mIsAdultAccount;
+ private boolean mIsAdIdEnabled;
+
+ public Builder() {
+ }
+
+ /** Set if the privacy sandbox UX entry point is enabled. */
+ public @NonNull Builder setPrivacySandboxUiEnabled(boolean isPrivacySandboxUiEnabled) {
+ mIsPrivacySandboxUiEnabled = isPrivacySandboxUiEnabled;
+ return this;
+ }
+
+ /** Set if the API call was the result of a privacy sandbox UX entry point request. */
+ public @NonNull Builder setPrivacySandboxUiRequest(boolean isPrivacySandboxUiRequest) {
+ mIsPrivacySandboxUiRequest = isPrivacySandboxUiRequest;
+ return this;
+ }
+
+ /** Set if the device is currently running under an U18 account. */
+ public @NonNull Builder setU18Account(boolean isU18Account) {
+ mIsU18Account = isU18Account;
+ return this;
+ }
+
+ /** Set if the device is currently running under an adult account. */
+ public @NonNull Builder setAdultAccount(boolean isAdultAccount) {
+ mIsAdultAccount = isAdultAccount;
+ return this;
+ }
+
+ /** Set if user has opt-in/out of Advertising ID. */
+ public @NonNull Builder setAdIdEnabled(boolean isAdIdEnabled) {
+ mIsAdIdEnabled = isAdIdEnabled;
+ return this;
+ }
+
+ /** Builds a {@link AdServicesStates} instance. */
+ public @NonNull AdServicesStates build() {
+ return new AdServicesStates(
+ mIsPrivacySandboxUiEnabled,
+ mIsPrivacySandboxUiRequest,
+ mIsU18Account,
+ mIsAdultAccount,
+ mIsAdIdEnabled);
+ }
+ }
+}
diff --git a/android-35/android/adservices/common/AdServicesStatusUtils.java b/android-35/android/adservices/common/AdServicesStatusUtils.java
new file mode 100644
index 0000000..e5403a3
--- /dev/null
+++ b/android-35/android/adservices/common/AdServicesStatusUtils.java
@@ -0,0 +1,403 @@
+/*
+ * 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.common;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.os.LimitExceededException;
+
+import com.android.adservices.shared.common.ServiceUnavailableException;
+
+import java.io.IOException;
+import java.io.InvalidObjectException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Utility class containing status codes and functions used by various response objects.
+ *
+ * <p>Those status codes are internal only.
+ *
+ * @hide
+ */
+public final class AdServicesStatusUtils {
+
+ /**
+ * The status code has not been set. Keep unset status code the lowest value of the status
+ * codes.
+ */
+ public static final int STATUS_UNSET = -1;
+
+ /** The call was successful. */
+ public static final int STATUS_SUCCESS = 0;
+
+ /**
+ * An internal error occurred within the API, which the caller cannot address.
+ *
+ * <p>This error may be considered similar to {@link IllegalStateException}.
+ */
+ public static final int STATUS_INTERNAL_ERROR = 1;
+
+ /**
+ * The caller supplied invalid arguments to the call.
+ *
+ * <p>This error may be considered similar to {@link IllegalArgumentException}.
+ */
+ public static final int STATUS_INVALID_ARGUMENT = 2;
+
+ /** There was an unknown error. */
+ public static final int STATUS_UNKNOWN_ERROR = 3;
+
+ /**
+ * There was an I/O error.
+ *
+ * <p>This error may be considered similar to {@link IOException}.
+ */
+ public static final int STATUS_IO_ERROR = 4;
+
+ /**
+ * Result code for Rate Limit Reached.
+ *
+ * <p>This error may be considered similar to {@link LimitExceededException}.
+ */
+ public static final int STATUS_RATE_LIMIT_REACHED = 5;
+
+ /**
+ * Killswitch was enabled. AdServices is not available.
+ *
+ * <p>This error may be considered similar to {@link IllegalStateException}.
+ */
+ public static final int STATUS_KILLSWITCH_ENABLED = 6;
+
+ /**
+ * User consent was revoked. AdServices is not available.
+ *
+ * <p>This error may be considered similar to {@link IllegalStateException}.
+ */
+ public static final int STATUS_USER_CONSENT_REVOKED = 7;
+
+ /**
+ * AdServices were disabled. AdServices is not available.
+ *
+ * <p>This error may be considered similar to {@link IllegalStateException}.
+ */
+ public static final int STATUS_ADSERVICES_DISABLED = 8;
+
+ /**
+ * The caller is not authorized to make this call. Permission was not requested.
+ *
+ * <p>This error may be considered similar to {@link SecurityException}.
+ */
+ public static final int STATUS_PERMISSION_NOT_REQUESTED = 9;
+
+ /**
+ * The caller is not authorized to make this call. Caller is not allowed (not present in the
+ * allowed list).
+ *
+ * <p>This error may be considered similar to {@link SecurityException}.
+ */
+ public static final int STATUS_CALLER_NOT_ALLOWED = 10;
+
+ /**
+ * The caller is not authorized to make this call. Call was executed from background thread.
+ *
+ * <p>This error may be considered similar to {@link IllegalStateException}.
+ */
+ public static final int STATUS_BACKGROUND_CALLER = 11;
+
+ /**
+ * The caller is not authorized to make this call.
+ *
+ * <p>This error may be considered similar to {@link SecurityException}.
+ */
+ public static final int STATUS_UNAUTHORIZED = 12;
+
+ /**
+ * There was an internal Timeout within the API, which is non-recoverable by the caller
+ *
+ * <p>This error may be considered similar to {@link java.util.concurrent.TimeoutException}
+ */
+ public static final int STATUS_TIMEOUT = 13;
+
+ /**
+ * The device is not running a version of WebView that supports JSSandbox, required for FLEDGE
+ * Ad Selection.
+ *
+ * <p>This error may be considered similar to {@link IllegalStateException}.
+ */
+ public static final int STATUS_JS_SANDBOX_UNAVAILABLE = 14;
+
+ /**
+ * The service received an invalid object from the remote server.
+ *
+ * <p>This error may be considered similar to {@link InvalidObjectException}.
+ */
+ public static final int STATUS_INVALID_OBJECT = 15;
+
+ /**
+ * The caller is not authorized to make this call because it crosses user boundaries.
+ *
+ * <p>This error may be considered similar to {@link SecurityException}.
+ */
+ public static final int STATUS_CALLER_NOT_ALLOWED_TO_CROSS_USER_BOUNDARIES = 16;
+
+ /**
+ * Result code for Server Rate Limit Reached.
+ *
+ * <p>This error may be considered similar to {@link LimitExceededException}.
+ */
+ public static final int STATUS_SERVER_RATE_LIMIT_REACHED = 17;
+
+ /**
+ * Consent notification has not been displayed yet. AdServices is not available.
+ *
+ * <p>This error may be considered similar to {@link IllegalStateException}.
+ */
+ public static final int STATUS_USER_CONSENT_NOTIFICATION_NOT_DISPLAYED_YET = 18;
+
+ /**
+ * Result code for Encryption related failures.
+ *
+ * <p>This error may be considered similar to {@link IllegalArgumentException}.
+ */
+ public static final int STATUS_ENCRYPTION_FAILURE = 19;
+
+ /**
+ * The caller is not authorized to make this call because the package is not in the allowlist.
+ *
+ * <p>This error may be considered similar to {@link SecurityException}.
+ */
+ public static final int STATUS_CALLER_NOT_ALLOWED_PACKAGE_NOT_IN_ALLOWLIST = 20;
+
+ /**
+ * The caller is not authorized to make this call because the package is not in the allowlist.
+ *
+ * <p>This error may be considered similar to {@link SecurityException}.
+ */
+ public static final int STATUS_CALLER_NOT_ALLOWED_PACKAGE_BLOCKLISTED = 21;
+
+ /**
+ * The caller is not authorized to make this call because enrollment data can't be found.
+ *
+ * <p>This error may be considered similar to {@link SecurityException}.
+ */
+ public static final int STATUS_CALLER_NOT_ALLOWED_ENROLLMENT_MATCH_NOT_FOUND = 22;
+
+ /**
+ * The caller is not authorized to make this call because enrollment ID is invalid.
+ *
+ * <p>This error may be considered similar to {@link SecurityException}.
+ */
+ public static final int STATUS_CALLER_NOT_ALLOWED_ENROLLMENT_INVALID_ID = 23;
+
+ /**
+ * The caller is not authorized to make this call because enrollment ID is in the blocklist.
+ *
+ * <p>This error may be considered similar to {@link SecurityException}.
+ */
+ public static final int STATUS_CALLER_NOT_ALLOWED_ENROLLMENT_BLOCKLISTED = 24;
+
+ /**
+ * The caller is not authorized to make this call because permission was not requested in the
+ * manifest.
+ *
+ * <p>This error may be considered similar to {@link SecurityException}.
+ */
+ public static final int STATUS_CALLER_NOT_ALLOWED_MANIFEST_ADSERVICES_CONFIG_NO_PERMISSION = 25;
+
+ /**
+ * AdServices activity is disabled.
+ *
+ * <p>This error may be considered similar to {@link IllegalStateException}.
+ */
+ public static final int STATUS_ADSERVICES_ACTIVITY_DISABLED = 26;
+
+ /**
+ * Callback is shut down and encountered an error when invoking its methods.
+ *
+ * <p>This error may be considered similar to {@link IllegalStateException}.
+ */
+ public static final int STATUS_CALLBACK_SHUTDOWN = 27;
+
+ /** The error message to be returned along with {@link LimitExceededException}. */
+ public static final String RATE_LIMIT_REACHED_ERROR_MESSAGE = "API rate limit exceeded.";
+
+ /** The error message to be returned along with {@link LimitExceededException}. */
+ public static final String SERVER_RATE_LIMIT_REACHED_ERROR_MESSAGE =
+ "Server rate limit exceeded.";
+
+ /**
+ * The error message to be returned along with {@link SecurityException} when permission was not
+ * requested in the manifest.
+ */
+ public static final String SECURITY_EXCEPTION_PERMISSION_NOT_REQUESTED_ERROR_MESSAGE =
+ "Caller is not authorized to call this API. Permission was not requested.";
+
+ /**
+ * The error message to be returned along with {@link SecurityException} when caller is not
+ * allowed to call AdServices (not present in the allowed list).
+ */
+ public static final String SECURITY_EXCEPTION_CALLER_NOT_ALLOWED_ERROR_MESSAGE =
+ "Caller is not authorized to call this API. Caller is not allowed.";
+
+ /**
+ * The error message to be returned along with {@link SecurityException} when call was executed
+ * from the background thread.
+ */
+ public static final String ILLEGAL_STATE_BACKGROUND_CALLER_ERROR_MESSAGE =
+ "Background thread is not allowed to call this service.";
+
+ /**
+ * The error message to be returned along with {@link IllegalStateException} when call failed
+ * because AdServices activity is disabled.
+ */
+ public static final String ILLEGAL_STATE_ACTIVITY_DISABLED_ERROR_MESSAGE =
+ "AdServices activity is disabled.";
+
+ /**
+ * The error message to be returned along with {@link SecurityException} when call failed
+ * because it crosses user boundaries.
+ */
+ public static final String SECURITY_EXCEPTION_CALLER_NOT_ALLOWED_TO_CROSS_USER_BOUNDARIES =
+ "Caller is not authorized to access information from another user";
+
+ /**
+ * The error message to be returned along with {@link SecurityException} when caller not allowed
+ * to perform this operation on behalf of the given package.
+ */
+ public static final String SECURITY_EXCEPTION_CALLER_NOT_ALLOWED_ON_BEHALF_ERROR_MESSAGE =
+ "Caller is not allowed to perform this operation on behalf of the given package.";
+
+ /** The error message to be returned along with {@link TimeoutException}. */
+ public static final String TIMED_OUT_ERROR_MESSAGE = "API timed out.";
+
+ /** The error message to be returned along with {@link InvalidObjectException}. */
+ public static final String INVALID_OBJECT_ERROR_MESSAGE =
+ "The service received an invalid object from the server.";
+
+ /** The error message to be returned along with {@link IllegalArgumentException}. */
+ public static final String ENCRYPTION_FAILURE_MESSAGE = "Failed to encrypt responses.";
+
+ /** Returns true for a successful status. */
+ public static boolean isSuccess(@StatusCode int statusCode) {
+ return statusCode == STATUS_SUCCESS;
+ }
+
+ /** Converts the input {@code statusCode} to an exception to be used in the callback. */
+ @NonNull
+ public static Exception asException(@StatusCode int statusCode) {
+ switch (statusCode) {
+ case STATUS_ENCRYPTION_FAILURE:
+ return new IllegalArgumentException(ENCRYPTION_FAILURE_MESSAGE);
+ case STATUS_INVALID_ARGUMENT:
+ return new IllegalArgumentException();
+ case STATUS_IO_ERROR:
+ return new IOException();
+ case STATUS_KILLSWITCH_ENABLED: // Intentional fallthrough
+ case STATUS_USER_CONSENT_NOTIFICATION_NOT_DISPLAYED_YET: // Intentional fallthrough
+ case STATUS_USER_CONSENT_REVOKED: // Intentional fallthrough
+ case STATUS_JS_SANDBOX_UNAVAILABLE:
+ return new ServiceUnavailableException();
+ case STATUS_PERMISSION_NOT_REQUESTED:
+ return new SecurityException(
+ SECURITY_EXCEPTION_PERMISSION_NOT_REQUESTED_ERROR_MESSAGE);
+ case STATUS_CALLER_NOT_ALLOWED:
+ return new SecurityException(SECURITY_EXCEPTION_CALLER_NOT_ALLOWED_ERROR_MESSAGE);
+ case STATUS_CALLER_NOT_ALLOWED_PACKAGE_NOT_IN_ALLOWLIST:
+ return new SecurityException(SECURITY_EXCEPTION_CALLER_NOT_ALLOWED_ERROR_MESSAGE);
+ case STATUS_CALLER_NOT_ALLOWED_PACKAGE_BLOCKLISTED:
+ return new SecurityException(SECURITY_EXCEPTION_CALLER_NOT_ALLOWED_ERROR_MESSAGE);
+ case STATUS_CALLER_NOT_ALLOWED_ENROLLMENT_MATCH_NOT_FOUND:
+ return new SecurityException(SECURITY_EXCEPTION_CALLER_NOT_ALLOWED_ERROR_MESSAGE);
+ case STATUS_CALLER_NOT_ALLOWED_ENROLLMENT_INVALID_ID:
+ return new SecurityException(SECURITY_EXCEPTION_CALLER_NOT_ALLOWED_ERROR_MESSAGE);
+ case STATUS_CALLER_NOT_ALLOWED_ENROLLMENT_BLOCKLISTED:
+ return new SecurityException(SECURITY_EXCEPTION_CALLER_NOT_ALLOWED_ERROR_MESSAGE);
+ case STATUS_CALLER_NOT_ALLOWED_MANIFEST_ADSERVICES_CONFIG_NO_PERMISSION:
+ return new SecurityException(SECURITY_EXCEPTION_CALLER_NOT_ALLOWED_ERROR_MESSAGE);
+ case STATUS_BACKGROUND_CALLER:
+ return new IllegalStateException(ILLEGAL_STATE_BACKGROUND_CALLER_ERROR_MESSAGE);
+ case STATUS_ADSERVICES_ACTIVITY_DISABLED:
+ return new IllegalStateException(ILLEGAL_STATE_ACTIVITY_DISABLED_ERROR_MESSAGE);
+ case STATUS_UNAUTHORIZED:
+ return new SecurityException(
+ SECURITY_EXCEPTION_CALLER_NOT_ALLOWED_ON_BEHALF_ERROR_MESSAGE);
+ case STATUS_TIMEOUT:
+ return new TimeoutException(TIMED_OUT_ERROR_MESSAGE);
+ case STATUS_RATE_LIMIT_REACHED:
+ return new LimitExceededException(RATE_LIMIT_REACHED_ERROR_MESSAGE);
+ case STATUS_INVALID_OBJECT:
+ return new InvalidObjectException(INVALID_OBJECT_ERROR_MESSAGE);
+ case STATUS_SERVER_RATE_LIMIT_REACHED:
+ return new LimitExceededException(SERVER_RATE_LIMIT_REACHED_ERROR_MESSAGE);
+ default:
+ return new IllegalStateException();
+ }
+ }
+
+ /** Converts the {@link AdServicesResponse} to an exception to be used in the callback. */
+ // TODO(b/328601595): Add unit test for AdServicesStatusUtils.asException
+ @NonNull
+ public static Exception asException(@NonNull AdServicesResponse adServicesResponse) {
+ return asException(adServicesResponse.getStatusCode());
+ }
+
+ /**
+ * Result codes that are common across various APIs.
+ *
+ * @hide
+ */
+ @IntDef(
+ prefix = {"STATUS_"},
+ value = {
+ STATUS_UNSET,
+ STATUS_SUCCESS,
+ STATUS_INTERNAL_ERROR,
+ STATUS_INVALID_ARGUMENT,
+ STATUS_RATE_LIMIT_REACHED,
+ STATUS_UNKNOWN_ERROR,
+ STATUS_IO_ERROR,
+ STATUS_KILLSWITCH_ENABLED,
+ STATUS_USER_CONSENT_REVOKED,
+ STATUS_ADSERVICES_DISABLED,
+ STATUS_ADSERVICES_ACTIVITY_DISABLED,
+ STATUS_PERMISSION_NOT_REQUESTED,
+ STATUS_CALLER_NOT_ALLOWED,
+ STATUS_BACKGROUND_CALLER,
+ STATUS_UNAUTHORIZED,
+ STATUS_TIMEOUT,
+ STATUS_JS_SANDBOX_UNAVAILABLE,
+ STATUS_INVALID_OBJECT,
+ STATUS_SERVER_RATE_LIMIT_REACHED,
+ STATUS_USER_CONSENT_NOTIFICATION_NOT_DISPLAYED_YET,
+ STATUS_ENCRYPTION_FAILURE,
+ STATUS_CALLER_NOT_ALLOWED_PACKAGE_NOT_IN_ALLOWLIST,
+ STATUS_CALLER_NOT_ALLOWED_PACKAGE_BLOCKLISTED,
+ STATUS_CALLER_NOT_ALLOWED_ENROLLMENT_MATCH_NOT_FOUND,
+ STATUS_CALLER_NOT_ALLOWED_ENROLLMENT_INVALID_ID,
+ STATUS_CALLER_NOT_ALLOWED_ENROLLMENT_BLOCKLISTED,
+ STATUS_CALLER_NOT_ALLOWED_MANIFEST_ADSERVICES_CONFIG_NO_PERMISSION,
+ STATUS_CALLBACK_SHUTDOWN
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface StatusCode {}
+
+ private AdServicesStatusUtils() {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/android-35/android/adservices/common/AdTechIdentifier.java b/android-35/android/adservices/common/AdTechIdentifier.java
new file mode 100644
index 0000000..e7fe66c
--- /dev/null
+++ b/android-35/android/adservices/common/AdTechIdentifier.java
@@ -0,0 +1,138 @@
+/*
+ * 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.common;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/** An Identifier representing an ad buyer or seller. */
+public final class AdTechIdentifier implements Parcelable {
+
+ @NonNull private final String mIdentifier;
+
+ private AdTechIdentifier(@NonNull Parcel in) {
+ this(in.readString());
+ }
+
+ private AdTechIdentifier(@NonNull String adTechIdentifier) {
+ this(adTechIdentifier, true);
+ }
+
+ private AdTechIdentifier(@NonNull String adTechIdentifier, boolean validate) {
+ Objects.requireNonNull(adTechIdentifier);
+ if (validate) {
+ validate(adTechIdentifier);
+ }
+ mIdentifier = adTechIdentifier;
+ }
+
+ @NonNull
+ public static final Creator<AdTechIdentifier> CREATOR =
+ new Creator<AdTechIdentifier>() {
+ @Override
+ public AdTechIdentifier createFromParcel(Parcel in) {
+ Objects.requireNonNull(in);
+ return new AdTechIdentifier(in);
+ }
+
+ @Override
+ public AdTechIdentifier[] newArray(int size) {
+ return new AdTechIdentifier[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ Objects.requireNonNull(dest);
+ dest.writeString(mIdentifier);
+ }
+
+ /**
+ * Compares this AdTechIdentifier to the specified object. The result is true if and only if the
+ * argument is not null and is a AdTechIdentifier object with the same string form (obtained by
+ * calling {@link #toString()}). Note that this method will not perform any eTLD+1 normalization
+ * so two AdTechIdentifier objects with the same eTLD+1 could be not equal if the String
+ * representations of the objects was not equal.
+ *
+ * @param o The object to compare this AdTechIdentifier against
+ * @return true if the given object represents an AdTechIdentifier equivalent to this
+ * AdTechIdentifier, false otherwise
+ */
+ @Override
+ public boolean equals(Object o) {
+ return o instanceof AdTechIdentifier
+ && mIdentifier.equals(((AdTechIdentifier) o).toString());
+ }
+
+ /**
+ * Returns a hash code corresponding to the string representation of this class obtained by
+ * calling {@link #toString()}. Note that this method will not perform any eTLD+1 normalization
+ * so two AdTechIdentifier objects with the same eTLD+1 could have different hash codes if the
+ * underlying string representation was different.
+ *
+ * @return a hash code value for this object.
+ */
+ @Override
+ public int hashCode() {
+ return mIdentifier.hashCode();
+ }
+
+ /** @return The identifier in String form. */
+ @Override
+ @NonNull
+ public String toString() {
+ return mIdentifier;
+ }
+
+ /**
+ * Construct an instance of this class from a String.
+ *
+ * @param source A valid eTLD+1 domain of an ad buyer or seller or null.
+ * @return An {@link AdTechIdentifier} class wrapping the given domain or null if the input was
+ * null.
+ */
+ @NonNull
+ public static AdTechIdentifier fromString(@NonNull String source) {
+ return AdTechIdentifier.fromString(source, true);
+ }
+
+ /**
+ * Construct an instance of this class from a String.
+ *
+ * @param source A valid eTLD+1 domain of an ad buyer or seller.
+ * @param validate Construction-time validation is run on the string if and only if this is
+ * true.
+ * @return An {@link AdTechIdentifier} class wrapping the given domain.
+ * @hide
+ */
+ @NonNull
+ public static AdTechIdentifier fromString(@NonNull String source, boolean validate) {
+ return new AdTechIdentifier(source, validate);
+ }
+
+ private void validate(String inputString) {
+ // TODO(b/238849930) Bring existing validation function here
+ }
+}
diff --git a/android-35/android/adservices/common/AppInstallFilters.java b/android-35/android/adservices/common/AppInstallFilters.java
new file mode 100644
index 0000000..02b344b
--- /dev/null
+++ b/android-35/android/adservices/common/AppInstallFilters.java
@@ -0,0 +1,206 @@
+/*
+ * 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.common;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.adservices.AdServicesParcelableUtil;
+import com.android.adservices.flags.Flags;
+import com.android.internal.annotations.VisibleForTesting;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.nio.charset.StandardCharsets;
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
+
+// TODO(b/266837113) link to setAppInstallAdvertisers once unhidden.
+
+/**
+ * A container for the ad filters that are based on app install state.
+ *
+ * <p>App install filters filter out ads based on the presence of packages installed on the device.
+ * In order for filtering to work, a package must call the setAppInstallAdvertisers API with the
+ * identifier of the adtech who owns this ad. If that call has been made, and the ad contains an
+ * {@link AppInstallFilters} object whose package name set contains the name of the package, the ad
+ * will be removed from the auction.
+ *
+ * <p>Note that the filtering is based on any package with one of the listed package names being on
+ * the device. It is possible that the package holding the package name is not the application
+ * targeted by the ad.
+ */
+@FlaggedApi(Flags.FLAG_FLEDGE_AD_SELECTION_FILTERING_ENABLED)
+public final class AppInstallFilters implements Parcelable {
+ /** @hide */
+ @VisibleForTesting public static final String PACKAGE_NAMES_FIELD_NAME = "package_names";
+
+ @NonNull private final Set<String> mPackageNames;
+
+ @NonNull
+ public static final Creator<AppInstallFilters> CREATOR =
+ new Creator<AppInstallFilters>() {
+ @NonNull
+ @Override
+ public AppInstallFilters createFromParcel(@NonNull Parcel in) {
+ Objects.requireNonNull(in);
+ return new AppInstallFilters(in);
+ }
+
+ @NonNull
+ @Override
+ public AppInstallFilters[] newArray(int size) {
+ return new AppInstallFilters[size];
+ }
+ };
+
+ private AppInstallFilters(@NonNull Builder builder) {
+ Objects.requireNonNull(builder);
+
+ mPackageNames = builder.mPackageNames;
+ }
+
+ private AppInstallFilters(@NonNull Parcel in) {
+ Objects.requireNonNull(in);
+
+ mPackageNames = AdServicesParcelableUtil.readStringSetFromParcel(in);
+ }
+
+ /**
+ * Gets the list of package names this ad is filtered on.
+ *
+ * <p>The ad containing this filter will be removed from the ad auction if any of the package
+ * names are present on the device and have called setAppInstallAdvertisers.
+ */
+ @NonNull
+ public Set<String> getPackageNames() {
+ return mPackageNames;
+ }
+
+ /**
+ * @return The estimated size of this object, in bytes using UTF_8 encoding.
+ * @hide
+ */
+ public int getSizeInBytes() {
+ int totalSize = 0;
+ for (String packageName : mPackageNames) {
+ totalSize += packageName.getBytes(StandardCharsets.UTF_8).length;
+ }
+ return totalSize;
+ }
+
+ /**
+ * A JSON serializer.
+ *
+ * @return A JSON serialization of this object.
+ * @hide
+ */
+ public JSONObject toJson() throws JSONException {
+ JSONObject toReturn = new JSONObject();
+ JSONArray packageNames = new JSONArray();
+ for (String packageName : mPackageNames) {
+ packageNames.put(packageName);
+ }
+ toReturn.put(PACKAGE_NAMES_FIELD_NAME, packageNames);
+ return toReturn;
+ }
+
+ /**
+ * A JSON de-serializer.
+ *
+ * @param json A JSON representation of an {@link AppInstallFilters} object as would be
+ * generated by {@link #toJson()}.
+ * @return An {@link AppInstallFilters} object generated from the given JSON.
+ * @hide
+ */
+ public static AppInstallFilters fromJson(JSONObject json) throws JSONException {
+ JSONArray serializedPackageNames = json.getJSONArray(PACKAGE_NAMES_FIELD_NAME);
+ Set<String> packageNames = new HashSet<>();
+ for (int i = 0; i < serializedPackageNames.length(); i++) {
+ Object packageName = serializedPackageNames.get(i);
+ if (packageName instanceof String) {
+ packageNames.add((String) packageName);
+ } else {
+ throw new JSONException(
+ "Found non-string package name when de-serializing AppInstallFilters");
+ }
+ }
+ return new Builder().setPackageNames(packageNames).build();
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ Objects.requireNonNull(dest);
+ AdServicesParcelableUtil.writeStringSetToParcel(dest, mPackageNames);
+ }
+
+ /** @hide */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Checks whether the {@link AppInstallFilters} objects contain the same information. */
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof AppInstallFilters)) return false;
+ AppInstallFilters that = (AppInstallFilters) o;
+ return mPackageNames.equals(that.mPackageNames);
+ }
+
+ /** Returns the hash of the {@link AppInstallFilters} object's data. */
+ @Override
+ public int hashCode() {
+ return Objects.hash(mPackageNames);
+ }
+
+ @Override
+ public String toString() {
+ return "AppInstallFilters{" + "mPackageNames=" + mPackageNames + '}';
+ }
+
+ /** Builder for creating {@link AppInstallFilters} objects. */
+ public static final class Builder {
+ @NonNull private Set<String> mPackageNames = new HashSet<>();
+
+ public Builder() {}
+
+ /**
+ * Gets the list of package names this ad is filtered on.
+ *
+ * <p>See {@link #getPackageNames()} for more information.
+ */
+ @NonNull
+ public Builder setPackageNames(@NonNull Set<String> packageNames) {
+ Objects.requireNonNull(packageNames);
+ mPackageNames = packageNames;
+ return this;
+ }
+
+ /** Builds and returns a {@link AppInstallFilters} instance. */
+ @NonNull
+ public AppInstallFilters build() {
+ return new AppInstallFilters(this);
+ }
+ }
+}
diff --git a/android-35/android/adservices/common/AssetFileDescriptorUtil.java b/android-35/android/adservices/common/AssetFileDescriptorUtil.java
new file mode 100644
index 0000000..fe56fbe
--- /dev/null
+++ b/android-35/android/adservices/common/AssetFileDescriptorUtil.java
@@ -0,0 +1,88 @@
+/*
+ * 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.common;
+
+import android.annotation.NonNull;
+import android.content.res.AssetFileDescriptor;
+import android.os.ParcelFileDescriptor;
+
+import com.android.adservices.LoggerFactory;
+
+import java.io.DataInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Objects;
+import java.util.concurrent.ExecutorService;
+
+/**
+ * Utility class used to set up the read and write pipes for the usage of reading pointers from
+ * shared memory.
+ *
+ * @hide
+ */
+public class AssetFileDescriptorUtil {
+ private static final LoggerFactory.Logger sLogger = LoggerFactory.getFledgeLogger();
+
+ private AssetFileDescriptorUtil() {}
+
+ /**
+ * Creates a read and write pipe, writes the data in {@code buffer} into the write end, and
+ * returns the read end in the form of a {@link AssetFileDescriptor}.
+ *
+ * @throws IOException if an exception is encountered while creating or writing to the pipe
+ */
+ public static AssetFileDescriptor setupAssetFileDescriptorResponse(
+ @NonNull byte[] buffer, @NonNull ExecutorService executorService) throws IOException {
+ Objects.requireNonNull(buffer);
+ Objects.requireNonNull(executorService);
+
+ ParcelFileDescriptor[] descriptors = ParcelFileDescriptor.createPipe();
+ ParcelFileDescriptor writeDescriptor = descriptors[1];
+
+ executorService.execute(
+ () -> {
+ try (FileOutputStream outputStream =
+ new FileOutputStream(writeDescriptor.getFileDescriptor())) {
+ outputStream.write(buffer);
+ } catch (IOException e) {
+ sLogger.e(
+ e, "Encountered IO Exception while writing byte array to stream.");
+ }
+ });
+ return new AssetFileDescriptor(descriptors[0], 0, buffer.length);
+ }
+
+ /**
+ * Reads the content the {@link AssetFileDescriptor} points to into a buffer and returns the
+ * number of bytes read.
+ *
+ * @throws IOException if an exception is encountered while reading the content.
+ */
+ public static byte[] readAssetFileDescriptorIntoBuffer(
+ @NonNull AssetFileDescriptor assetFileDescriptor) throws IOException {
+ Objects.requireNonNull(assetFileDescriptor);
+
+ byte[] result = new byte[(int) assetFileDescriptor.getLength()];
+
+ try (DataInputStream inputStream =
+ new DataInputStream(assetFileDescriptor.createInputStream())) {
+ inputStream.readFully(result);
+ }
+
+ return result;
+ }
+}
diff --git a/android-35/android/adservices/common/CallerMetadata.java b/android-35/android/adservices/common/CallerMetadata.java
new file mode 100644
index 0000000..4c3be11
--- /dev/null
+++ b/android-35/android/adservices/common/CallerMetadata.java
@@ -0,0 +1,90 @@
+/*
+ * 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.common;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * A class to hold the metadata of an IPC call.
+ *
+ * @hide
+ */
+public class CallerMetadata implements Parcelable {
+ private @NonNull long mBinderElapsedTimestamp;
+
+ private CallerMetadata(@NonNull long binderElapsedTimestamp) {
+ mBinderElapsedTimestamp = binderElapsedTimestamp;
+ }
+
+ private CallerMetadata(@NonNull Parcel in) {
+ mBinderElapsedTimestamp = in.readLong();
+ }
+
+ @NonNull
+ public static final Parcelable.Creator<CallerMetadata> CREATOR =
+ new Parcelable.Creator<CallerMetadata>() {
+ @Override
+ public CallerMetadata createFromParcel(@NonNull Parcel in) {
+ Objects.requireNonNull(in);
+ return new CallerMetadata(in);
+ }
+
+ @Override
+ public CallerMetadata[] newArray(int size) {
+ return new CallerMetadata[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeLong(mBinderElapsedTimestamp);
+ }
+
+ /** Get the binder elapsed timestamp. */
+ public long getBinderElapsedTimestamp() {
+ return mBinderElapsedTimestamp;
+ }
+
+ /** Builder for {@link CallerMetadata} objects. */
+ public static final class Builder {
+ private long mBinderElapsedTimestamp;
+
+ public Builder() {
+ }
+
+ /** Set the binder elapsed timestamp. */
+ public @NonNull CallerMetadata.Builder setBinderElapsedTimestamp(
+ @NonNull long binderElapsedTimestamp) {
+ mBinderElapsedTimestamp = binderElapsedTimestamp;
+ return this;
+ }
+
+ /** Builds a {@link CallerMetadata} instance. */
+ public @NonNull CallerMetadata build() {
+ return new CallerMetadata(mBinderElapsedTimestamp);
+ }
+ }
+}
diff --git a/android-35/android/adservices/common/ConsentStatus.java b/android-35/android/adservices/common/ConsentStatus.java
new file mode 100644
index 0000000..2fc6b38
--- /dev/null
+++ b/android-35/android/adservices/common/ConsentStatus.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 android.adservices.common;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Utility class containing consent status codes.
+ *
+ * <p>Those status codes are internal only.
+ *
+ * @hide
+ */
+public class ConsentStatus {
+ public static final int UNKNOWN = 0;
+ public static final int UNSET = 1;
+ public static final int REVOKED = 2;
+ public static final int GIVEN = 3;
+ public static final int SERVICE_NOT_ENABLED = 4;
+ public static final int WAS_RESET = 5;
+
+ /**
+ * Result codes that are common across various APIs.
+ *
+ * @hide
+ */
+ @IntDef(
+ prefix = {""},
+ value = {UNKNOWN, UNSET, REVOKED, GIVEN, SERVICE_NOT_ENABLED, WAS_RESET})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ConsentStatusCode {}
+}
diff --git a/android-35/android/adservices/common/EnableAdServicesResponse.java b/android-35/android/adservices/common/EnableAdServicesResponse.java
new file mode 100644
index 0000000..ab87b32
--- /dev/null
+++ b/android-35/android/adservices/common/EnableAdServicesResponse.java
@@ -0,0 +1,190 @@
+/*
+ * 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.common;
+
+import static android.adservices.common.AdServicesStatusUtils.StatusCode;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.Objects;
+
+
+/**
+ * Response parcel of the enableAdServices API.
+ *
+ * @hide
+ */
+@SystemApi
+public final class EnableAdServicesResponse implements Parcelable {
+
+ private int mStatusCode;
+
+ private String mErrorMessage;
+
+ private boolean mIsSuccess;
+
+ private boolean mIsApiEnabled;
+
+ private EnableAdServicesResponse(
+ @StatusCode int statusCode,
+ @Nullable String errorMessage,
+ boolean isSuccess,
+ boolean isApiEnabled) {
+ mStatusCode = statusCode;
+ mErrorMessage = errorMessage;
+ mIsSuccess = isSuccess;
+ mIsApiEnabled = isApiEnabled;
+ }
+
+ private EnableAdServicesResponse(@NonNull Parcel in) {
+ mStatusCode = in.readInt();
+ mErrorMessage = in.readString();
+ mIsSuccess = in.readBoolean();
+ mIsApiEnabled = in.readBoolean();
+ }
+
+ /** Returns the response status code. */
+ int getStatusCode() {
+ return mStatusCode;
+ }
+
+ /** Returns whether the enableAdServices API finished successfully. */
+ public boolean isSuccess() {
+ return mIsSuccess;
+ }
+
+ /** Returns whether the enableAdServices API is enabled. */
+ public boolean isApiEnabled() {
+ return mIsApiEnabled;
+ }
+
+ @NonNull
+ public static final Creator<EnableAdServicesResponse> CREATOR =
+ new Parcelable.Creator<EnableAdServicesResponse>() {
+ @Override
+ public EnableAdServicesResponse createFromParcel(@NonNull Parcel in) {
+ Objects.requireNonNull(in);
+ return new EnableAdServicesResponse(in);
+ }
+
+ @Override
+ public EnableAdServicesResponse[] newArray(int size) {
+ return new EnableAdServicesResponse[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** @hide */
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ Objects.requireNonNull(dest);
+
+ dest.writeInt(mStatusCode);
+ dest.writeString(mErrorMessage);
+ dest.writeBoolean(mIsSuccess);
+ dest.writeBoolean(mIsApiEnabled);
+ }
+
+ @Override
+ public String toString() {
+ return "EnableAdServicesResponse{"
+ + "mStatusCode="
+ + mStatusCode
+ + ", mErrorMessage="
+ + mErrorMessage
+ + ", mIsSuccess="
+ + mIsSuccess
+ + ", mIsApiEnabled="
+ + mIsApiEnabled
+ + "'}";
+ }
+
+ /**
+ * Builder for {@link EnableAdServicesResponse} objects.
+ *
+ * @hide
+ */
+ public static final class Builder {
+ @StatusCode
+ private int mStatusCode = AdServicesStatusUtils.STATUS_UNSET;
+
+ @Nullable
+ private String mErrorMessage;
+
+ private boolean mIsSuccess;
+
+ private boolean mIsApiEnabled;
+
+ public Builder() {
+ }
+
+ /** Set the enableAdServices API response status Code. */
+ @NonNull
+ public EnableAdServicesResponse.Builder setStatusCode(
+ @AdServicesStatusUtils.StatusCode int statusCode) {
+ mStatusCode = statusCode;
+ return this;
+ }
+
+ /** Set the error messaged passed by the enableAdServices API. */
+ @NonNull
+ public EnableAdServicesResponse.Builder setErrorMessage(@Nullable String errorMessage) {
+ mErrorMessage = errorMessage;
+ return this;
+ }
+
+ /** Set the isSuccess bit when enableAdServices API finishes successfully. */
+ @NonNull
+ public EnableAdServicesResponse.Builder setSuccess(boolean isSuccess) {
+ mIsSuccess = isSuccess;
+ return this;
+ }
+
+ /** Set the isApiEnabled bit when enableAdServices API is enabled. */
+ @NonNull
+ public EnableAdServicesResponse.Builder setApiEnabled(boolean isApiEnabled) {
+ mIsApiEnabled = isApiEnabled;
+ return this;
+ }
+
+ /**
+ * Builds a {@link EnableAdServicesResponse} instance.
+ *
+ * <p>throws IllegalArgumentException if any of the status code is null or error message is
+ * not set for an unsuccessful status.
+ */
+ @NonNull
+ public EnableAdServicesResponse build() {
+ Preconditions.checkArgument(
+ mStatusCode != AdServicesStatusUtils.STATUS_UNSET,
+ "Status code has not been set!");
+
+ return new EnableAdServicesResponse(
+ mStatusCode, mErrorMessage, mIsSuccess, mIsApiEnabled);
+ }
+ }
+}
diff --git a/android-35/android/adservices/common/FledgeErrorResponse.java b/android-35/android/adservices/common/FledgeErrorResponse.java
new file mode 100644
index 0000000..10274d6
--- /dev/null
+++ b/android-35/android/adservices/common/FledgeErrorResponse.java
@@ -0,0 +1,123 @@
+/*
+ * 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.common;
+
+import android.adservices.common.AdServicesStatusUtils.StatusCode;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.Objects;
+
+/**
+ * Represent a generic response for FLEDGE API's.
+ *
+ * @hide
+ */
+public final class FledgeErrorResponse extends AdServicesResponse {
+
+ private FledgeErrorResponse(@StatusCode int statusCode, @Nullable String errorMessage) {
+ super(statusCode, errorMessage);
+ }
+
+ private FledgeErrorResponse(@NonNull Parcel in) {
+ super(in);
+ }
+
+ @NonNull
+ public static final Creator<FledgeErrorResponse> CREATOR =
+ new Parcelable.Creator<FledgeErrorResponse>() {
+ @Override
+ public FledgeErrorResponse createFromParcel(@NonNull Parcel in) {
+ Objects.requireNonNull(in);
+ return new FledgeErrorResponse(in);
+ }
+
+ @Override
+ public FledgeErrorResponse[] newArray(int size) {
+ return new FledgeErrorResponse[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** @hide */
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ Objects.requireNonNull(dest);
+
+ dest.writeInt(mStatusCode);
+ dest.writeString(mErrorMessage);
+ }
+
+ @Override
+ public String toString() {
+ return "FledgeErrorResponse{"
+ + "mStatusCode="
+ + mStatusCode
+ + ", mErrorMessage='"
+ + mErrorMessage
+ + "'}";
+ }
+
+ /**
+ * Builder for {@link FledgeErrorResponse} objects.
+ *
+ * @hide
+ */
+ public static final class Builder {
+ @StatusCode private int mStatusCode = AdServicesStatusUtils.STATUS_UNSET;
+ @Nullable private String mErrorMessage;
+
+ public Builder() {}
+
+ /** Set the Status Code. */
+ @NonNull
+ public FledgeErrorResponse.Builder setStatusCode(@StatusCode int statusCode) {
+ mStatusCode = statusCode;
+ return this;
+ }
+
+ /** Set the Error Message. */
+ @NonNull
+ public FledgeErrorResponse.Builder setErrorMessage(@Nullable String errorMessage) {
+ mErrorMessage = errorMessage;
+ return this;
+ }
+
+ /**
+ * Builds a {@link FledgeErrorResponse} instance.
+ *
+ * <p>throws IllegalArgumentException if any of the status code is null or error message is
+ * not set for an unsuccessful status
+ */
+ @NonNull
+ public FledgeErrorResponse build() {
+ Preconditions.checkArgument(
+ mStatusCode != AdServicesStatusUtils.STATUS_UNSET,
+ "Status code has not been set!");
+
+ return new FledgeErrorResponse(mStatusCode, mErrorMessage);
+ }
+ }
+}
diff --git a/android-35/android/adservices/common/FrequencyCapFilters.java b/android-35/android/adservices/common/FrequencyCapFilters.java
new file mode 100644
index 0000000..f940cf9
--- /dev/null
+++ b/android-35/android/adservices/common/FrequencyCapFilters.java
@@ -0,0 +1,470 @@
+/*
+ * 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.common;
+
+import android.adservices.adselection.ReportImpressionRequest;
+import android.adservices.adselection.UpdateAdCounterHistogramRequest;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.os.OutcomeReceiver;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/**
+ * A container for the ad filters that are based on frequency caps.
+ *
+ * <p>No more than 20 frequency cap filters may be associated with a single ad.
+ *
+ * <p>Frequency caps filters combine an event type with a list of {@link KeyedFrequencyCap} objects
+ * to define a collection of ad filters. If any of these frequency caps are exceeded for a given ad,
+ * the ad will be removed from the group of ads submitted to a buyer adtech's bidding function.
+ */
+public final class FrequencyCapFilters implements Parcelable {
+ /** @hide */
+ public static final String NUM_FREQUENCY_CAP_FILTERS_EXCEEDED_FORMAT =
+ "FrequencyCapFilters should have no more than %d filters";
+ /** @hide */
+ public static final int MAX_NUM_FREQUENCY_CAP_FILTERS = 20;
+ /** @hide */
+ public static final String FREQUENCY_CAP_FILTERS_NULL_LIST_ERROR_MESSAGE =
+ "FrequencyCapFilters should not set null list of KeyedFrequencyCaps";
+ /** @hide */
+ public static final String FREQUENCY_CAP_FILTERS_NULL_ELEMENT_ERROR_MESSAGE =
+ "FrequencyCapFilters should not contain null KeyedFrequencyCaps";
+
+ /**
+ * Event types which are used to update ad counter histograms, which inform frequency cap
+ * filtering in Protected Audience.
+ *
+ * @hide
+ */
+ @IntDef(
+ prefix = {"AD_EVENT_TYPE_"},
+ value = {
+ AD_EVENT_TYPE_INVALID,
+ AD_EVENT_TYPE_WIN,
+ AD_EVENT_TYPE_IMPRESSION,
+ AD_EVENT_TYPE_VIEW,
+ AD_EVENT_TYPE_CLICK,
+ AD_EVENT_TYPE_MIN,
+ AD_EVENT_TYPE_MAX
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface AdEventType {}
+
+ /** @hide */
+ public static final int AD_EVENT_TYPE_INVALID = -1;
+
+ /**
+ * The WIN ad event type is automatically populated within the Protected Audience service for
+ * any winning ad which is returned from Protected Audience ad selection.
+ *
+ * <p>It should not be used to manually update an ad counter histogram.
+ */
+ public static final int AD_EVENT_TYPE_WIN = 0;
+
+ public static final int AD_EVENT_TYPE_IMPRESSION = 1;
+ public static final int AD_EVENT_TYPE_VIEW = 2;
+ public static final int AD_EVENT_TYPE_CLICK = 3;
+
+ /** @hide */
+ public static final int AD_EVENT_TYPE_MIN = AD_EVENT_TYPE_WIN;
+ /** @hide */
+ public static final int AD_EVENT_TYPE_MAX = AD_EVENT_TYPE_CLICK;
+
+ /** @hide */
+ @VisibleForTesting public static final String WIN_EVENTS_FIELD_NAME = "win";
+ /** @hide */
+ @VisibleForTesting public static final String IMPRESSION_EVENTS_FIELD_NAME = "impression";
+ /** @hide */
+ @VisibleForTesting public static final String VIEW_EVENTS_FIELD_NAME = "view";
+ /** @hide */
+ @VisibleForTesting public static final String CLICK_EVENTS_FIELD_NAME = "click";
+
+ @NonNull private final List<KeyedFrequencyCap> mKeyedFrequencyCapsForWinEvents;
+ @NonNull private final List<KeyedFrequencyCap> mKeyedFrequencyCapsForImpressionEvents;
+ @NonNull private final List<KeyedFrequencyCap> mKeyedFrequencyCapsForViewEvents;
+ @NonNull private final List<KeyedFrequencyCap> mKeyedFrequencyCapsForClickEvents;
+
+ @NonNull
+ public static final Creator<FrequencyCapFilters> CREATOR =
+ new Creator<FrequencyCapFilters>() {
+ @Override
+ public FrequencyCapFilters createFromParcel(@NonNull Parcel in) {
+ Objects.requireNonNull(in);
+ return new FrequencyCapFilters(in);
+ }
+
+ @Override
+ public FrequencyCapFilters[] newArray(int size) {
+ return new FrequencyCapFilters[size];
+ }
+ };
+
+ private FrequencyCapFilters(@NonNull Builder builder) {
+ Objects.requireNonNull(builder);
+
+ mKeyedFrequencyCapsForWinEvents = builder.mKeyedFrequencyCapsForWinEvents;
+ mKeyedFrequencyCapsForImpressionEvents = builder.mKeyedFrequencyCapsForImpressionEvents;
+ mKeyedFrequencyCapsForViewEvents = builder.mKeyedFrequencyCapsForViewEvents;
+ mKeyedFrequencyCapsForClickEvents = builder.mKeyedFrequencyCapsForClickEvents;
+ }
+
+ private FrequencyCapFilters(@NonNull Parcel in) {
+ Objects.requireNonNull(in);
+
+ mKeyedFrequencyCapsForWinEvents = new ArrayList<>();
+ mKeyedFrequencyCapsForImpressionEvents = new ArrayList<>();
+ mKeyedFrequencyCapsForViewEvents = new ArrayList<>();
+ mKeyedFrequencyCapsForClickEvents = new ArrayList<>();
+
+ in.readTypedList(mKeyedFrequencyCapsForWinEvents, KeyedFrequencyCap.CREATOR);
+ in.readTypedList(mKeyedFrequencyCapsForImpressionEvents, KeyedFrequencyCap.CREATOR);
+ in.readTypedList(mKeyedFrequencyCapsForViewEvents, KeyedFrequencyCap.CREATOR);
+ in.readTypedList(mKeyedFrequencyCapsForClickEvents, KeyedFrequencyCap.CREATOR);
+ }
+
+ /**
+ * Gets the list of {@link KeyedFrequencyCap} objects that will filter on the {@link
+ * #AD_EVENT_TYPE_WIN} event type.
+ *
+ * <p>These frequency caps apply to events for ads that were selected as winners in ad
+ * selection. Winning ads are used to automatically increment the associated counter keys on the
+ * win event type.
+ *
+ * <p>Note that the {@link #AD_EVENT_TYPE_WIN} event type cannot be updated manually using the
+ * {@link android.adservices.adselection.AdSelectionManager#updateAdCounterHistogram(
+ * UpdateAdCounterHistogramRequest, Executor, OutcomeReceiver)} API.
+ */
+ @NonNull
+ public List<KeyedFrequencyCap> getKeyedFrequencyCapsForWinEvents() {
+ return mKeyedFrequencyCapsForWinEvents;
+ }
+
+ /**
+ * Gets the list of {@link KeyedFrequencyCap} objects that will filter on the {@link
+ * #AD_EVENT_TYPE_IMPRESSION} event type.
+ *
+ * <p>These frequency caps apply to events which correlate to an impression as interpreted by an
+ * adtech.
+ *
+ * <p>Note that events are not automatically counted when calling {@link
+ * android.adservices.adselection.AdSelectionManager#reportImpression(ReportImpressionRequest,
+ * Executor, OutcomeReceiver)}. Instead, the {@link #AD_EVENT_TYPE_IMPRESSION} event type must
+ * be updated using the {@link
+ * android.adservices.adselection.AdSelectionManager#updateAdCounterHistogram(
+ * UpdateAdCounterHistogramRequest, Executor, OutcomeReceiver)} API.
+ */
+ @NonNull
+ public List<KeyedFrequencyCap> getKeyedFrequencyCapsForImpressionEvents() {
+ return mKeyedFrequencyCapsForImpressionEvents;
+ }
+
+ /**
+ * Gets the list of {@link KeyedFrequencyCap} objects that will filter on the {@link
+ * #AD_EVENT_TYPE_VIEW} event type.
+ *
+ * <p>These frequency caps apply to events which correlate to a view as interpreted by an
+ * adtech. View events are counted when the {@link
+ * android.adservices.adselection.AdSelectionManager#updateAdCounterHistogram(
+ * UpdateAdCounterHistogramRequest, Executor, OutcomeReceiver)} API is invoked with the {@link
+ * #AD_EVENT_TYPE_VIEW} event type.
+ */
+ @NonNull
+ public List<KeyedFrequencyCap> getKeyedFrequencyCapsForViewEvents() {
+ return mKeyedFrequencyCapsForViewEvents;
+ }
+
+ /**
+ * Gets the list of {@link KeyedFrequencyCap} objects that will filter on the {@link
+ * #AD_EVENT_TYPE_CLICK} event type.
+ *
+ * <p>These frequency caps apply to events which correlate to a click as interpreted by an
+ * adtech. Click events are counted when the {@link
+ * android.adservices.adselection.AdSelectionManager#updateAdCounterHistogram(
+ * UpdateAdCounterHistogramRequest, Executor, OutcomeReceiver)} API is invoked with the {@link
+ * #AD_EVENT_TYPE_CLICK} event type.
+ */
+ @NonNull
+ public List<KeyedFrequencyCap> getKeyedFrequencyCapsForClickEvents() {
+ return mKeyedFrequencyCapsForClickEvents;
+ }
+
+ /**
+ * @return The estimated size of this object, in bytes.
+ * @hide
+ */
+ public int getSizeInBytes() {
+ return getSizeInBytesOfFcapList(mKeyedFrequencyCapsForWinEvents)
+ + getSizeInBytesOfFcapList(mKeyedFrequencyCapsForImpressionEvents)
+ + getSizeInBytesOfFcapList(mKeyedFrequencyCapsForViewEvents)
+ + getSizeInBytesOfFcapList(mKeyedFrequencyCapsForClickEvents);
+ }
+
+ private int getSizeInBytesOfFcapList(List<KeyedFrequencyCap> fcaps) {
+ int toReturn = 0;
+ for (final KeyedFrequencyCap fcap : fcaps) {
+ toReturn += fcap.getSizeInBytes();
+ }
+ return toReturn;
+ }
+
+ /**
+ * A JSON serializer.
+ *
+ * @return A JSON serialization of this object.
+ * @hide
+ */
+ public JSONObject toJson() throws JSONException {
+ JSONObject toReturn = new JSONObject();
+ toReturn.put(WIN_EVENTS_FIELD_NAME, fcapSetToJsonArray(mKeyedFrequencyCapsForWinEvents));
+ toReturn.put(
+ IMPRESSION_EVENTS_FIELD_NAME,
+ fcapSetToJsonArray(mKeyedFrequencyCapsForImpressionEvents));
+ toReturn.put(VIEW_EVENTS_FIELD_NAME, fcapSetToJsonArray(mKeyedFrequencyCapsForViewEvents));
+ toReturn.put(
+ CLICK_EVENTS_FIELD_NAME, fcapSetToJsonArray(mKeyedFrequencyCapsForClickEvents));
+ return toReturn;
+ }
+
+ private static JSONArray fcapSetToJsonArray(List<KeyedFrequencyCap> fcapSet)
+ throws JSONException {
+ JSONArray toReturn = new JSONArray();
+ for (KeyedFrequencyCap fcap : fcapSet) {
+ toReturn.put(fcap.toJson());
+ }
+ return toReturn;
+ }
+
+ /**
+ * A JSON de-serializer.
+ *
+ * @param json A JSON representation of an {@link FrequencyCapFilters} object as would be
+ * generated by {@link #toJson()}.
+ * @return An {@link FrequencyCapFilters} object generated from the given JSON.
+ * @hide
+ */
+ public static FrequencyCapFilters fromJson(JSONObject json) throws JSONException {
+ Builder builder = new Builder();
+ if (json.has(WIN_EVENTS_FIELD_NAME)) {
+ builder.setKeyedFrequencyCapsForWinEvents(
+ jsonArrayToFcapList(json.getJSONArray(WIN_EVENTS_FIELD_NAME)));
+ }
+ if (json.has(IMPRESSION_EVENTS_FIELD_NAME)) {
+ builder.setKeyedFrequencyCapsForImpressionEvents(
+ jsonArrayToFcapList(json.getJSONArray(IMPRESSION_EVENTS_FIELD_NAME)));
+ }
+ if (json.has(VIEW_EVENTS_FIELD_NAME)) {
+ builder.setKeyedFrequencyCapsForViewEvents(
+ jsonArrayToFcapList(json.getJSONArray(VIEW_EVENTS_FIELD_NAME)));
+ }
+ if (json.has(CLICK_EVENTS_FIELD_NAME)) {
+ builder.setKeyedFrequencyCapsForClickEvents(
+ jsonArrayToFcapList(json.getJSONArray(CLICK_EVENTS_FIELD_NAME)));
+ }
+ return builder.build();
+ }
+
+ private static List<KeyedFrequencyCap> jsonArrayToFcapList(JSONArray json)
+ throws JSONException {
+ List<KeyedFrequencyCap> toReturn = new ArrayList<>();
+ for (int i = 0; i < json.length(); i++) {
+ toReturn.add(KeyedFrequencyCap.fromJson(json.getJSONObject(i)));
+ }
+ return toReturn;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ Objects.requireNonNull(dest);
+ dest.writeTypedList(mKeyedFrequencyCapsForWinEvents);
+ dest.writeTypedList(mKeyedFrequencyCapsForImpressionEvents);
+ dest.writeTypedList(mKeyedFrequencyCapsForViewEvents);
+ dest.writeTypedList(mKeyedFrequencyCapsForClickEvents);
+ }
+
+ /** @hide */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Checks whether the {@link FrequencyCapFilters} objects contain the same information. */
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof FrequencyCapFilters)) return false;
+ FrequencyCapFilters that = (FrequencyCapFilters) o;
+ return mKeyedFrequencyCapsForWinEvents.equals(that.mKeyedFrequencyCapsForWinEvents)
+ && mKeyedFrequencyCapsForImpressionEvents.equals(
+ that.mKeyedFrequencyCapsForImpressionEvents)
+ && mKeyedFrequencyCapsForViewEvents.equals(that.mKeyedFrequencyCapsForViewEvents)
+ && mKeyedFrequencyCapsForClickEvents.equals(that.mKeyedFrequencyCapsForClickEvents);
+ }
+
+ /** Returns the hash of the {@link FrequencyCapFilters} object's data. */
+ @Override
+ public int hashCode() {
+ return Objects.hash(
+ mKeyedFrequencyCapsForWinEvents,
+ mKeyedFrequencyCapsForImpressionEvents,
+ mKeyedFrequencyCapsForViewEvents,
+ mKeyedFrequencyCapsForClickEvents);
+ }
+
+ @Override
+ public String toString() {
+ return "FrequencyCapFilters{"
+ + "mKeyedFrequencyCapsForWinEvents="
+ + mKeyedFrequencyCapsForWinEvents
+ + ", mKeyedFrequencyCapsForImpressionEvents="
+ + mKeyedFrequencyCapsForImpressionEvents
+ + ", mKeyedFrequencyCapsForViewEvents="
+ + mKeyedFrequencyCapsForViewEvents
+ + ", mKeyedFrequencyCapsForClickEvents="
+ + mKeyedFrequencyCapsForClickEvents
+ + '}';
+ }
+
+ /** Builder for creating {@link FrequencyCapFilters} objects. */
+ public static final class Builder {
+ @NonNull
+ private List<KeyedFrequencyCap> mKeyedFrequencyCapsForWinEvents = new ArrayList<>();
+
+ @NonNull
+ private List<KeyedFrequencyCap> mKeyedFrequencyCapsForImpressionEvents = new ArrayList<>();
+
+ @NonNull
+ private List<KeyedFrequencyCap> mKeyedFrequencyCapsForViewEvents = new ArrayList<>();
+
+ @NonNull
+ private List<KeyedFrequencyCap> mKeyedFrequencyCapsForClickEvents = new ArrayList<>();
+
+ public Builder() {}
+
+ /**
+ * Sets the list of {@link KeyedFrequencyCap} objects that will filter on the {@link
+ * #AD_EVENT_TYPE_WIN} event type.
+ *
+ * <p>See {@link #getKeyedFrequencyCapsForWinEvents()} for more information.
+ */
+ @NonNull
+ public Builder setKeyedFrequencyCapsForWinEvents(
+ @NonNull List<KeyedFrequencyCap> keyedFrequencyCapsForWinEvents) {
+ Objects.requireNonNull(
+ keyedFrequencyCapsForWinEvents, FREQUENCY_CAP_FILTERS_NULL_LIST_ERROR_MESSAGE);
+ Preconditions.checkArgument(
+ !keyedFrequencyCapsForWinEvents.contains(null),
+ FREQUENCY_CAP_FILTERS_NULL_ELEMENT_ERROR_MESSAGE);
+ mKeyedFrequencyCapsForWinEvents = keyedFrequencyCapsForWinEvents;
+ return this;
+ }
+
+ /**
+ * Sets the list of {@link KeyedFrequencyCap} objects that will filter on the {@link
+ * #AD_EVENT_TYPE_IMPRESSION} event type.
+ *
+ * <p>See {@link #getKeyedFrequencyCapsForImpressionEvents()} for more information.
+ */
+ @NonNull
+ public Builder setKeyedFrequencyCapsForImpressionEvents(
+ @NonNull List<KeyedFrequencyCap> keyedFrequencyCapsForImpressionEvents) {
+ Objects.requireNonNull(
+ keyedFrequencyCapsForImpressionEvents,
+ FREQUENCY_CAP_FILTERS_NULL_LIST_ERROR_MESSAGE);
+ Preconditions.checkArgument(
+ !keyedFrequencyCapsForImpressionEvents.contains(null),
+ FREQUENCY_CAP_FILTERS_NULL_ELEMENT_ERROR_MESSAGE);
+ mKeyedFrequencyCapsForImpressionEvents = keyedFrequencyCapsForImpressionEvents;
+ return this;
+ }
+
+ /**
+ * Sets the list of {@link KeyedFrequencyCap} objects that will filter on the {@link
+ * #AD_EVENT_TYPE_VIEW} event type.
+ *
+ * <p>See {@link #getKeyedFrequencyCapsForViewEvents()} for more information.
+ */
+ @NonNull
+ public Builder setKeyedFrequencyCapsForViewEvents(
+ @NonNull List<KeyedFrequencyCap> keyedFrequencyCapsForViewEvents) {
+ Objects.requireNonNull(
+ keyedFrequencyCapsForViewEvents, FREQUENCY_CAP_FILTERS_NULL_LIST_ERROR_MESSAGE);
+ Preconditions.checkArgument(
+ !keyedFrequencyCapsForViewEvents.contains(null),
+ FREQUENCY_CAP_FILTERS_NULL_ELEMENT_ERROR_MESSAGE);
+ mKeyedFrequencyCapsForViewEvents = keyedFrequencyCapsForViewEvents;
+ return this;
+ }
+
+ /**
+ * Sets the list of {@link KeyedFrequencyCap} objects that will filter on the {@link
+ * #AD_EVENT_TYPE_CLICK} event type.
+ *
+ * <p>See {@link #getKeyedFrequencyCapsForClickEvents()} for more information.
+ */
+ @NonNull
+ public Builder setKeyedFrequencyCapsForClickEvents(
+ @NonNull List<KeyedFrequencyCap> keyedFrequencyCapsForClickEvents) {
+ Objects.requireNonNull(
+ keyedFrequencyCapsForClickEvents,
+ FREQUENCY_CAP_FILTERS_NULL_LIST_ERROR_MESSAGE);
+ Preconditions.checkArgument(
+ !keyedFrequencyCapsForClickEvents.contains(null),
+ FREQUENCY_CAP_FILTERS_NULL_ELEMENT_ERROR_MESSAGE);
+ mKeyedFrequencyCapsForClickEvents = keyedFrequencyCapsForClickEvents;
+ return this;
+ }
+
+ /**
+ * Builds and returns a {@link FrequencyCapFilters} instance.
+ *
+ * <p>No more than 20 frequency cap filters may be associated with a single ad. If more
+ * total filters than the limit have been set, an {@link IllegalArgumentException} will be
+ * thrown.
+ */
+ @NonNull
+ public FrequencyCapFilters build() {
+ int numFrequencyCapFilters = 0;
+ numFrequencyCapFilters += mKeyedFrequencyCapsForWinEvents.size();
+ numFrequencyCapFilters += mKeyedFrequencyCapsForImpressionEvents.size();
+ numFrequencyCapFilters += mKeyedFrequencyCapsForViewEvents.size();
+ numFrequencyCapFilters += mKeyedFrequencyCapsForClickEvents.size();
+
+ Preconditions.checkArgument(
+ numFrequencyCapFilters <= MAX_NUM_FREQUENCY_CAP_FILTERS,
+ NUM_FREQUENCY_CAP_FILTERS_EXCEEDED_FORMAT,
+ MAX_NUM_FREQUENCY_CAP_FILTERS);
+
+ return new FrequencyCapFilters(this);
+ }
+ }
+}
diff --git a/android-35/android/adservices/common/GetAdServicesCommonStatesParams.java b/android-35/android/adservices/common/GetAdServicesCommonStatesParams.java
new file mode 100644
index 0000000..51680ab
--- /dev/null
+++ b/android-35/android/adservices/common/GetAdServicesCommonStatesParams.java
@@ -0,0 +1,115 @@
+/*
+ * 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.common;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Represent input params to the getAdservicesCommonStates API.
+ *
+ * @hide
+ */
+public final class GetAdServicesCommonStatesParams implements Parcelable {
+ private final String mSdkPackageName;
+ private final String mAppPackageName;
+
+ private GetAdServicesCommonStatesParams(
+ @Nullable String sdkPackageName, @NonNull String appPackageName) {
+ mSdkPackageName = sdkPackageName;
+ mAppPackageName = appPackageName;
+ }
+
+ private GetAdServicesCommonStatesParams(@NonNull Parcel in) {
+ mSdkPackageName = in.readString();
+ mAppPackageName = in.readString();
+ }
+
+ @NonNull
+ public static final Creator<GetAdServicesCommonStatesParams> CREATOR =
+ new Creator<GetAdServicesCommonStatesParams>() {
+ @Override
+ public GetAdServicesCommonStatesParams createFromParcel(Parcel in) {
+ return new GetAdServicesCommonStatesParams(in);
+ }
+
+ @Override
+ public GetAdServicesCommonStatesParams[] newArray(int size) {
+ return new GetAdServicesCommonStatesParams[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeString(mSdkPackageName);
+ out.writeString(mAppPackageName);
+ }
+
+ /** Get the Sdk Package Name. This is the package name in the Manifest. */
+ @NonNull
+ public String getSdkPackageName() {
+ return mSdkPackageName;
+ }
+
+ /** Get the App PackageName. */
+ @NonNull
+ public String getAppPackageName() {
+ return mAppPackageName;
+ }
+
+ /** Builder for {@link GetAdServicesCommonStatesParams} objects. */
+ public static final class Builder {
+ private String mSdkPackageName;
+ private String mAppPackageName;
+
+ public Builder(String appPackageName, String sdkPackageName) {
+ mAppPackageName = appPackageName;
+ mSdkPackageName = sdkPackageName;
+ }
+
+ /**
+ * Set the Sdk Package Name. When the app calls the AdId API directly without using an SDK,
+ * don't set this field.
+ */
+ public @NonNull Builder setSdkPackageName(@NonNull String sdkPackageName) {
+ mSdkPackageName = sdkPackageName;
+ return this;
+ }
+
+ /** Set the App PackageName. */
+ public @NonNull Builder setAppPackageName(@NonNull String appPackageName) {
+ mAppPackageName = appPackageName;
+ return this;
+ }
+
+ /** Builds a {@link GetAdServicesCommonStatesParams} instance. */
+ public @NonNull GetAdServicesCommonStatesParams build() {
+ if (mAppPackageName == null || mAppPackageName.isEmpty()) {
+ throw new IllegalArgumentException("App PackageName must not be empty or null");
+ }
+
+ return new GetAdServicesCommonStatesParams(mSdkPackageName, mAppPackageName);
+ }
+ }
+}
diff --git a/android-35/android/adservices/common/IsAdServicesEnabledResult.java b/android-35/android/adservices/common/IsAdServicesEnabledResult.java
new file mode 100644
index 0000000..a9d8728
--- /dev/null
+++ b/android-35/android/adservices/common/IsAdServicesEnabledResult.java
@@ -0,0 +1,147 @@
+/*
+ * 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.common;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Result from the isAdServicesEnabled API.
+ *
+ * @hide
+ */
+public final class IsAdServicesEnabledResult implements Parcelable {
+ @Nullable private final String mErrorMessage;
+ private final boolean mAdServicesEnabled;
+
+ private IsAdServicesEnabledResult(@Nullable String errorMessage, @NonNull boolean enabled) {
+ mErrorMessage = errorMessage;
+ mAdServicesEnabled = enabled;
+ }
+
+ private IsAdServicesEnabledResult(@NonNull Parcel in) {
+ mErrorMessage = in.readString();
+ mAdServicesEnabled = in.readBoolean();
+ }
+
+ public static final @NonNull Creator<IsAdServicesEnabledResult> CREATOR =
+ new Creator<IsAdServicesEnabledResult>() {
+ @Override
+ public IsAdServicesEnabledResult createFromParcel(Parcel in) {
+ return new IsAdServicesEnabledResult(in);
+ }
+
+ @Override
+ public IsAdServicesEnabledResult[] newArray(int size) {
+ return new IsAdServicesEnabledResult[size];
+ }
+ };
+
+ /** @hide */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** @hide */
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeString(mErrorMessage);
+ out.writeBoolean(mAdServicesEnabled);
+ }
+
+ /** Returns the error message associated with this result. */
+ @Nullable
+ public String getErrorMessage() {
+ return mErrorMessage;
+ }
+
+ /** Returns the Adservices enabled status. */
+ @NonNull
+ public boolean getAdServicesEnabled() {
+ return mAdServicesEnabled;
+ }
+
+ @Override
+ public String toString() {
+ return "GetAdserviceStatusResult{"
+ + ", mErrorMessage='"
+ + mErrorMessage
+ + ", mAdservicesEnabled="
+ + mAdServicesEnabled
+ + '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+
+ if (!(o instanceof IsAdServicesEnabledResult)) {
+ return false;
+ }
+
+ IsAdServicesEnabledResult that = (IsAdServicesEnabledResult) o;
+
+ return Objects.equals(mErrorMessage, that.mErrorMessage)
+ && mAdServicesEnabled == that.mAdServicesEnabled;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mErrorMessage, mAdServicesEnabled);
+ }
+
+ /**
+ * Builder for {@link IsAdServicesEnabledResult} objects.
+ *
+ * @hide
+ */
+ public static final class Builder {
+ @Nullable private String mErrorMessage;
+ private boolean mAdServicesEnabled;
+
+ public Builder() {}
+
+ /** Set the Error Message. */
+ public @NonNull Builder setErrorMessage(@Nullable String errorMessage) {
+ mErrorMessage = errorMessage;
+ return this;
+ }
+
+ /** Set the list of the returned Status */
+ public @NonNull Builder setAdServicesEnabled(@NonNull boolean adServicesEnabled) {
+ mAdServicesEnabled = adServicesEnabled;
+ return this;
+ }
+
+ /**
+ * Builds a {@link IsAdServicesEnabledResult} instance.
+ *
+ * <p>throws IllegalArgumentException if any of the params are null or there is any mismatch
+ * in the size of ModelVersions and TaxonomyVersions.
+ */
+ public @NonNull IsAdServicesEnabledResult build() {
+ return new IsAdServicesEnabledResult(mErrorMessage, mAdServicesEnabled);
+ }
+ }
+}
diff --git a/android-35/android/adservices/common/KeyedFrequencyCap.java b/android-35/android/adservices/common/KeyedFrequencyCap.java
new file mode 100644
index 0000000..95c42c0
--- /dev/null
+++ b/android-35/android/adservices/common/KeyedFrequencyCap.java
@@ -0,0 +1,295 @@
+/*
+ * 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.common;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.time.Duration;
+import java.util.Objects;
+
+/**
+ * A frequency cap for a specific ad counter key.
+ *
+ * <p>Frequency caps define the maximum rate an event can occur within a given time interval. If the
+ * frequency cap is exceeded, the associated ad will be filtered out of ad selection.
+ */
+public final class KeyedFrequencyCap implements Parcelable {
+ /** @hide */
+ @VisibleForTesting public static final String AD_COUNTER_KEY_FIELD_NAME = "ad_counter_key";
+ /** @hide */
+ @VisibleForTesting public static final String MAX_COUNT_FIELD_NAME = "max_count";
+ /** @hide */
+ @VisibleForTesting public static final String INTERVAL_FIELD_NAME = "interval_in_seconds";
+
+ /** @hide */
+ public static final String MAX_COUNT_NOT_POSITIVE_ERROR_MESSAGE =
+ "KeyedFrequencyCap max count %d must be strictly positive";
+ /** @hide */
+ public static final String INTERVAL_NULL_ERROR_MESSAGE =
+ "KeyedFrequencyCap interval must not be null";
+ /** @hide */
+ public static final String INTERVAL_NOT_POSITIVE_FORMAT =
+ "KeyedFrequencyCap interval %s must be strictly positive";
+ /** @hide */
+ public static final String MAX_INTERVAL_EXCEEDED_FORMAT =
+ "KeyedFrequencyCap interval %s must be no greater than %s";
+ /** @hide */
+ public static final Duration MAX_INTERVAL = Duration.ofDays(100);
+
+ // 4 bytes for the key, 12 bytes for the duration, and 4 for the maxCount
+ private static final int SIZE_OF_FIXED_FIELDS = 20;
+
+ private final int mAdCounterKey;
+ private final int mMaxCount;
+ @NonNull private final Duration mInterval;
+
+ @NonNull
+ public static final Creator<KeyedFrequencyCap> CREATOR =
+ new Creator<KeyedFrequencyCap>() {
+ @Override
+ public KeyedFrequencyCap createFromParcel(@NonNull Parcel in) {
+ Objects.requireNonNull(in);
+ return new KeyedFrequencyCap(in);
+ }
+
+ @Override
+ public KeyedFrequencyCap[] newArray(int size) {
+ return new KeyedFrequencyCap[size];
+ }
+ };
+
+ private KeyedFrequencyCap(@NonNull Builder builder) {
+ Objects.requireNonNull(builder);
+
+ mAdCounterKey = builder.mAdCounterKey;
+ mMaxCount = builder.mMaxCount;
+ mInterval = builder.mInterval;
+ }
+
+ private KeyedFrequencyCap(@NonNull Parcel in) {
+ Objects.requireNonNull(in);
+
+ mAdCounterKey = in.readInt();
+ mMaxCount = in.readInt();
+ mInterval = Duration.ofSeconds(in.readLong());
+ }
+
+ /**
+ * Returns the ad counter key that the frequency cap is applied to.
+ *
+ * <p>The ad counter key is defined by an adtech and is an arbitrary numeric identifier which
+ * defines any criteria which may have previously been counted and persisted on the device. If
+ * the on-device count exceeds the maximum count within a certain time interval, the frequency
+ * cap has been exceeded.
+ */
+ @NonNull
+ public int getAdCounterKey() {
+ return mAdCounterKey;
+ }
+
+ /**
+ * Returns the maximum count of event occurrences allowed within a given time interval.
+ *
+ * <p>If there are more events matching the ad counter key and ad event type counted on the
+ * device within the time interval defined by {@link #getInterval()}, the frequency cap has been
+ * exceeded, and the ad will not be eligible for ad selection.
+ *
+ * <p>For example, an ad that specifies a filter for a max count of two within one hour will not
+ * be eligible for ad selection if the event has been counted two or more times within the hour
+ * preceding the ad selection process.
+ */
+ public int getMaxCount() {
+ return mMaxCount;
+ }
+
+ /**
+ * Returns the interval, as a {@link Duration} which will be truncated to the nearest second,
+ * over which the frequency cap is calculated.
+ *
+ * <p>When this frequency cap is computed, the number of persisted events is counted in the most
+ * recent time interval. If the count of previously occurring matching events for an adtech is
+ * greater than the number returned by {@link #getMaxCount()}, the frequency cap has been
+ * exceeded, and the ad will not be eligible for ad selection.
+ */
+ @NonNull
+ public Duration getInterval() {
+ return mInterval;
+ }
+
+ /**
+ * @return The estimated size of this object, in bytes.
+ * @hide
+ */
+ public int getSizeInBytes() {
+ return SIZE_OF_FIXED_FIELDS;
+ }
+
+ /**
+ * A JSON serializer.
+ *
+ * @return A JSON serialization of this object.
+ * @hide
+ */
+ public JSONObject toJson() throws JSONException {
+ JSONObject toReturn = new JSONObject();
+ toReturn.put(AD_COUNTER_KEY_FIELD_NAME, mAdCounterKey);
+ toReturn.put(MAX_COUNT_FIELD_NAME, mMaxCount);
+ toReturn.put(INTERVAL_FIELD_NAME, mInterval.getSeconds());
+ return toReturn;
+ }
+
+ /**
+ * A JSON de-serializer.
+ *
+ * @param json A JSON representation of an {@link KeyedFrequencyCap} object as would be
+ * generated by {@link #toJson()}.
+ * @return An {@link KeyedFrequencyCap} object generated from the given JSON.
+ * @hide
+ */
+ public static KeyedFrequencyCap fromJson(JSONObject json) throws JSONException {
+ return new Builder(
+ json.getInt(AD_COUNTER_KEY_FIELD_NAME),
+ json.getInt(MAX_COUNT_FIELD_NAME),
+ Duration.ofSeconds(json.getLong(INTERVAL_FIELD_NAME)))
+ .build();
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ Objects.requireNonNull(dest);
+ dest.writeInt(mAdCounterKey);
+ dest.writeInt(mMaxCount);
+ dest.writeLong(mInterval.getSeconds());
+ }
+
+ /** @hide */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Checks whether the {@link KeyedFrequencyCap} objects contain the same information. */
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof KeyedFrequencyCap)) return false;
+ KeyedFrequencyCap that = (KeyedFrequencyCap) o;
+ return mMaxCount == that.mMaxCount
+ && mInterval.equals(that.mInterval)
+ && mAdCounterKey == that.mAdCounterKey;
+ }
+
+ /** Returns the hash of the {@link KeyedFrequencyCap} object's data. */
+ @Override
+ public int hashCode() {
+ return Objects.hash(mAdCounterKey, mMaxCount, mInterval);
+ }
+
+ @Override
+ public String toString() {
+ return "KeyedFrequencyCap{"
+ + "mAdCounterKey="
+ + mAdCounterKey
+ + ", mMaxCount="
+ + mMaxCount
+ + ", mInterval="
+ + mInterval
+ + '}';
+ }
+
+ /** Builder for creating {@link KeyedFrequencyCap} objects. */
+ public static final class Builder {
+ private int mAdCounterKey;
+ private int mMaxCount;
+ @NonNull private Duration mInterval;
+
+ public Builder(int adCounterKey, int maxCount, @NonNull Duration interval) {
+ Preconditions.checkArgument(
+ maxCount > 0, MAX_COUNT_NOT_POSITIVE_ERROR_MESSAGE, maxCount);
+ Objects.requireNonNull(interval, INTERVAL_NULL_ERROR_MESSAGE);
+ Preconditions.checkArgument(
+ interval.getSeconds() > 0, INTERVAL_NOT_POSITIVE_FORMAT, interval);
+ Preconditions.checkArgument(
+ interval.getSeconds() <= MAX_INTERVAL.getSeconds(),
+ MAX_INTERVAL_EXCEEDED_FORMAT,
+ interval,
+ MAX_INTERVAL);
+
+ mAdCounterKey = adCounterKey;
+ mMaxCount = maxCount;
+ mInterval = interval;
+ }
+
+ /**
+ * Sets the ad counter key the frequency cap applies to.
+ *
+ * <p>See {@link #getAdCounterKey()} for more information.
+ */
+ @NonNull
+ public Builder setAdCounterKey(int adCounterKey) {
+ mAdCounterKey = adCounterKey;
+ return this;
+ }
+
+ /**
+ * Sets the maximum count within the time interval for the frequency cap.
+ *
+ * <p>See {@link #getMaxCount()} for more information.
+ */
+ @NonNull
+ public Builder setMaxCount(int maxCount) {
+ Preconditions.checkArgument(
+ maxCount > 0, MAX_COUNT_NOT_POSITIVE_ERROR_MESSAGE, maxCount);
+ mMaxCount = maxCount;
+ return this;
+ }
+
+ /**
+ * Sets the interval, as a {@link Duration} which will be truncated to the nearest second,
+ * over which the frequency cap is calculated.
+ *
+ * <p>See {@link #getInterval()} for more information.
+ */
+ @NonNull
+ public Builder setInterval(@NonNull Duration interval) {
+ Objects.requireNonNull(interval, INTERVAL_NULL_ERROR_MESSAGE);
+ Preconditions.checkArgument(
+ interval.getSeconds() > 0, INTERVAL_NOT_POSITIVE_FORMAT, interval);
+ Preconditions.checkArgument(
+ interval.getSeconds() <= MAX_INTERVAL.getSeconds(),
+ MAX_INTERVAL_EXCEEDED_FORMAT,
+ interval,
+ MAX_INTERVAL);
+ mInterval = interval;
+ return this;
+ }
+
+ /** Builds and returns a {@link KeyedFrequencyCap} instance. */
+ @NonNull
+ public KeyedFrequencyCap build() {
+ return new KeyedFrequencyCap(this);
+ }
+ }
+}
diff --git a/android-35/android/adservices/common/OutcomeReceiverConverter.java b/android-35/android/adservices/common/OutcomeReceiverConverter.java
new file mode 100644
index 0000000..39048c8
--- /dev/null
+++ b/android-35/android/adservices/common/OutcomeReceiverConverter.java
@@ -0,0 +1,64 @@
+/*
+ * 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.common;
+
+import android.annotation.NonNull;
+import android.os.Build;
+import android.os.OutcomeReceiver;
+
+import androidx.annotation.RequiresApi;
+
+/**
+ * Utility class to convert between {@link OutcomeReceiver} and {@link AdServicesOutcomeReceiver}.
+ *
+ * @hide
+ */
+@RequiresApi(Build.VERSION_CODES.S)
+public final class OutcomeReceiverConverter {
+ private OutcomeReceiverConverter() {
+ // Prevent instantiation
+ }
+
+ /**
+ * Converts an instance of {@link OutcomeReceiver} to a custom {@link
+ * AdServicesOutcomeReceiver}.
+ *
+ * @param callback the instance of {@link OutcomeReceiver} to wrap
+ * @return an {@link AdServicesOutcomeReceiver} that wraps the original input
+ * @param <R> the type of Result that the receiver can process
+ * @param <E> the type of Exception that can be handled by the receiver
+ */
+ public static <R, E extends Throwable>
+ AdServicesOutcomeReceiver<R, E> toAdServicesOutcomeReceiver(
+ OutcomeReceiver<R, E> callback) {
+ if (callback == null) {
+ return null;
+ }
+
+ return new AdServicesOutcomeReceiver<R, E>() {
+ @Override
+ public void onResult(R result) {
+ callback.onResult(result);
+ }
+
+ @Override
+ public void onError(@NonNull E error) {
+ callback.onError(error);
+ }
+ };
+ }
+}
diff --git a/android-35/android/adservices/common/SandboxedSdkContextUtils.java b/android-35/android/adservices/common/SandboxedSdkContextUtils.java
new file mode 100644
index 0000000..c138229
--- /dev/null
+++ b/android-35/android/adservices/common/SandboxedSdkContextUtils.java
@@ -0,0 +1,52 @@
+/*
+ * 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.common;
+
+import android.app.sdksandbox.SandboxedSdkContext;
+import android.content.Context;
+import android.os.Build;
+
+/**
+ * Class containing some utility functions used by other methods within AdServices.
+ *
+ * @hide
+ */
+public final class SandboxedSdkContextUtils {
+ private SandboxedSdkContextUtils() {
+ // Intended to be a utility class that should not be instantiated.
+ }
+
+ /**
+ * Checks if the context is an instance of SandboxedSdkContext.
+ *
+ * @param context the object to check and cast to {@link SandboxedSdkContext}
+ * @return the context object cast to {@link SandboxedSdkContext} if it is an instance of {@link
+ * SandboxedSdkContext}, or {@code null} otherwise.
+ */
+ public static SandboxedSdkContext getAsSandboxedSdkContext(Context context) {
+ // TODO(b/266693417): Replace build version check with SdkLevel.isAtLeastT()
+ if (context == null || Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
+ return null; // SandboxedSdkContext is only available in T+
+ }
+
+ if (!(context instanceof SandboxedSdkContext)) {
+ return null;
+ }
+
+ return (SandboxedSdkContext) context;
+ }
+}
diff --git a/android-35/android/adservices/common/UpdateAdIdRequest.java b/android-35/android/adservices/common/UpdateAdIdRequest.java
new file mode 100644
index 0000000..3b9ee5a
--- /dev/null
+++ b/android-35/android/adservices/common/UpdateAdIdRequest.java
@@ -0,0 +1,152 @@
+/*
+ * 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.common;
+
+import android.adservices.adid.AdId;
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.adservices.flags.Flags;
+
+import java.util.Objects;
+
+/**
+ * The request sent from the AdIdProvider to update the AdId in Adservices, when the device updates
+ * the AdId.
+ *
+ * @hide
+ */
+// TODO(b/300445889): Consider using codegen for Parcelable.
+@SystemApi
+@FlaggedApi(Flags.FLAG_AD_ID_CACHE_ENABLED)
+public final class UpdateAdIdRequest implements Parcelable {
+ private final String mAdId;
+ private final boolean mLimitAdTrackingEnabled;
+
+ private UpdateAdIdRequest(String adId, boolean isLimitAdTrackingEnabled) {
+ mAdId = Objects.requireNonNull(adId);
+ mLimitAdTrackingEnabled = isLimitAdTrackingEnabled;
+ }
+
+ private UpdateAdIdRequest(Parcel in) {
+ this(in.readString(), in.readBoolean());
+ }
+
+ @NonNull
+ public static final Creator<UpdateAdIdRequest> CREATOR =
+ new Parcelable.Creator<>() {
+ @Override
+ public UpdateAdIdRequest createFromParcel(@NonNull Parcel in) {
+ Objects.requireNonNull(in);
+ return new UpdateAdIdRequest(in);
+ }
+
+ @Override
+ public UpdateAdIdRequest[] newArray(int size) {
+ return new UpdateAdIdRequest[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** @hide */
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ Objects.requireNonNull(out);
+
+ out.writeString(mAdId);
+ out.writeBoolean(mLimitAdTrackingEnabled);
+ }
+
+ /** Returns the advertising ID associated with this result. */
+ @NonNull
+ public String getAdId() {
+ return mAdId;
+ }
+
+ /**
+ * Returns the Limited Ad Tracking field associated with this result.
+ *
+ * <p>When Limited Ad Tracking is enabled, it implies the user opts out the usage of {@link
+ * AdId}. {@link AdId#ZERO_OUT} will be assigned to the device.
+ */
+ public boolean isLimitAdTrackingEnabled() {
+ return mLimitAdTrackingEnabled;
+ }
+
+ // TODO(b/302682607): Investigate encoding AdId in logcat for related AdId classes.
+ @Override
+ public String toString() {
+ return "UpdateAdIdRequest{"
+ + "mAdId="
+ + mAdId
+ + ", mLimitAdTrackingEnabled="
+ + mLimitAdTrackingEnabled
+ + '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+
+ if (!(o instanceof UpdateAdIdRequest)) {
+ return false;
+ }
+
+ UpdateAdIdRequest that = (UpdateAdIdRequest) o;
+
+ return Objects.equals(mAdId, that.mAdId)
+ && (mLimitAdTrackingEnabled == that.mLimitAdTrackingEnabled);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mAdId, mLimitAdTrackingEnabled);
+ }
+
+ /** Builder for {@link UpdateAdIdRequest} objects. */
+ public static final class Builder {
+ private final String mAdId;
+ private boolean mLimitAdTrackingEnabled;
+
+ public Builder(@NonNull String adId) {
+ mAdId = Objects.requireNonNull(adId);
+ }
+
+ /** Sets the Limited AdTracking enabled field. */
+ @NonNull
+ public UpdateAdIdRequest.Builder setLimitAdTrackingEnabled(
+ boolean isLimitAdTrackingEnabled) {
+ mLimitAdTrackingEnabled = isLimitAdTrackingEnabled;
+ return this;
+ }
+
+ /** Builds a {@link UpdateAdIdRequest} instance. */
+ @NonNull
+ public UpdateAdIdRequest build() {
+ return new UpdateAdIdRequest(mAdId, mLimitAdTrackingEnabled);
+ }
+ }
+}
diff --git a/android-35/android/adservices/customaudience/AddCustomAudienceOverrideRequest.java b/android-35/android/adservices/customaudience/AddCustomAudienceOverrideRequest.java
new file mode 100644
index 0000000..8e9bfa0
--- /dev/null
+++ b/android-35/android/adservices/customaudience/AddCustomAudienceOverrideRequest.java
@@ -0,0 +1,175 @@
+/*
+ * 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.customaudience;
+
+import android.adservices.common.AdSelectionSignals;
+import android.adservices.common.AdTechIdentifier;
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.OutcomeReceiver;
+
+import com.android.adservices.flags.Flags;
+
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/**
+ * This POJO represents the {@link
+ * TestCustomAudienceManager#overrideCustomAudienceRemoteInfo(AddCustomAudienceOverrideRequest,
+ * Executor, OutcomeReceiver)} request.
+ *
+ * <p>It contains fields {@code buyer} and {@code name} which will serve as the identifier for the
+ * override fields, {@code biddingLogicJs} and {@code trustedBiddingSignals}, which are used during
+ * ad selection instead of querying external servers.
+ */
+public class AddCustomAudienceOverrideRequest {
+ @NonNull private final AdTechIdentifier mBuyer;
+ @NonNull private final String mName;
+ @NonNull private final String mBiddingLogicJs;
+ private final long mBiddingLogicJsVersion;
+ @NonNull private final AdSelectionSignals mTrustedBiddingSignals;
+
+ public AddCustomAudienceOverrideRequest(
+ @NonNull AdTechIdentifier buyer,
+ @NonNull String name,
+ @NonNull String biddingLogicJs,
+ @NonNull AdSelectionSignals trustedBiddingSignals) {
+ this(buyer, name, biddingLogicJs, 0L, trustedBiddingSignals);
+ }
+
+ private AddCustomAudienceOverrideRequest(
+ @NonNull AdTechIdentifier buyer,
+ @NonNull String name,
+ @NonNull String biddingLogicJs,
+ long biddingLogicJsVersion,
+ @NonNull AdSelectionSignals trustedBiddingSignals) {
+ mBuyer = buyer;
+ mName = name;
+ mBiddingLogicJs = biddingLogicJs;
+ mBiddingLogicJsVersion = biddingLogicJsVersion;
+ mTrustedBiddingSignals = trustedBiddingSignals;
+ }
+
+ /** @return an {@link AdTechIdentifier} representing the buyer */
+ @NonNull
+ public AdTechIdentifier getBuyer() {
+ return mBuyer;
+ }
+
+ /** @return name of the custom audience being overridden */
+ @NonNull
+ public String getName() {
+ return mName;
+ }
+
+ /** @return the override JavaScript result that should be served during ad selection */
+ @NonNull
+ public String getBiddingLogicJs() {
+ return mBiddingLogicJs;
+ }
+
+ /**
+ * Returns the value to return as version for JavaScript bidding logic.
+ *
+ * <p>Default to be {@code 0L}, which will fall back to use default version(V1 or V2).
+ */
+ @FlaggedApi(Flags.FLAG_FLEDGE_AD_SELECTION_FILTERING_ENABLED)
+ public long getBiddingLogicJsVersion() {
+ return mBiddingLogicJsVersion;
+ }
+
+ /** @return the override trusted bidding signals that should be served during ad selection */
+ @NonNull
+ public AdSelectionSignals getTrustedBiddingSignals() {
+ return mTrustedBiddingSignals;
+ }
+
+ /** Builder for {@link AddCustomAudienceOverrideRequest} objects. */
+ public static final class Builder {
+ @Nullable private AdTechIdentifier mBuyer;
+ @Nullable private String mName;
+ @Nullable private String mBiddingLogicJs;
+ private long mBiddingLogicJsVersion;
+ @Nullable private AdSelectionSignals mTrustedBiddingSignals;
+
+ public Builder() {}
+
+ /** Sets the buyer {@link AdTechIdentifier} for the custom audience. */
+ @NonNull
+ public AddCustomAudienceOverrideRequest.Builder setBuyer(@NonNull AdTechIdentifier buyer) {
+ Objects.requireNonNull(buyer);
+
+ this.mBuyer = buyer;
+ return this;
+ }
+
+ /** Sets the name for the custom audience to be overridden. */
+ @NonNull
+ public AddCustomAudienceOverrideRequest.Builder setName(@NonNull String name) {
+ Objects.requireNonNull(name);
+
+ this.mName = name;
+ return this;
+ }
+
+ /** Sets the trusted bidding signals to be served during ad selection. */
+ @NonNull
+ public AddCustomAudienceOverrideRequest.Builder setTrustedBiddingSignals(
+ @NonNull AdSelectionSignals trustedBiddingSignals) {
+ Objects.requireNonNull(trustedBiddingSignals);
+
+ this.mTrustedBiddingSignals = trustedBiddingSignals;
+ return this;
+ }
+
+ /** Sets the bidding logic JavaScript that should be served during ad selection. */
+ @NonNull
+ public AddCustomAudienceOverrideRequest.Builder setBiddingLogicJs(
+ @NonNull String biddingLogicJs) {
+ Objects.requireNonNull(biddingLogicJs);
+
+ this.mBiddingLogicJs = biddingLogicJs;
+ return this;
+ }
+
+ /**
+ * Sets the bidding logic JavaScript version.
+ *
+ * <p>Default to be {@code 0L}, which will fall back to use default version(V1 or V2).
+ */
+ @FlaggedApi(Flags.FLAG_FLEDGE_AD_SELECTION_FILTERING_ENABLED)
+ @NonNull
+ public AddCustomAudienceOverrideRequest.Builder setBiddingLogicJsVersion(
+ long biddingLogicJsVersion) {
+ this.mBiddingLogicJsVersion = biddingLogicJsVersion;
+ return this;
+ }
+
+ /** Builds a {@link AddCustomAudienceOverrideRequest} instance. */
+ @NonNull
+ public AddCustomAudienceOverrideRequest build() {
+ Objects.requireNonNull(mBuyer);
+ Objects.requireNonNull(mName);
+ Objects.requireNonNull(mBiddingLogicJs);
+ Objects.requireNonNull(mTrustedBiddingSignals);
+
+ return new AddCustomAudienceOverrideRequest(
+ mBuyer, mName, mBiddingLogicJs, mBiddingLogicJsVersion, mTrustedBiddingSignals);
+ }
+ }
+}
diff --git a/android-35/android/adservices/customaudience/CustomAudience.java b/android-35/android/adservices/customaudience/CustomAudience.java
new file mode 100644
index 0000000..baf644e
--- /dev/null
+++ b/android-35/android/adservices/customaudience/CustomAudience.java
@@ -0,0 +1,552 @@
+/*
+ * 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.customaudience;
+
+import android.adservices.adselection.GetAdSelectionDataRequest;
+import android.adservices.common.AdData;
+import android.adservices.common.AdSelectionSignals;
+import android.adservices.common.AdTechIdentifier;
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.Uri;
+import android.os.OutcomeReceiver;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.adservices.AdServicesParcelableUtil;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.time.Instant;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/**
+ * Represents the information necessary for a custom audience to participate in ad selection.
+ *
+ * <p>A custom audience is an abstract grouping of users with similar demonstrated interests. This
+ * class is a collection of some data stored on a device that is necessary to serve advertisements
+ * targeting a single custom audience.
+ */
+public final class CustomAudience implements Parcelable {
+ /** @hide */
+ public static final int FLAG_AUCTION_SERVER_REQUEST_DEFAULT = 0;
+
+ /**
+ * This auction server request flag indicates to the service that ads for this {@link
+ * CustomAudience} can be omitted in the server auction payload.
+ */
+ @FlaggedApi(
+ "com.android.adservices.flags.fledge_custom_audience_auction_server_request_flags_enabled")
+ public static final int FLAG_AUCTION_SERVER_REQUEST_OMIT_ADS = 1 << 0;
+
+ @NonNull private final AdTechIdentifier mBuyer;
+ @NonNull private final String mName;
+ @Nullable private final Instant mActivationTime;
+ @Nullable private final Instant mExpirationTime;
+ @NonNull private final Uri mDailyUpdateUri;
+ @Nullable private final AdSelectionSignals mUserBiddingSignals;
+ @Nullable private final TrustedBiddingData mTrustedBiddingData;
+ @NonNull private final Uri mBiddingLogicUri;
+ @NonNull private final List<AdData> mAds;
+ @AuctionServerRequestFlag private final int mAuctionServerRequestFlags;
+
+ /** @hide */
+ @IntDef(
+ flag = true,
+ prefix = {"FLAG_AUCTION_SERVER_REQUEST"},
+ value = {FLAG_AUCTION_SERVER_REQUEST_DEFAULT, FLAG_AUCTION_SERVER_REQUEST_OMIT_ADS})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface AuctionServerRequestFlag {}
+
+ @NonNull
+ public static final Creator<CustomAudience> CREATOR = new Creator<CustomAudience>() {
+ @Override
+ public CustomAudience createFromParcel(@NonNull Parcel in) {
+ Objects.requireNonNull(in);
+
+ return new CustomAudience(in);
+ }
+
+ @Override
+ public CustomAudience[] newArray(int size) {
+ return new CustomAudience[size];
+ }
+ };
+
+ private CustomAudience(@NonNull CustomAudience.Builder builder) {
+ Objects.requireNonNull(builder);
+
+ mBuyer = builder.mBuyer;
+ mName = builder.mName;
+ mActivationTime = builder.mActivationTime;
+ mExpirationTime = builder.mExpirationTime;
+ mDailyUpdateUri = builder.mDailyUpdateUri;
+ mUserBiddingSignals = builder.mUserBiddingSignals;
+ mTrustedBiddingData = builder.mTrustedBiddingData;
+ mBiddingLogicUri = builder.mBiddingLogicUri;
+ mAds = builder.mAds;
+ mAuctionServerRequestFlags = builder.mAuctionServerRequestFlags;
+ }
+
+ private CustomAudience(@NonNull Parcel in) {
+ Objects.requireNonNull(in);
+
+ mBuyer = AdTechIdentifier.CREATOR.createFromParcel(in);
+ mName = in.readString();
+ mActivationTime =
+ AdServicesParcelableUtil.readNullableFromParcel(
+ in, (sourceParcel) -> Instant.ofEpochMilli(sourceParcel.readLong()));
+ mExpirationTime =
+ AdServicesParcelableUtil.readNullableFromParcel(
+ in, (sourceParcel) -> Instant.ofEpochMilli(sourceParcel.readLong()));
+ mDailyUpdateUri = Uri.CREATOR.createFromParcel(in);
+ mUserBiddingSignals =
+ AdServicesParcelableUtil.readNullableFromParcel(
+ in, AdSelectionSignals.CREATOR::createFromParcel);
+ mTrustedBiddingData =
+ AdServicesParcelableUtil.readNullableFromParcel(
+ in, TrustedBiddingData.CREATOR::createFromParcel);
+ mBiddingLogicUri = Uri.CREATOR.createFromParcel(in);
+ mAds = in.createTypedArrayList(AdData.CREATOR);
+ mAuctionServerRequestFlags = in.readInt();
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ Objects.requireNonNull(dest);
+
+ mBuyer.writeToParcel(dest, flags);
+ dest.writeString(mName);
+ AdServicesParcelableUtil.writeNullableToParcel(
+ dest,
+ mActivationTime,
+ (targetParcel, sourceInstant) ->
+ targetParcel.writeLong(sourceInstant.toEpochMilli()));
+ AdServicesParcelableUtil.writeNullableToParcel(
+ dest,
+ mExpirationTime,
+ (targetParcel, sourceInstant) ->
+ targetParcel.writeLong(sourceInstant.toEpochMilli()));
+ mDailyUpdateUri.writeToParcel(dest, flags);
+ AdServicesParcelableUtil.writeNullableToParcel(
+ dest,
+ mUserBiddingSignals,
+ (targetParcel, sourceSignals) -> sourceSignals.writeToParcel(targetParcel, flags));
+ AdServicesParcelableUtil.writeNullableToParcel(
+ dest,
+ mTrustedBiddingData,
+ (targetParcel, sourceData) -> sourceData.writeToParcel(targetParcel, flags));
+ mBiddingLogicUri.writeToParcel(dest, flags);
+ dest.writeTypedList(mAds);
+ dest.writeInt(mAuctionServerRequestFlags);
+ }
+
+ @Override
+ public String toString() {
+ return "CustomAudience{"
+ + "mBuyer="
+ + mBuyer
+ + ", mName='"
+ + mName
+ + ", mActivationTime="
+ + mActivationTime
+ + ", mExpirationTime="
+ + mExpirationTime
+ + ", mDailyUpdateUri="
+ + mDailyUpdateUri
+ + ", mUserBiddingSignals="
+ + mUserBiddingSignals
+ + ", mTrustedBiddingData="
+ + mTrustedBiddingData
+ + ", mBiddingLogicUri="
+ + mBiddingLogicUri
+ + ", mAds="
+ + mAds
+ + ", mAuctionServerRequestFlags="
+ + mAuctionServerRequestFlags
+ + '}';
+ }
+
+ /** @hide */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * A buyer is identified by a domain in the form "buyerexample.com".
+ *
+ * @return an {@link AdTechIdentifier} containing the custom audience's buyer's domain
+ */
+ @NonNull
+ public AdTechIdentifier getBuyer() {
+ return mBuyer;
+ }
+
+ /**
+ * The custom audience's name is an arbitrary string provided by the owner and buyer on creation
+ * of the {@link CustomAudience} object.
+ *
+ * <p>The overall size of the CA is limited and the size of this field is considered using
+ * {@link String#getBytes()} in {@code UTF-8} encoding.
+ *
+ * @return the String name of the custom audience
+ */
+ @NonNull
+ public String getName() {
+ return mName;
+ }
+
+ /**
+ * On creation of the {@link CustomAudience} object, an optional activation time may be set in
+ * the future, in order to serve a delayed activation. If the field is not set, the {@link
+ * CustomAudience} will be activated at the time of joining.
+ *
+ * <p>For example, a custom audience for lapsed users may not activate until a threshold of
+ * inactivity is reached, at which point the custom audience's ads will participate in the ad
+ * selection process, potentially redirecting lapsed users to the original owner application.
+ *
+ * <p>The maximum delay in activation is 60 days from initial creation.
+ *
+ * <p>If specified, the activation time must be an earlier instant than the expiration time.
+ *
+ * @return the timestamp {@link Instant}, truncated to milliseconds, after which the custom
+ * audience is active
+ */
+ @Nullable
+ public Instant getActivationTime() {
+ return mActivationTime;
+ }
+
+ /**
+ * Once the expiration time has passed, a custom audience is no longer eligible for daily
+ * ad/bidding data updates or participation in the ad selection process. The custom audience
+ * will then be deleted from memory by the next daily update.
+ *
+ * <p>If no expiration time is provided on creation of the {@link CustomAudience}, expiry will
+ * default to 60 days from activation.
+ *
+ * <p>The maximum expiry is 60 days from initial activation.
+ *
+ * @return the timestamp {@link Instant}, truncated to milliseconds, after which the custom
+ * audience should be removed
+ */
+ @Nullable
+ public Instant getExpirationTime() {
+ return mExpirationTime;
+ }
+
+ /**
+ * This URI points to a buyer-operated server that hosts updated bidding data and ads metadata
+ * to be used in the on-device ad selection process. The URI must use HTTPS.
+ *
+ * @return the custom audience's daily update URI
+ */
+ @NonNull
+ public Uri getDailyUpdateUri() {
+ return mDailyUpdateUri;
+ }
+
+ /**
+ * User bidding signals are optionally provided by buyers to be consumed by buyer-provided
+ * JavaScript during ad selection in an isolated execution environment.
+ *
+ * <p>If the user bidding signals are not a valid JSON object that can be consumed by the
+ * buyer's JS, the custom audience will not be eligible for ad selection.
+ *
+ * <p>If not specified, the {@link CustomAudience} will not participate in ad selection until
+ * user bidding signals are provided via the daily update for the custom audience.
+ *
+ * @return an {@link AdSelectionSignals} object representing the user bidding signals for the
+ * custom audience
+ */
+ @Nullable
+ public AdSelectionSignals getUserBiddingSignals() {
+ return mUserBiddingSignals;
+ }
+
+ /**
+ * Trusted bidding data consists of a URI pointing to a trusted server for buyers' bidding data
+ * and a list of keys to query the server with. Note that the keys are arbitrary identifiers
+ * that will only be used to query the trusted server for a buyer's bidding logic during ad
+ * selection.
+ *
+ * <p>If not specified, the {@link CustomAudience} will not participate in ad selection until
+ * trusted bidding data are provided via the daily update for the custom audience.
+ *
+ * @return a {@link TrustedBiddingData} object containing the custom audience's trusted bidding
+ * data
+ */
+ @Nullable
+ public TrustedBiddingData getTrustedBiddingData() {
+ return mTrustedBiddingData;
+ }
+
+ /**
+ * Returns the target URI used to fetch bidding logic when a custom audience participates in the
+ * ad selection process. The URI must use HTTPS.
+ *
+ * @return the URI for fetching buyer bidding logic
+ */
+ @NonNull
+ public Uri getBiddingLogicUri() {
+ return mBiddingLogicUri;
+ }
+
+ /**
+ * This list of {@link AdData} objects is a full and complete list of the ads that will be
+ * served by this {@link CustomAudience} during the ad selection process.
+ *
+ * <p>If not specified, or if an empty list is provided, the {@link CustomAudience} will not
+ * participate in ad selection until a valid list of ads are provided via the daily update for
+ * the custom audience.
+ *
+ * <p>The combined ads size of the CA is limited and the sizes of each ad's string fields are
+ * considered using {@link String#getBytes()} in {@code UTF-8} encoding.
+ *
+ * @return a {@link List} of {@link AdData} objects representing ads currently served by the
+ * custom audience
+ */
+ @NonNull
+ public List<AdData> getAds() {
+ return mAds;
+ }
+
+ /**
+ * Returns the bitfield of auction server request flags. These are flags that influence the
+ * creation of the payload generated by the {@link
+ * android.adservices.adselection.AdSelectionManager#getAdSelectionData(GetAdSelectionDataRequest,
+ * Executor, OutcomeReceiver)} API.
+ *
+ * <p>To create this bitfield, place an {@code |} bitwise operator between each {@link
+ * AuctionServerRequestFlag} to be enabled.
+ */
+ @FlaggedApi(
+ "com.android.adservices.flags.fledge_custom_audience_auction_server_request_flags_enabled")
+ @AuctionServerRequestFlag
+ public int getAuctionServerRequestFlags() {
+ return mAuctionServerRequestFlags;
+ }
+
+ /**
+ * Checks whether two {@link CustomAudience} objects contain the same information.
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof CustomAudience)) return false;
+ CustomAudience that = (CustomAudience) o;
+ return mBuyer.equals(that.mBuyer)
+ && mName.equals(that.mName)
+ && Objects.equals(mActivationTime, that.mActivationTime)
+ && Objects.equals(mExpirationTime, that.mExpirationTime)
+ && mDailyUpdateUri.equals(that.mDailyUpdateUri)
+ && Objects.equals(mUserBiddingSignals, that.mUserBiddingSignals)
+ && Objects.equals(mTrustedBiddingData, that.mTrustedBiddingData)
+ && mBiddingLogicUri.equals(that.mBiddingLogicUri)
+ && mAds.equals(that.mAds)
+ && mAuctionServerRequestFlags == that.mAuctionServerRequestFlags;
+ }
+
+ /**
+ * Returns the hash of the {@link CustomAudience} object's data.
+ */
+ @Override
+ public int hashCode() {
+ return Objects.hash(
+ mBuyer,
+ mName,
+ mActivationTime,
+ mExpirationTime,
+ mDailyUpdateUri,
+ mUserBiddingSignals,
+ mTrustedBiddingData,
+ mBiddingLogicUri,
+ mAds,
+ mAuctionServerRequestFlags);
+ }
+
+ /** Builder for {@link CustomAudience} objects. */
+ public static final class Builder {
+ @Nullable private AdTechIdentifier mBuyer;
+ @Nullable private String mName;
+ @Nullable private Instant mActivationTime;
+ @Nullable private Instant mExpirationTime;
+ @Nullable private Uri mDailyUpdateUri;
+ @Nullable private AdSelectionSignals mUserBiddingSignals;
+ @Nullable private TrustedBiddingData mTrustedBiddingData;
+ @Nullable private Uri mBiddingLogicUri;
+ @Nullable private List<AdData> mAds;
+ @AuctionServerRequestFlag private int mAuctionServerRequestFlags;
+
+ // TODO(b/232883403): We may need to add @NonNUll members as args.
+ public Builder() {
+ }
+
+ /**
+ * Sets the buyer {@link AdTechIdentifier}.
+ *
+ * <p>See {@link #getBuyer()} for more information.
+ */
+ @NonNull
+ public CustomAudience.Builder setBuyer(@NonNull AdTechIdentifier buyer) {
+ Objects.requireNonNull(buyer);
+ mBuyer = buyer;
+ return this;
+ }
+
+ /**
+ * Sets the {@link CustomAudience} object's name.
+ * <p>
+ * See {@link #getName()} for more information.
+ */
+ @NonNull
+ public CustomAudience.Builder setName(@NonNull String name) {
+ Objects.requireNonNull(name);
+ mName = name;
+ return this;
+ }
+
+ /**
+ * Sets the time, truncated to milliseconds, after which the {@link CustomAudience} will
+ * serve ads.
+ *
+ * <p>Set to {@code null} in order for this {@link CustomAudience} to be immediately active
+ * and participate in ad selection.
+ *
+ * <p>See {@link #getActivationTime()} for more information.
+ */
+ @NonNull
+ public CustomAudience.Builder setActivationTime(@Nullable Instant activationTime) {
+ mActivationTime = activationTime;
+ return this;
+ }
+
+ /**
+ * Sets the time, truncated to milliseconds, after which the {@link CustomAudience} should
+ * be removed.
+ * <p>
+ * See {@link #getExpirationTime()} for more information.
+ */
+ @NonNull
+ public CustomAudience.Builder setExpirationTime(@Nullable Instant expirationTime) {
+ mExpirationTime = expirationTime;
+ return this;
+ }
+
+ /**
+ * Sets the daily update URI. The URI must use HTTPS.
+ *
+ * <p>See {@link #getDailyUpdateUri()} for more information.
+ */
+ @NonNull
+ public CustomAudience.Builder setDailyUpdateUri(@NonNull Uri dailyUpdateUri) {
+ Objects.requireNonNull(dailyUpdateUri);
+ mDailyUpdateUri = dailyUpdateUri;
+ return this;
+ }
+
+ /**
+ * Sets the user bidding signals used in the ad selection process.
+ *
+ * <p>See {@link #getUserBiddingSignals()} for more information.
+ */
+ @NonNull
+ public CustomAudience.Builder setUserBiddingSignals(
+ @Nullable AdSelectionSignals userBiddingSignals) {
+ mUserBiddingSignals = userBiddingSignals;
+ return this;
+ }
+
+ /**
+ * Sets the trusted bidding data to be queried and used in the ad selection process.
+ * <p>
+ * See {@link #getTrustedBiddingData()} for more information.
+ */
+ @NonNull
+ public CustomAudience.Builder setTrustedBiddingData(
+ @Nullable TrustedBiddingData trustedBiddingData) {
+ mTrustedBiddingData = trustedBiddingData;
+ return this;
+ }
+
+ /**
+ * Sets the URI to fetch bidding logic from for use in the ad selection process. The URI
+ * must use HTTPS.
+ *
+ * <p>See {@link #getBiddingLogicUri()} for more information.
+ */
+ @NonNull
+ public CustomAudience.Builder setBiddingLogicUri(@NonNull Uri biddingLogicUri) {
+ Objects.requireNonNull(biddingLogicUri);
+ mBiddingLogicUri = biddingLogicUri;
+ return this;
+ }
+
+ /**
+ * Sets the initial remarketing ads served by the custom audience. Will be assigned with an
+ * empty list if not provided.
+ *
+ * <p>See {@link #getAds()} for more information.
+ */
+ @NonNull
+ public CustomAudience.Builder setAds(@Nullable List<AdData> ads) {
+ mAds = ads;
+ return this;
+ }
+
+ /**
+ * Sets the bitfield of auction server request flags.
+ *
+ * <p>See {@link #getAuctionServerRequestFlags()} for more information.
+ */
+ @FlaggedApi(
+ "com.android.adservices.flags.fledge_custom_audience_auction_server_request_flags_enabled")
+ @NonNull
+ public CustomAudience.Builder setAuctionServerRequestFlags(
+ @AuctionServerRequestFlag int auctionServerRequestFlags) {
+ mAuctionServerRequestFlags = auctionServerRequestFlags;
+ return this;
+ }
+
+ /**
+ * Builds an instance of a {@link CustomAudience}.
+ *
+ * @throws NullPointerException if any non-null parameter is null
+ * @throws IllegalArgumentException if the expiration time occurs before activation time
+ * @throws IllegalArgumentException if the expiration time is set before the current time
+ */
+ @NonNull
+ public CustomAudience build() {
+ Objects.requireNonNull(mBuyer, "The buyer has not been provided");
+ Objects.requireNonNull(mName, "The name has not been provided");
+ Objects.requireNonNull(mDailyUpdateUri, "The daily update URI has not been provided");
+ Objects.requireNonNull(mBiddingLogicUri, "The bidding logic URI has not been provided");
+
+ // To pass the API lint, we should not allow null Collection.
+ if (mAds == null) {
+ mAds = List.of();
+ }
+
+ return new CustomAudience(this);
+ }
+ }
+}
diff --git a/android-35/android/adservices/customaudience/CustomAudienceManager.java b/android-35/android/adservices/customaudience/CustomAudienceManager.java
new file mode 100644
index 0000000..54e959e
--- /dev/null
+++ b/android-35/android/adservices/customaudience/CustomAudienceManager.java
@@ -0,0 +1,533 @@
+/*
+ * 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.customaudience;
+
+import static android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE;
+
+import static com.android.adservices.flags.Flags.FLAG_FLEDGE_SCHEDULE_CUSTOM_AUDIENCE_UPDATE_ENABLED;
+
+import android.adservices.common.AdServicesOutcomeReceiver;
+import android.adservices.common.AdServicesStatusUtils;
+import android.adservices.common.AdTechIdentifier;
+import android.adservices.common.FledgeErrorResponse;
+import android.adservices.common.SandboxedSdkContextUtils;
+import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.app.sdksandbox.SandboxedSdkContext;
+import android.content.Context;
+import android.os.Build;
+import android.os.LimitExceededException;
+import android.os.OutcomeReceiver;
+import android.os.RemoteException;
+
+import androidx.annotation.RequiresApi;
+
+import com.android.adservices.AdServicesCommon;
+import com.android.adservices.LoggerFactory;
+import com.android.adservices.ServiceBinder;
+
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/** CustomAudienceManager provides APIs for app and ad-SDKs to join / leave custom audiences. */
+@RequiresApi(Build.VERSION_CODES.S)
+public class CustomAudienceManager {
+ private static final LoggerFactory.Logger sLogger = LoggerFactory.getFledgeLogger();
+ /**
+ * Constant that represents the service name for {@link CustomAudienceManager} to be used in
+ * {@link android.adservices.AdServicesFrameworkInitializer#registerServiceWrappers}
+ *
+ * @hide
+ */
+ public static final String CUSTOM_AUDIENCE_SERVICE = "custom_audience_service";
+
+ @NonNull private Context mContext;
+ @NonNull private ServiceBinder<ICustomAudienceService> mServiceBinder;
+
+ /**
+ * Factory method for creating an instance of CustomAudienceManager.
+ *
+ * @param context The {@link Context} to use
+ * @return A {@link CustomAudienceManager} instance
+ */
+ @NonNull
+ public static CustomAudienceManager get(@NonNull Context context) {
+ // On T+, context.getSystemService() does more than just call constructor.
+ return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
+ ? context.getSystemService(CustomAudienceManager.class)
+ : new CustomAudienceManager(context);
+ }
+
+ /**
+ * Create a service binder CustomAudienceManager
+ *
+ * @hide
+ */
+ public CustomAudienceManager(@NonNull Context context) {
+ Objects.requireNonNull(context);
+
+ // In case the CustomAudienceManager is initiated from inside a sdk_sandbox process the
+ // fields will be immediately rewritten by the initialize method below.
+ initialize(context);
+ }
+
+ /**
+ * Initializes {@link CustomAudienceManager} with the given {@code context}.
+ *
+ * <p>This method is called by the {@link SandboxedSdkContext} to propagate the correct context.
+ * For more information check the javadoc on the {@link
+ * android.app.sdksandbox.SdkSandboxSystemServiceRegistry}.
+ *
+ * @hide
+ * @see android.app.sdksandbox.SdkSandboxSystemServiceRegistry
+ */
+ public CustomAudienceManager initialize(@NonNull Context context) {
+ Objects.requireNonNull(context);
+
+ mContext = context;
+ mServiceBinder =
+ ServiceBinder.getServiceBinder(
+ context,
+ AdServicesCommon.ACTION_CUSTOM_AUDIENCE_SERVICE,
+ ICustomAudienceService.Stub::asInterface);
+ return this;
+ }
+
+ /** Create a service with test-enabling APIs */
+ @NonNull
+ public TestCustomAudienceManager getTestCustomAudienceManager() {
+ return new TestCustomAudienceManager(this, getCallerPackageName());
+ }
+
+ @NonNull
+ ICustomAudienceService getService() {
+ ICustomAudienceService service = mServiceBinder.getService();
+ if (service == null) {
+ throw new IllegalStateException("custom audience service is not available.");
+ }
+ return service;
+ }
+
+ /**
+ * Adds the user to the given {@link CustomAudience}.
+ *
+ * <p>An attempt to register the user for a custom audience with the same combination of {@code
+ * ownerPackageName}, {@code buyer}, and {@code name} will cause the existing custom audience's
+ * information to be overwritten, including the list of ads data.
+ *
+ * <p>Note that the ads list can be completely overwritten by the daily background fetch job.
+ *
+ * <p>This call fails with an {@link SecurityException} if
+ *
+ * <ol>
+ * <li>the {@code ownerPackageName} is not calling app's package name and/or
+ * <li>the buyer is not authorized to use the API.
+ * </ol>
+ *
+ * <p>This call fails with an {@link IllegalArgumentException} if
+ *
+ * <ol>
+ * <li>the storage limit has been exceeded by the calling application and/or
+ * <li>any URI parameters in the {@link CustomAudience} given are not authenticated with the
+ * {@link CustomAudience} buyer.
+ * </ol>
+ *
+ * <p>This call fails with {@link LimitExceededException} if the calling package exceeds the
+ * allowed rate limits and is throttled.
+ *
+ * <p>This call fails with an {@link IllegalStateException} if an internal service error is
+ * encountered.
+ */
+ @RequiresPermission(ACCESS_ADSERVICES_CUSTOM_AUDIENCE)
+ public void joinCustomAudience(
+ @NonNull JoinCustomAudienceRequest joinCustomAudienceRequest,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OutcomeReceiver<Object, Exception> receiver) {
+ Objects.requireNonNull(joinCustomAudienceRequest);
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(receiver);
+
+ final CustomAudience customAudience = joinCustomAudienceRequest.getCustomAudience();
+
+ try {
+ final ICustomAudienceService service = getService();
+
+ service.joinCustomAudience(
+ customAudience,
+ getCallerPackageName(),
+ new ICustomAudienceCallback.Stub() {
+ @Override
+ public void onSuccess() {
+ executor.execute(() -> receiver.onResult(new Object()));
+ }
+
+ @Override
+ public void onFailure(FledgeErrorResponse failureParcel) {
+ executor.execute(
+ () ->
+ receiver.onError(
+ AdServicesStatusUtils.asException(
+ failureParcel)));
+ }
+ });
+ } catch (RemoteException e) {
+ sLogger.e(e, "Exception");
+ receiver.onError(new IllegalStateException("Internal Error!", e));
+ }
+ }
+
+ /**
+ * Adds the user to the {@link CustomAudience} fetched from a {@code fetchUri}.
+ *
+ * <p>An attempt to register the user for a custom audience with the same combination of {@code
+ * ownerPackageName}, {@code buyer}, and {@code name} will cause the existing custom audience's
+ * information to be overwritten, including the list of ads data.
+ *
+ * <p>Note that the ads list can be completely overwritten by the daily background fetch job.
+ *
+ * <p>This call fails with an {@link SecurityException} if
+ *
+ * <ol>
+ * <li>the {@code ownerPackageName} is not calling app's package name and/or
+ * <li>the buyer is not authorized to use the API.
+ * </ol>
+ *
+ * <p>This call fails with an {@link IllegalArgumentException} if
+ *
+ * <ol>
+ * <li>the storage limit has been exceeded by the calling application and/or
+ * <li>any URI parameters in the {@link CustomAudience} given are not authenticated with the
+ * {@link CustomAudience} buyer.
+ * </ol>
+ *
+ * <p>This call fails with {@link LimitExceededException} if the calling package exceeds the
+ * allowed rate limits and is throttled.
+ *
+ * <p>This call fails with an {@link IllegalStateException} if an internal service error is
+ * encountered.
+ */
+ @RequiresPermission(ACCESS_ADSERVICES_CUSTOM_AUDIENCE)
+ public void fetchAndJoinCustomAudience(
+ @NonNull FetchAndJoinCustomAudienceRequest fetchAndJoinCustomAudienceRequest,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OutcomeReceiver<Object, Exception> receiver) {
+ Objects.requireNonNull(fetchAndJoinCustomAudienceRequest);
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(receiver);
+
+ try {
+ final ICustomAudienceService service = getService();
+
+ service.fetchAndJoinCustomAudience(
+ new FetchAndJoinCustomAudienceInput.Builder(
+ fetchAndJoinCustomAudienceRequest.getFetchUri(),
+ getCallerPackageName())
+ .setName(fetchAndJoinCustomAudienceRequest.getName())
+ .setActivationTime(
+ fetchAndJoinCustomAudienceRequest.getActivationTime())
+ .setExpirationTime(
+ fetchAndJoinCustomAudienceRequest.getExpirationTime())
+ .setUserBiddingSignals(
+ fetchAndJoinCustomAudienceRequest.getUserBiddingSignals())
+ .build(),
+ new FetchAndJoinCustomAudienceCallback.Stub() {
+ @Override
+ public void onSuccess() {
+ executor.execute(() -> receiver.onResult(new Object()));
+ }
+
+ @Override
+ public void onFailure(FledgeErrorResponse failureParcel) {
+ executor.execute(
+ () ->
+ receiver.onError(
+ AdServicesStatusUtils.asException(
+ failureParcel)));
+ }
+ });
+ } catch (RemoteException e) {
+ sLogger.e(e, "Exception");
+ receiver.onError(new IllegalStateException("Internal Error!", e));
+ }
+ }
+
+ /**
+ * Attempts to remove a user from a custom audience by deleting any existing {@link
+ * CustomAudience} data, identified by {@code ownerPackageName}, {@code buyer}, and {@code
+ * name}.
+ *
+ * <p>This call fails with an {@link SecurityException} if
+ *
+ * <ol>
+ * <li>the {@code ownerPackageName} is not calling app's package name; and/or
+ * <li>the buyer is not authorized to use the API.
+ * </ol>
+ *
+ * <p>This call fails with {@link LimitExceededException} if the calling package exceeds the
+ * allowed rate limits and is throttled.
+ *
+ * <p>This call does not inform the caller whether the custom audience specified existed in
+ * on-device storage. In other words, it will fail silently when a buyer attempts to leave a
+ * custom audience that was not joined.
+ */
+ @RequiresPermission(ACCESS_ADSERVICES_CUSTOM_AUDIENCE)
+ public void leaveCustomAudience(
+ @NonNull LeaveCustomAudienceRequest leaveCustomAudienceRequest,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OutcomeReceiver<Object, Exception> receiver) {
+ Objects.requireNonNull(leaveCustomAudienceRequest);
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(receiver);
+
+ final AdTechIdentifier buyer = leaveCustomAudienceRequest.getBuyer();
+ final String name = leaveCustomAudienceRequest.getName();
+
+ try {
+ final ICustomAudienceService service = getService();
+
+ service.leaveCustomAudience(
+ getCallerPackageName(),
+ buyer,
+ name,
+ new ICustomAudienceCallback.Stub() {
+ @Override
+ public void onSuccess() {
+ executor.execute(() -> receiver.onResult(new Object()));
+ }
+
+ @Override
+ public void onFailure(FledgeErrorResponse failureParcel) {
+ executor.execute(
+ () ->
+ receiver.onError(
+ AdServicesStatusUtils.asException(
+ failureParcel)));
+ }
+ });
+ } catch (RemoteException e) {
+ sLogger.e(e, "Exception");
+ receiver.onError(new IllegalStateException("Internal Error!", e));
+ }
+ }
+
+ /**
+ * Allows the API caller to schedule a deferred Custom Audience update. For each update the user
+ * will be able to join or leave a set of CustomAudiences.
+ *
+ * <p>This API only guarantees minimum delay to make the update, and does not guarantee a
+ * maximum deadline within which the update request would be made. Scheduled updates could be
+ * batched and queued together to preserve system resources, thus exact delay time is not
+ * guaranteed.
+ *
+ * <p>In order to conserve system resources the API will make and update request only if the
+ * following constraints are satisfied
+ *
+ * <ol>
+ * <li>The device is using an un-metered internet connection
+ * <li>The device battery is not low
+ * <li>The device storage is not low
+ * </ol>
+ *
+ * <p>When the deferred update is triggered the API makes a POST request to the provided
+ * updateUri with the request body containing a JSON of Partial Custom Audience list.
+ *
+ * <p>An example of request body containing list of Partial Custom Audiences would look like:
+ *
+ * <pre>{@code
+ * {
+ * "partial_custom_audience_data": [
+ * {
+ * "name": "running_shoes",
+ * "activation_time": 1644375856883,
+ * "expiration_time": 1644375908397
+ * },
+ * {
+ * "name": "casual_shirt",
+ * "user_bidding_signals": {
+ * "signal1": "value1"
+ * }
+ * }
+ * ]
+ * }
+ * }</pre>
+ *
+ * <p>In response the API expects a JSON in return with following keys:
+ *
+ * <ol>
+ * <li>"join" : Should contain list containing full data for a {@link CustomAudience} object
+ * <li>"leave" : List of {@link CustomAudience} names that user is intended to be removed from
+ * </ol>
+ *
+ * <p>An example of JSON in response would look like:
+ *
+ * <pre>{@code
+ * {
+ * "join": [
+ * {
+ * "name": "running-shoes",
+ * "activation_time": 1680603133,
+ * "expiration_time": 1680803133,
+ * "user_bidding_signals": {
+ * "signal1": "value"
+ * },
+ * "trusted_bidding_data": {
+ * "trusted_bidding_uri": "https://example-dsp.com/",
+ * "trusted_bidding_keys": [
+ * "k1",
+ * "k2"
+ * ]
+ * },
+ * "bidding_logic_uri": "https://example-dsp.com/...",
+ * "ads": [
+ * {
+ * "render_uri": "https://example-dsp.com/...",
+ * "metadata": {},
+ * "ad_filters": {
+ * "frequency_cap": {
+ * "win": [
+ * {
+ * "ad_counter_key": "key1",
+ * "max_count": 2,
+ * "interval_in_seconds": 60
+ * }
+ * ],
+ * "view": [
+ * {
+ * "ad_counter_key": "key2",
+ * "max_count": 10,
+ * "interval_in_seconds": 3600
+ * }
+ * ]
+ * },
+ * "app_install": {
+ * "package_names": [
+ * "package.name.one"
+ * ]
+ * }
+ * }
+ * }
+ * ]
+ * },
+ * {}
+ * ],
+ * "leave": [
+ * "tennis_shoes",
+ * "formal_shirt"
+ * ]
+ * }
+ * }</pre>
+ *
+ * <p>An attempt to register the user for a custom audience from the same application with the
+ * same combination of {@code buyer} inferred from Update Uri, and {@code name} will cause the
+ * existing custom audience's information to be overwritten, including the list of ads data.
+ *
+ * <p>In case information related to any of the CustomAudience to be joined is malformed, the
+ * deferred update would silently ignore that single Custom Audience
+ *
+ * <p>When removing this API attempts to remove a user from a custom audience by deleting any
+ * existing {@link CustomAudience} identified by owner i.e. calling app, {@code buyer} inferred
+ * from Update Uri, and {@code name}
+ *
+ * <p>Any partial custom audience field set by the caller cannot be overridden by the custom
+ * audience fetched from the {@code updateUri}. Given multiple Custom Audiences could be
+ * returned by a DSP we will match the override restriction based on the names of the Custom
+ * Audiences. A DSP may skip returning a full Custom Audience for any Partial Custom Audience in
+ * request.
+ *
+ * <p>In case the API encounters transient errors while making the network call for update, like
+ * 5xx, connection timeout, rate limit exceeded it would employ retries, with backoff up to a
+ * 'retry limit' number of times. The API would also honor 'retry-after' header specifying the
+ * min amount of seconds by which the next request should be delayed.
+ *
+ * <p>In a scenario where server responds with a '429 status code', signifying 'Too many
+ * requests', API would place the deferred update and other updates for the same requester i.e.
+ * caller package and buyer combination, in a quarantine. The quarantine records would be
+ * referred before making any calls for requesters, and request will only be made once the
+ * quarantine period has expired. The applications can leverage the `retry-after` header to
+ * self-quarantine for traffic management to their servers and prevent being overwhelmed with
+ * requests. The default quarantine value will be set to 30 minutes.
+ *
+ * <p>This call fails with an {@link SecurityException} if
+ *
+ * <ol>
+ * <li>the {@code ownerPackageName} is not calling app's package name; and/or
+ * <li>the buyer, inferred from {@code updateUri}, is not authorized to use the API.
+ * </ol>
+ *
+ * <p>This call fails with an {@link IllegalArgumentException} if
+ *
+ * <ol>
+ * <li>the provided {@code updateUri} is invalid or malformed.
+ * <li>the provided {@code delayTime} is not within permissible bounds
+ * <li>the combined size of {@code partialCustomAudience} list is larger than allowed limits
+ * </ol>
+ *
+ * <p>This call fails with {@link LimitExceededException} if the calling package exceeds the
+ * allowed rate limits and is throttled.
+ */
+ @FlaggedApi(FLAG_FLEDGE_SCHEDULE_CUSTOM_AUDIENCE_UPDATE_ENABLED)
+ @RequiresPermission(ACCESS_ADSERVICES_CUSTOM_AUDIENCE)
+ public void scheduleCustomAudienceUpdate(
+ @NonNull ScheduleCustomAudienceUpdateRequest request,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull AdServicesOutcomeReceiver<Object, Exception> receiver) {
+ Objects.requireNonNull(request);
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(receiver);
+
+ try {
+ final ICustomAudienceService service = getService();
+
+ service.scheduleCustomAudienceUpdate(
+ new ScheduleCustomAudienceUpdateInput.Builder(
+ request.getUpdateUri(),
+ getCallerPackageName(),
+ request.getMinDelay(),
+ request.getPartialCustomAudienceList())
+ .build(),
+ new ScheduleCustomAudienceUpdateCallback.Stub() {
+ @Override
+ public void onSuccess() {
+ executor.execute(() -> receiver.onResult(new Object()));
+ }
+
+ @Override
+ public void onFailure(FledgeErrorResponse failureParcel) {
+ executor.execute(
+ () ->
+ receiver.onError(
+ AdServicesStatusUtils.asException(
+ failureParcel)));
+ }
+ });
+
+ } catch (RemoteException e) {
+ sLogger.e(e, "Exception");
+ receiver.onError(new IllegalStateException("Internal Error!", e));
+ }
+ }
+
+ private String getCallerPackageName() {
+ SandboxedSdkContext sandboxedSdkContext =
+ SandboxedSdkContextUtils.getAsSandboxedSdkContext(mContext);
+ return sandboxedSdkContext == null
+ ? mContext.getPackageName()
+ : sandboxedSdkContext.getClientPackageName();
+ }
+}
diff --git a/android-35/android/adservices/customaudience/FetchAndJoinCustomAudienceInput.java b/android-35/android/adservices/customaudience/FetchAndJoinCustomAudienceInput.java
new file mode 100644
index 0000000..466dea3
--- /dev/null
+++ b/android-35/android/adservices/customaudience/FetchAndJoinCustomAudienceInput.java
@@ -0,0 +1,341 @@
+/*
+ * 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.customaudience;
+
+import android.adservices.common.AdSelectionSignals;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.adservices.AdServicesParcelableUtil;
+
+import java.time.Instant;
+import java.util.Objects;
+
+/**
+ * The input object wrapping the required and optional parameters needed to fetch a {@link
+ * CustomAudience}.
+ *
+ * <p>Refer to {@link FetchAndJoinCustomAudienceRequest} for more information about the parameters.
+ *
+ * @hide
+ */
+public final class FetchAndJoinCustomAudienceInput implements Parcelable {
+ @NonNull private final Uri mFetchUri;
+ @NonNull private final String mCallerPackageName;
+ @Nullable private final String mName;
+ @Nullable private final Instant mActivationTime;
+ @Nullable private final Instant mExpirationTime;
+ @Nullable private final AdSelectionSignals mUserBiddingSignals;
+
+ @NonNull
+ public static final Creator<FetchAndJoinCustomAudienceInput> CREATOR =
+ new Creator<FetchAndJoinCustomAudienceInput>() {
+ @NonNull
+ @Override
+ public FetchAndJoinCustomAudienceInput createFromParcel(@NonNull Parcel in) {
+ Objects.requireNonNull(in);
+ return new FetchAndJoinCustomAudienceInput(in);
+ }
+
+ @NonNull
+ @Override
+ public FetchAndJoinCustomAudienceInput[] newArray(int size) {
+ return new FetchAndJoinCustomAudienceInput[size];
+ }
+ };
+
+ private FetchAndJoinCustomAudienceInput(
+ @NonNull FetchAndJoinCustomAudienceInput.Builder builder) {
+ Objects.requireNonNull(builder);
+
+ mFetchUri = builder.mFetchUri;
+ mName = builder.mName;
+ mActivationTime = builder.mActivationTime;
+ mExpirationTime = builder.mExpirationTime;
+ mUserBiddingSignals = builder.mUserBiddingSignals;
+ mCallerPackageName = builder.mCallerPackageName;
+ }
+
+ private FetchAndJoinCustomAudienceInput(@NonNull Parcel in) {
+ Objects.requireNonNull(in);
+
+ mFetchUri = Uri.CREATOR.createFromParcel(in);
+ mName =
+ AdServicesParcelableUtil.readNullableFromParcel(
+ in, (sourceParcel -> in.readString()));
+ mActivationTime =
+ AdServicesParcelableUtil.readNullableFromParcel(
+ in, (sourceParcel) -> Instant.ofEpochMilli(sourceParcel.readLong()));
+ mExpirationTime =
+ AdServicesParcelableUtil.readNullableFromParcel(
+ in, (sourceParcel) -> Instant.ofEpochMilli(sourceParcel.readLong()));
+ mUserBiddingSignals =
+ AdServicesParcelableUtil.readNullableFromParcel(
+ in, AdSelectionSignals.CREATOR::createFromParcel);
+ mCallerPackageName = in.readString();
+ }
+
+ /**
+ * @return the {@link Uri} from which the custom audience is to be fetched.
+ */
+ @NonNull
+ public Uri getFetchUri() {
+ return mFetchUri;
+ }
+
+ /**
+ * Reference {@link CustomAudience#getName()} for details.
+ *
+ * @return the {@link String} name of the custom audience to join.
+ */
+ @Nullable
+ public String getName() {
+ return mName;
+ }
+
+ /**
+ * Reference {@link CustomAudience#getActivationTime()} for details.
+ *
+ * @return the {@link Instant} by which joining the custom audience will be delayed.
+ */
+ @Nullable
+ public Instant getActivationTime() {
+ return mActivationTime;
+ }
+
+ /**
+ * Reference {@link CustomAudience#getExpirationTime()} for details.
+ *
+ * @return the {@link Instant} by when the membership to the custom audience will expire.
+ */
+ @Nullable
+ public Instant getExpirationTime() {
+ return mExpirationTime;
+ }
+
+ /**
+ * Reference {@link CustomAudience#getUserBiddingSignals()} for details.
+ *
+ * @return the buyer signals to be consumed by the buyer-provided JavaScript when the custom
+ * audience participates in an ad selection.
+ */
+ @Nullable
+ public AdSelectionSignals getUserBiddingSignals() {
+ return mUserBiddingSignals;
+ }
+
+ /**
+ * @return the caller app's package name.
+ */
+ @NonNull
+ public String getCallerPackageName() {
+ return mCallerPackageName;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ Objects.requireNonNull(dest);
+
+ mFetchUri.writeToParcel(dest, flags);
+ AdServicesParcelableUtil.writeNullableToParcel(dest, mName, Parcel::writeString);
+ AdServicesParcelableUtil.writeNullableToParcel(
+ dest,
+ mActivationTime,
+ (targetParcel, sourceInstant) ->
+ targetParcel.writeLong(sourceInstant.toEpochMilli()));
+ AdServicesParcelableUtil.writeNullableToParcel(
+ dest,
+ mExpirationTime,
+ (targetParcel, sourceInstant) ->
+ targetParcel.writeLong(sourceInstant.toEpochMilli()));
+ AdServicesParcelableUtil.writeNullableToParcel(
+ dest,
+ mUserBiddingSignals,
+ (targetParcel, sourceSignals) -> sourceSignals.writeToParcel(targetParcel, flags));
+ dest.writeString(mCallerPackageName);
+ }
+
+ /**
+ * @return {@code true} only if two {@link FetchAndJoinCustomAudienceInput} objects contain the
+ * same information.
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof FetchAndJoinCustomAudienceInput)) return false;
+ FetchAndJoinCustomAudienceInput that = (FetchAndJoinCustomAudienceInput) o;
+ return mFetchUri.equals(that.mFetchUri)
+ && mCallerPackageName.equals(that.mCallerPackageName)
+ && Objects.equals(mName, that.mName)
+ && Objects.equals(mActivationTime, that.mActivationTime)
+ && Objects.equals(mExpirationTime, that.mExpirationTime)
+ && Objects.equals(mUserBiddingSignals, that.mUserBiddingSignals);
+ }
+
+ /**
+ * @return the hash of the {@link FetchAndJoinCustomAudienceInput} object's data.
+ */
+ @Override
+ public int hashCode() {
+ return Objects.hash(
+ mFetchUri,
+ mCallerPackageName,
+ mName,
+ mActivationTime,
+ mExpirationTime,
+ mUserBiddingSignals);
+ }
+
+ /**
+ * @return a human-readable representation of {@link FetchAndJoinCustomAudienceInput}.
+ */
+ @Override
+ public String toString() {
+ return "FetchAndJoinCustomAudienceInput{"
+ + "fetchUri="
+ + mFetchUri
+ + ", name="
+ + mName
+ + ", activationTime="
+ + mActivationTime
+ + ", expirationTime="
+ + mExpirationTime
+ + ", userBiddingSignals="
+ + mUserBiddingSignals
+ + ", callerPackageName="
+ + mCallerPackageName
+ + '}';
+ }
+
+ /**
+ * Builder for {@link FetchAndJoinCustomAudienceInput} objects.
+ *
+ * @hide
+ */
+ public static final class Builder {
+ @NonNull private Uri mFetchUri;
+ @NonNull private String mCallerPackageName;
+ @Nullable private String mName;
+ @Nullable private Instant mActivationTime;
+ @Nullable private Instant mExpirationTime;
+ @Nullable private AdSelectionSignals mUserBiddingSignals;
+
+ /**
+ * Instantiates a {@link FetchAndJoinCustomAudienceInput.Builder} with the {@link Uri} from
+ * which the custom audience is to be fetched and the caller app's package name.
+ *
+ * @throws NullPointerException if any non-null parameter is null.
+ */
+ public Builder(@NonNull Uri fetchUri, @NonNull String callerPackageName) {
+ Objects.requireNonNull(fetchUri);
+ Objects.requireNonNull(callerPackageName);
+
+ this.mFetchUri = fetchUri;
+ this.mCallerPackageName = callerPackageName;
+ }
+
+ /**
+ * Sets the {@link Uri} from which the custom audience is to be fetched.
+ *
+ * <p>See {@link #getFetchUri()} ()} for details.
+ */
+ @NonNull
+ public Builder setFetchUri(@NonNull Uri fetchUri) {
+ Objects.requireNonNull(fetchUri);
+ this.mFetchUri = fetchUri;
+ return this;
+ }
+
+ /**
+ * Sets the {@link String} name of the custom audience to join.
+ *
+ * <p>See {@link #getName()} for details.
+ */
+ @NonNull
+ public Builder setName(@Nullable String name) {
+ this.mName = name;
+ return this;
+ }
+
+ /**
+ * Sets the {@link Instant} by which joining the custom audience will be delayed.
+ *
+ * <p>See {@link #getActivationTime()} for details.
+ */
+ @NonNull
+ public Builder setActivationTime(@Nullable Instant activationTime) {
+ this.mActivationTime = activationTime;
+ return this;
+ }
+
+ /**
+ * Sets the {@link Instant} by when the membership to the custom audience will expire.
+ *
+ * <p>See {@link #getExpirationTime()} for details.
+ */
+ @NonNull
+ public Builder setExpirationTime(@Nullable Instant expirationTime) {
+ this.mExpirationTime = expirationTime;
+ return this;
+ }
+
+ /**
+ * Sets the buyer signals to be consumed by the buyer-provided JavaScript when the custom
+ * audience participates in an ad selection.
+ *
+ * <p>See {@link #getUserBiddingSignals()} for details.
+ */
+ @NonNull
+ public Builder setUserBiddingSignals(@Nullable AdSelectionSignals userBiddingSignals) {
+ this.mUserBiddingSignals = userBiddingSignals;
+ return this;
+ }
+
+ /**
+ * Sets the caller app's package name.
+ *
+ * <p>See {@link #getCallerPackageName()} for details.
+ */
+ @NonNull
+ public Builder setCallerPackageName(@NonNull String callerPackageName) {
+ Objects.requireNonNull(callerPackageName);
+ this.mCallerPackageName = callerPackageName;
+ return this;
+ }
+
+ /**
+ * Builds an instance of a {@link FetchAndJoinCustomAudienceInput}.
+ *
+ * @throws NullPointerException if any non-null parameter is null.
+ */
+ @NonNull
+ public FetchAndJoinCustomAudienceInput build() {
+ Objects.requireNonNull(mFetchUri);
+ Objects.requireNonNull(mCallerPackageName);
+
+ return new FetchAndJoinCustomAudienceInput(this);
+ }
+ }
+}
diff --git a/android-35/android/adservices/customaudience/FetchAndJoinCustomAudienceRequest.java b/android-35/android/adservices/customaudience/FetchAndJoinCustomAudienceRequest.java
new file mode 100644
index 0000000..bc97eaa
--- /dev/null
+++ b/android-35/android/adservices/customaudience/FetchAndJoinCustomAudienceRequest.java
@@ -0,0 +1,234 @@
+/*
+ * 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.customaudience;
+
+import android.adservices.common.AdSelectionSignals;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.Uri;
+
+import java.time.Instant;
+import java.util.Objects;
+
+/**
+ * The request object wrapping the required and optional parameters needed to fetch a {@link
+ * CustomAudience}.
+ *
+ * <p>{@code fetchUri} is the only required parameter. It represents the URI to fetch a custom
+ * audience from. {@code name}, {@code activationTime}, {@code expirationTime} and {@code
+ * userBiddingSignals} are optional parameters. They represent a partial custom audience which can
+ * be used by the caller to inform the choice of the custom audience the user should be added to.
+ * Any field set by the caller cannot be overridden by the custom audience fetched from the {@code
+ * fetchUri}. For more information about each field refer to {@link CustomAudience}.
+ */
+public final class FetchAndJoinCustomAudienceRequest {
+ @NonNull private final Uri mFetchUri;
+ @Nullable private final String mName;
+ @Nullable private final Instant mActivationTime;
+ @Nullable private final Instant mExpirationTime;
+ @Nullable private final AdSelectionSignals mUserBiddingSignals;
+
+ private FetchAndJoinCustomAudienceRequest(
+ @NonNull FetchAndJoinCustomAudienceRequest.Builder builder) {
+ Objects.requireNonNull(builder.mFetchUri);
+
+ mFetchUri = builder.mFetchUri;
+ mName = builder.mName;
+ mActivationTime = builder.mActivationTime;
+ mExpirationTime = builder.mExpirationTime;
+ mUserBiddingSignals = builder.mUserBiddingSignals;
+ }
+
+ /**
+ * @return the {@link Uri} from which the custom audience is to be fetched.
+ */
+ @NonNull
+ public Uri getFetchUri() {
+ return mFetchUri;
+ }
+
+ /**
+ * Reference {@link CustomAudience#getName()} for details.
+ *
+ * @return the {@link String} name of the custom audience to join.
+ */
+ @Nullable
+ public String getName() {
+ return mName;
+ }
+
+ /**
+ * Reference {@link CustomAudience#getActivationTime()} for details.
+ *
+ * @return the {@link Instant} by which joining the custom audience will be delayed.
+ */
+ @Nullable
+ public Instant getActivationTime() {
+ return mActivationTime;
+ }
+
+ /**
+ * Reference {@link CustomAudience#getExpirationTime()} for details.
+ *
+ * @return the {@link Instant} by when the membership to the custom audience will expire.
+ */
+ @Nullable
+ public Instant getExpirationTime() {
+ return mExpirationTime;
+ }
+
+ /**
+ * Reference {@link CustomAudience#getUserBiddingSignals()} for details.
+ *
+ * @return the buyer signals to be consumed by the buyer-provided JavaScript when the custom
+ * audience participates in an ad selection.
+ */
+ @Nullable
+ public AdSelectionSignals getUserBiddingSignals() {
+ return mUserBiddingSignals;
+ }
+
+ /**
+ * @return {@code true} only if two {@link FetchAndJoinCustomAudienceRequest} objects contain
+ * the same information.
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof FetchAndJoinCustomAudienceRequest)) return false;
+ FetchAndJoinCustomAudienceRequest that = (FetchAndJoinCustomAudienceRequest) o;
+ return mFetchUri.equals(that.mFetchUri)
+ && Objects.equals(mName, that.mName)
+ && Objects.equals(mActivationTime, that.mActivationTime)
+ && Objects.equals(mExpirationTime, that.mExpirationTime)
+ && Objects.equals(mUserBiddingSignals, that.mUserBiddingSignals);
+ }
+
+ /**
+ * @return the hash of the {@link FetchAndJoinCustomAudienceRequest} object's data.
+ */
+ @Override
+ public int hashCode() {
+ return Objects.hash(
+ mFetchUri, mName, mActivationTime, mExpirationTime, mUserBiddingSignals);
+ }
+
+ /**
+ * @return a human-readable representation of {@link FetchAndJoinCustomAudienceRequest}.
+ */
+ @Override
+ public String toString() {
+ return "FetchAndJoinCustomAudienceRequest{"
+ + "fetchUri="
+ + mFetchUri
+ + ", name="
+ + mName
+ + ", activationTime="
+ + mActivationTime
+ + ", expirationTime="
+ + mExpirationTime
+ + ", userBiddingSignals="
+ + mUserBiddingSignals
+ + '}';
+ }
+
+ /** Builder for {@link FetchAndJoinCustomAudienceRequest} objects. */
+ public static final class Builder {
+ @NonNull private Uri mFetchUri;
+ @Nullable private String mName;
+ @Nullable private Instant mActivationTime;
+ @Nullable private Instant mExpirationTime;
+ @Nullable private AdSelectionSignals mUserBiddingSignals;
+
+ /**
+ * Instantiates a {@link FetchAndJoinCustomAudienceRequest.Builder} with the {@link Uri}
+ * from which the custom audience is to be fetched.
+ */
+ public Builder(@NonNull Uri fetchUri) {
+ Objects.requireNonNull(fetchUri);
+ this.mFetchUri = fetchUri;
+ }
+
+ /**
+ * Sets the {@link Uri} from which the custom audience is to be fetched.
+ *
+ * <p>See {@link #getFetchUri()} ()} for details.
+ */
+ @NonNull
+ public Builder setFetchUri(@NonNull Uri fetchUri) {
+ Objects.requireNonNull(fetchUri);
+ this.mFetchUri = fetchUri;
+ return this;
+ }
+
+ /**
+ * Sets the {@link String} name of the custom audience to join.
+ *
+ * <p>See {@link #getName()} for details.
+ */
+ @NonNull
+ public Builder setName(@Nullable String name) {
+ this.mName = name;
+ return this;
+ }
+
+ /**
+ * Sets the {@link Instant} by which joining the custom audience will be delayed.
+ *
+ * <p>See {@link #getActivationTime()} for details.
+ */
+ @NonNull
+ public Builder setActivationTime(@Nullable Instant activationTime) {
+ this.mActivationTime = activationTime;
+ return this;
+ }
+
+ /**
+ * Sets the {@link Instant} by when the membership to the custom audience will expire.
+ *
+ * <p>See {@link #getExpirationTime()} for details.
+ */
+ @NonNull
+ public Builder setExpirationTime(@Nullable Instant expirationTime) {
+ this.mExpirationTime = expirationTime;
+ return this;
+ }
+
+ /**
+ * Sets the buyer signals to be consumed by the buyer-provided JavaScript when the custom
+ * audience participates in an ad selection.
+ *
+ * <p>See {@link #getUserBiddingSignals()} for details.
+ */
+ @NonNull
+ public Builder setUserBiddingSignals(@Nullable AdSelectionSignals userBiddingSignals) {
+ this.mUserBiddingSignals = userBiddingSignals;
+ return this;
+ }
+
+ /**
+ * Builds an instance of a {@link FetchAndJoinCustomAudienceRequest}.
+ *
+ * @throws NullPointerException if any non-null parameter is null.
+ */
+ @NonNull
+ public FetchAndJoinCustomAudienceRequest build() {
+ Objects.requireNonNull(mFetchUri);
+ return new FetchAndJoinCustomAudienceRequest(this);
+ }
+ }
+}
diff --git a/android-35/android/adservices/customaudience/JoinCustomAudienceRequest.java b/android-35/android/adservices/customaudience/JoinCustomAudienceRequest.java
new file mode 100644
index 0000000..7a5f59c
--- /dev/null
+++ b/android-35/android/adservices/customaudience/JoinCustomAudienceRequest.java
@@ -0,0 +1,94 @@
+/*
+ * 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.customaudience;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.util.Objects;
+
+/**
+ * The request object to join a custom audience.
+ */
+public class JoinCustomAudienceRequest {
+ @NonNull
+ private final CustomAudience mCustomAudience;
+
+ private JoinCustomAudienceRequest(@NonNull JoinCustomAudienceRequest.Builder builder) {
+ mCustomAudience = builder.mCustomAudience;
+ }
+
+ /**
+ * Returns the custom audience to join.
+ */
+ @NonNull
+ public CustomAudience getCustomAudience() {
+ return mCustomAudience;
+ }
+
+ /**
+ * Checks whether two {@link JoinCustomAudienceRequest} objects contain the same information.
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof JoinCustomAudienceRequest)) return false;
+ JoinCustomAudienceRequest that = (JoinCustomAudienceRequest) o;
+ return mCustomAudience.equals(that.mCustomAudience);
+ }
+
+ /**
+ * Returns the hash of the {@link JoinCustomAudienceRequest} object's data.
+ */
+ @Override
+ public int hashCode() {
+ return Objects.hash(mCustomAudience);
+ }
+
+ /** Builder for {@link JoinCustomAudienceRequest} objects. */
+ public static final class Builder {
+ @Nullable private CustomAudience mCustomAudience;
+
+ public Builder() {
+ }
+
+ /**
+ * Sets the custom audience to join.
+ *
+ * <p>See {@link #getCustomAudience()} for more information.
+ */
+ @NonNull
+ public JoinCustomAudienceRequest.Builder setCustomAudience(
+ @NonNull CustomAudience customAudience) {
+ Objects.requireNonNull(customAudience);
+ mCustomAudience = customAudience;
+ return this;
+ }
+
+ /**
+ * Builds an instance of a {@link JoinCustomAudienceRequest}.
+ *
+ * @throws NullPointerException if any non-null parameter is null
+ */
+ @NonNull
+ public JoinCustomAudienceRequest build() {
+ Objects.requireNonNull(mCustomAudience, "The custom audience has not been provided");
+
+ return new JoinCustomAudienceRequest(this);
+ }
+ }
+}
diff --git a/android-35/android/adservices/customaudience/LeaveCustomAudienceRequest.java b/android-35/android/adservices/customaudience/LeaveCustomAudienceRequest.java
new file mode 100644
index 0000000..b7d77ef
--- /dev/null
+++ b/android-35/android/adservices/customaudience/LeaveCustomAudienceRequest.java
@@ -0,0 +1,120 @@
+/*
+ * 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.customaudience;
+
+import android.adservices.common.AdTechIdentifier;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.util.Objects;
+
+/** The request object is used to leave a custom audience. */
+public final class LeaveCustomAudienceRequest {
+ @NonNull private final AdTechIdentifier mBuyer;
+ @NonNull private final String mName;
+
+ private LeaveCustomAudienceRequest(@NonNull LeaveCustomAudienceRequest.Builder builder) {
+ mBuyer = builder.mBuyer;
+ mName = builder.mName;
+ }
+
+ /**
+ * Gets the buyer's {@link AdTechIdentifier}, as identified by a domain in the form
+ * "buyerexample.com".
+ *
+ * @return an {@link AdTechIdentifier} containing the custom audience's buyer's domain
+ */
+ @NonNull
+ public AdTechIdentifier getBuyer() {
+ return mBuyer;
+ }
+
+ /**
+ * Gets the arbitrary string provided by the owner and buyer on creation of the {@link
+ * CustomAudience} object that represents a single custom audience.
+ *
+ * @return the String name of the custom audience
+ */
+ @NonNull
+ public String getName() {
+ return mName;
+ }
+
+ /**
+ * Checks whether two {@link LeaveCustomAudienceRequest} objects contain the same information.
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof LeaveCustomAudienceRequest)) return false;
+ LeaveCustomAudienceRequest that = (LeaveCustomAudienceRequest) o;
+ return mBuyer.equals(that.mBuyer) && mName.equals(that.mName);
+ }
+
+ /**
+ * Returns the hash of the {@link LeaveCustomAudienceRequest} object's data.
+ */
+ @Override
+ public int hashCode() {
+ return Objects.hash(mBuyer, mName);
+ }
+
+ /** Builder for {@link LeaveCustomAudienceRequest} objects. */
+ public static final class Builder {
+ @Nullable private AdTechIdentifier mBuyer;
+ @Nullable private String mName;
+
+ public Builder() {}
+
+ /**
+ * Sets the buyer {@link AdTechIdentifier}.
+ *
+ * <p>See {@link #getBuyer()} for more information.
+ */
+ @NonNull
+ public LeaveCustomAudienceRequest.Builder setBuyer(@NonNull AdTechIdentifier buyer) {
+ Objects.requireNonNull(buyer);
+ mBuyer = buyer;
+ return this;
+ }
+
+ /**
+ * Sets the {@link CustomAudience} object's name.
+ * <p>
+ * See {@link #getName()} for more information.
+ */
+ @NonNull
+ public LeaveCustomAudienceRequest.Builder setName(@NonNull String name) {
+ Objects.requireNonNull(name);
+ mName = name;
+ return this;
+ }
+
+ /**
+ * Builds an instance of a {@link LeaveCustomAudienceRequest}.
+ *
+ * @throws NullPointerException if any non-null parameter is null
+ */
+ @NonNull
+ public LeaveCustomAudienceRequest build() {
+ Objects.requireNonNull(mBuyer);
+ Objects.requireNonNull(mName);
+
+ return new LeaveCustomAudienceRequest(this);
+ }
+ }
+}
diff --git a/android-35/android/adservices/customaudience/PartialCustomAudience.java b/android-35/android/adservices/customaudience/PartialCustomAudience.java
new file mode 100644
index 0000000..36c194b
--- /dev/null
+++ b/android-35/android/adservices/customaudience/PartialCustomAudience.java
@@ -0,0 +1,265 @@
+/*
+ * 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.customaudience;
+
+import static com.android.adservices.flags.Flags.FLAG_FLEDGE_SCHEDULE_CUSTOM_AUDIENCE_UPDATE_ENABLED;
+
+import android.adservices.common.AdSelectionSignals;
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.adservices.AdServicesParcelableUtil;
+
+import java.time.Instant;
+import java.util.Objects;
+
+/**
+ * Represents a partial custom audience that is passed along to DSP, when scheduling a delayed
+ * update for Custom Audience. Any field set by the caller cannot be overridden by the custom
+ * audience fetched from the {@code updateUri}
+ *
+ * <p>Given multiple Custom Audiences could be returned by DSP we will match the override
+ * restriction based on the name of Custom Audience. Thus name would be a required field.
+ *
+ * <p>Other nullable fields will not be overridden if left null
+ *
+ * <p>For more information about each field refer to {@link CustomAudience}.
+ */
+@FlaggedApi(FLAG_FLEDGE_SCHEDULE_CUSTOM_AUDIENCE_UPDATE_ENABLED)
+public final class PartialCustomAudience implements Parcelable {
+ @NonNull private final String mName;
+ @Nullable private final Instant mActivationTime;
+ @Nullable private final Instant mExpirationTime;
+ @Nullable private final AdSelectionSignals mUserBiddingSignals;
+
+ private PartialCustomAudience(@NonNull PartialCustomAudience.Builder builder) {
+ Objects.requireNonNull(builder);
+
+ mName = builder.mName;
+ mActivationTime = builder.mActivationTime;
+ mExpirationTime = builder.mExpirationTime;
+ mUserBiddingSignals = builder.mUserBiddingSignals;
+ }
+
+ @NonNull
+ public static final Creator<PartialCustomAudience> CREATOR =
+ new Creator<PartialCustomAudience>() {
+ @NonNull
+ @Override
+ public PartialCustomAudience createFromParcel(@NonNull Parcel in) {
+ Objects.requireNonNull(in);
+ return new PartialCustomAudience(in);
+ }
+
+ @NonNull
+ @Override
+ public PartialCustomAudience[] newArray(int size) {
+ return new PartialCustomAudience[size];
+ }
+ };
+
+ private PartialCustomAudience(@NonNull Parcel in) {
+ Objects.requireNonNull(in);
+
+ mName = in.readString();
+ mActivationTime =
+ AdServicesParcelableUtil.readNullableFromParcel(
+ in, (sourceParcel) -> Instant.ofEpochMilli(sourceParcel.readLong()));
+ mExpirationTime =
+ AdServicesParcelableUtil.readNullableFromParcel(
+ in, (sourceParcel) -> Instant.ofEpochMilli(sourceParcel.readLong()));
+ mUserBiddingSignals =
+ AdServicesParcelableUtil.readNullableFromParcel(
+ in, AdSelectionSignals.CREATOR::createFromParcel);
+ }
+
+ /**
+ * Reference {@link CustomAudience#getName()} for details.
+ *
+ * @return the {@link String} name of the custom audience to join.
+ */
+ @NonNull
+ public String getName() {
+ return mName;
+ }
+
+ /**
+ * Reference {@link CustomAudience#getActivationTime()} for details. Will not be overridden if
+ * left null.
+ *
+ * @return the {@link Instant} by which joining the custom audience will be delayed.
+ */
+ @Nullable
+ public Instant getActivationTime() {
+ return mActivationTime;
+ }
+
+ /**
+ * Reference {@link CustomAudience#getExpirationTime()} for details. Will not be overridden if
+ * left null.
+ *
+ * @return the {@link Instant} by when the membership to the custom audience will expire.
+ */
+ @Nullable
+ public Instant getExpirationTime() {
+ return mExpirationTime;
+ }
+
+ /**
+ * Reference {@link CustomAudience#getUserBiddingSignals()} for details. Will not be overridden
+ * if left null.
+ *
+ * @return the buyer signals to be consumed by the buyer-provided JavaScript when the custom
+ * audience participates in an ad selection.
+ */
+ @Nullable
+ public AdSelectionSignals getUserBiddingSignals() {
+ return mUserBiddingSignals;
+ }
+
+ /**
+ * @return the hash of the {@link PartialCustomAudience} object's data.
+ */
+ @Override
+ public int hashCode() {
+ return Objects.hash(mName, mActivationTime, mExpirationTime, mUserBiddingSignals);
+ }
+
+ /**
+ * @return {@code true} only if two {@link PartialCustomAudience} objects contain the same
+ * information.
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof PartialCustomAudience)) return false;
+ PartialCustomAudience that = (PartialCustomAudience) o;
+ return Objects.equals(mName, that.mName)
+ && Objects.equals(mActivationTime, that.mActivationTime)
+ && Objects.equals(mExpirationTime, that.mExpirationTime)
+ && Objects.equals(mUserBiddingSignals, that.mUserBiddingSignals);
+ }
+
+ /**
+ * @return a human-readable representation of {@link PartialCustomAudience}.
+ */
+ @Override
+ public String toString() {
+ return "PartialCustomAudience {"
+ + "name="
+ + mName
+ + ", activationTime="
+ + mActivationTime
+ + ", expirationTime="
+ + mExpirationTime
+ + ", userBiddingSignals="
+ + mUserBiddingSignals
+ + '}';
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ Objects.requireNonNull(dest);
+
+ dest.writeString(mName);
+ AdServicesParcelableUtil.writeNullableToParcel(
+ dest,
+ mActivationTime,
+ (targetParcel, sourceInstant) ->
+ targetParcel.writeLong(sourceInstant.toEpochMilli()));
+ AdServicesParcelableUtil.writeNullableToParcel(
+ dest,
+ mExpirationTime,
+ (targetParcel, sourceInstant) ->
+ targetParcel.writeLong(sourceInstant.toEpochMilli()));
+ AdServicesParcelableUtil.writeNullableToParcel(
+ dest,
+ mUserBiddingSignals,
+ (targetParcel, sourceSignals) -> sourceSignals.writeToParcel(targetParcel, flags));
+ }
+
+ /** Builder for {@link PartialCustomAudience} objects. */
+ public static final class Builder {
+ @NonNull private String mName;
+ @Nullable private Instant mActivationTime;
+ @Nullable private Instant mExpirationTime;
+ @Nullable private AdSelectionSignals mUserBiddingSignals;
+
+ /**
+ * Instantiates a {@link PartialCustomAudience.Builder} with a {@link String} name for which
+ * this Partial Custom Audience will be updated
+ */
+ public Builder(@NonNull String name) {
+ Objects.requireNonNull(name);
+ this.mName = name;
+ }
+
+ /**
+ * Sets the {@link Instant} by which joining the custom audience will be delayed.
+ *
+ * <p>See {@link #getActivationTime()} for details.
+ */
+ @NonNull
+ public PartialCustomAudience.Builder setActivationTime(@Nullable Instant activationTime) {
+ this.mActivationTime = activationTime;
+ return this;
+ }
+
+ /**
+ * Sets the {@link Instant} by when the membership to the custom audience will expire.
+ *
+ * <p>See {@link #getExpirationTime()} for details.
+ */
+ @NonNull
+ public PartialCustomAudience.Builder setExpirationTime(@Nullable Instant expirationTime) {
+ this.mExpirationTime = expirationTime;
+ return this;
+ }
+
+ /**
+ * Sets the buyer signals to be consumed by the buyer-provided JavaScript when the custom
+ * audience participates in an ad selection.
+ *
+ * <p>See {@link #getUserBiddingSignals()} for details.
+ */
+ @NonNull
+ public PartialCustomAudience.Builder setUserBiddingSignals(
+ @Nullable AdSelectionSignals userBiddingSignals) {
+ this.mUserBiddingSignals = userBiddingSignals;
+ return this;
+ }
+
+ /**
+ * Builds an instance of a {@link FetchAndJoinCustomAudienceRequest}.
+ *
+ * @throws NullPointerException if any non-null parameter is null.
+ */
+ @NonNull
+ public PartialCustomAudience build() {
+ Objects.requireNonNull(mName);
+ return new PartialCustomAudience(this);
+ }
+ }
+}
diff --git a/android-35/android/adservices/customaudience/RemoveCustomAudienceOverrideRequest.java b/android-35/android/adservices/customaudience/RemoveCustomAudienceOverrideRequest.java
new file mode 100644
index 0000000..2996129
--- /dev/null
+++ b/android-35/android/adservices/customaudience/RemoveCustomAudienceOverrideRequest.java
@@ -0,0 +1,92 @@
+/*
+ * 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.customaudience;
+
+import android.adservices.common.AdTechIdentifier;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.OutcomeReceiver;
+
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/**
+ * This POJO represents the {@link TestCustomAudienceManager#removeCustomAudienceRemoteInfoOverride(
+ * RemoveCustomAudienceOverrideRequest, Executor, OutcomeReceiver)} request.
+ *
+ * <p>It contains fields {@code buyer} and {@code name} which will serve as the identifier for the
+ * overrides to be removed.
+ */
+public class RemoveCustomAudienceOverrideRequest {
+ @NonNull private final AdTechIdentifier mBuyer;
+ @NonNull private final String mName;
+
+ public RemoveCustomAudienceOverrideRequest(
+ @NonNull AdTechIdentifier buyer,
+ @NonNull String name) {
+ mBuyer = buyer;
+ mName = name;
+ }
+
+ /** @return an {@link AdTechIdentifier} representing the buyer */
+ @NonNull
+ public AdTechIdentifier getBuyer() {
+ return mBuyer;
+ }
+
+ /** @return name of the custom audience being overridden */
+ @NonNull
+ public String getName() {
+ return mName;
+ }
+
+ /** Builder for {@link RemoveCustomAudienceOverrideRequest} objects. */
+ public static final class Builder {
+ @Nullable private AdTechIdentifier mBuyer;
+ @Nullable private String mName;
+
+ public Builder() {}
+
+ /** Sets the buyer {@link AdTechIdentifier} for the custom audience. */
+ @NonNull
+ public RemoveCustomAudienceOverrideRequest.Builder setBuyer(
+ @NonNull AdTechIdentifier buyer) {
+ Objects.requireNonNull(buyer);
+
+ this.mBuyer = buyer;
+ return this;
+ }
+
+ /** Sets the name for the custom audience that was overridden. */
+ @NonNull
+ public RemoveCustomAudienceOverrideRequest.Builder setName(@NonNull String name) {
+ Objects.requireNonNull(name);
+
+ this.mName = name;
+ return this;
+ }
+
+ /** Builds a {@link RemoveCustomAudienceOverrideRequest} instance. */
+ @NonNull
+ public RemoveCustomAudienceOverrideRequest build() {
+ Objects.requireNonNull(mBuyer);
+ Objects.requireNonNull(mName);
+
+ return new RemoveCustomAudienceOverrideRequest(mBuyer, mName);
+ }
+ }
+}
diff --git a/android-35/android/adservices/customaudience/ScheduleCustomAudienceUpdateInput.java b/android-35/android/adservices/customaudience/ScheduleCustomAudienceUpdateInput.java
new file mode 100644
index 0000000..50714fb
--- /dev/null
+++ b/android-35/android/adservices/customaudience/ScheduleCustomAudienceUpdateInput.java
@@ -0,0 +1,254 @@
+/*
+ * 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.customaudience;
+
+import android.annotation.NonNull;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.time.Duration;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Allows AdTechs to provide an Update Uri, and the minimum Delay Time to schedule the update.
+ *
+ * <p>Refer to {@link ScheduleCustomAudienceUpdateRequest} for more information about the
+ * parameters.
+ *
+ * @hide
+ */
+public final class ScheduleCustomAudienceUpdateInput implements Parcelable {
+ @NonNull private final Uri mUpdateUri;
+ @NonNull private final String mCallerPackageName;
+ @NonNull private final Duration mMinDelay;
+ @NonNull private final List<PartialCustomAudience> mPartialCustomAudienceList;
+
+ @NonNull
+ public static final Creator<ScheduleCustomAudienceUpdateInput> CREATOR =
+ new Creator<ScheduleCustomAudienceUpdateInput>() {
+ @NonNull
+ @Override
+ public ScheduleCustomAudienceUpdateInput createFromParcel(@NonNull Parcel in) {
+ Objects.requireNonNull(in);
+ return new ScheduleCustomAudienceUpdateInput(in);
+ }
+
+ @NonNull
+ @Override
+ public ScheduleCustomAudienceUpdateInput[] newArray(int size) {
+ return new ScheduleCustomAudienceUpdateInput[size];
+ }
+ };
+
+ private ScheduleCustomAudienceUpdateInput(
+ @NonNull ScheduleCustomAudienceUpdateInput.Builder builder) {
+ Objects.requireNonNull(builder);
+
+ mUpdateUri = builder.mUpdateUri;
+ mCallerPackageName = builder.mCallerPackageName;
+ mMinDelay = builder.mMinDelay;
+ mPartialCustomAudienceList = builder.mPartialCustomAudienceList;
+ }
+
+ private ScheduleCustomAudienceUpdateInput(@NonNull Parcel in) {
+ Objects.requireNonNull(in);
+
+ mUpdateUri = Uri.CREATOR.createFromParcel(in);
+ mCallerPackageName = in.readString();
+ mMinDelay = Duration.ofMillis(in.readLong());
+ mPartialCustomAudienceList = in.createTypedArrayList(PartialCustomAudience.CREATOR);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ Objects.requireNonNull(dest);
+
+ mUpdateUri.writeToParcel(dest, flags);
+ dest.writeString(mCallerPackageName);
+ dest.writeLong(mMinDelay.toMillis());
+ dest.writeTypedList(mPartialCustomAudienceList);
+ }
+
+ /** Returns the {@link Uri} from which the Custom Audience is to be fetched */
+ @NonNull
+ public Uri getUpdateUri() {
+ return mUpdateUri;
+ }
+
+ /** Returns the caller app's package name. */
+ @NonNull
+ public String getCallerPackageName() {
+ return mCallerPackageName;
+ }
+
+ /** Returns the {@link Duration} min time duration for which the update is deferred */
+ @NonNull
+ public Duration getMinDelay() {
+ return mMinDelay;
+ }
+
+ /**
+ * Returns the list of {@link PartialCustomAudience} which are sent along with the request to
+ * download the update for Custom Audience
+ */
+ @NonNull
+ public List<PartialCustomAudience> getPartialCustomAudienceList() {
+ return mPartialCustomAudienceList;
+ }
+
+ /** Returns the hash of {@link ScheduleCustomAudienceUpdateInput} object's data. */
+ @Override
+ public int hashCode() {
+ return Objects.hash(mUpdateUri, mCallerPackageName, mMinDelay, mPartialCustomAudienceList);
+ }
+
+ /**
+ * @return {@code true} only if two {@link ScheduleCustomAudienceUpdateInput} objects contain
+ * the same information.
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof ScheduleCustomAudienceUpdateInput)) return false;
+ ScheduleCustomAudienceUpdateInput that = (ScheduleCustomAudienceUpdateInput) o;
+ return mUpdateUri.equals(that.mUpdateUri)
+ && mCallerPackageName.equals(that.mCallerPackageName)
+ && mMinDelay.equals(that.mMinDelay)
+ && Objects.equals(mPartialCustomAudienceList, that.mPartialCustomAudienceList);
+ }
+
+ /**
+ * @return a human-readable representation of {@link ScheduleCustomAudienceUpdateInput}.
+ */
+ @Override
+ public String toString() {
+ return "ScheduleCustomAudienceUpdateInput {"
+ + "updateUri="
+ + mUpdateUri
+ + ", callerPackageName="
+ + mCallerPackageName
+ + ", delayTimeMinutes="
+ + mMinDelay.toMinutes()
+ + ", partialCustomAudienceList="
+ + mPartialCustomAudienceList
+ + '}';
+ }
+
+ /** Builder for {@link ScheduleCustomAudienceUpdateInput} objects. */
+ public static final class Builder {
+ @NonNull private Uri mUpdateUri;
+ @NonNull private Duration mMinDelay;
+ @NonNull private String mCallerPackageName;
+ @NonNull private List<PartialCustomAudience> mPartialCustomAudienceList;
+
+ /**
+ * Instantiates a {@link ScheduleCustomAudienceUpdateInput.Builder} with the following
+ *
+ * @param updateUri from which the update for Custom Audience is to be fetched
+ * @param callerPackageName the caller app's package name
+ * @param minDelay minimum delay time duration for which the update is to be deferred
+ * @param partialCustomAudienceList list of partial Custom Audiences that are overridden by
+ * MMP on update
+ */
+ public Builder(
+ @NonNull Uri updateUri,
+ @NonNull String callerPackageName,
+ @NonNull Duration minDelay,
+ @NonNull List<PartialCustomAudience> partialCustomAudienceList) {
+ Objects.requireNonNull(updateUri);
+ Objects.requireNonNull(callerPackageName);
+ Objects.requireNonNull(minDelay);
+ Objects.requireNonNull(partialCustomAudienceList);
+
+ mUpdateUri = updateUri;
+ mCallerPackageName = callerPackageName;
+ mMinDelay = minDelay;
+ mPartialCustomAudienceList = partialCustomAudienceList;
+ }
+
+ /**
+ * Sets the {@link Uri} from which the update for Custom Audience is to be fetched
+ *
+ * <p>See {@link #getUpdateUri()} for details
+ */
+ @NonNull
+ public Builder setUpdateUri(@NonNull Uri updateUri) {
+ Objects.requireNonNull(updateUri);
+ this.mUpdateUri = updateUri;
+ return this;
+ }
+
+ /**
+ * Sets the caller app's package name.
+ *
+ * <p>See {@link #getCallerPackageName()} for details.
+ */
+ @NonNull
+ public Builder setCallerPackageName(@NonNull String callerPackageName) {
+ Objects.requireNonNull(callerPackageName);
+ this.mCallerPackageName = callerPackageName;
+ return this;
+ }
+
+ /**
+ * Sets the {@link Duration} , min time for which the update is to be deferred
+ *
+ * <p>See {@link #getMinDelay()} for more details
+ */
+ @NonNull
+ public Builder setMinDelay(@NonNull Duration minDelay) {
+ Objects.requireNonNull(minDelay);
+ this.mMinDelay = minDelay;
+ return this;
+ }
+
+ /**
+ * Sets list of Partial Custom Audiences that are sent to the DSP server when making a
+ * request to download updates for Custom Audience
+ *
+ * <p>See {@link #getPartialCustomAudienceList()} for more details
+ */
+ @NonNull
+ public Builder setPartialCustomAudienceList(
+ @NonNull List<PartialCustomAudience> partialCustomAudiences) {
+ this.mPartialCustomAudienceList = partialCustomAudiences;
+ return this;
+ }
+
+ /**
+ * Builds an instance of {@link ScheduleCustomAudienceUpdateInput}
+ *
+ * @throws NullPointerException if any of the non-null parameters is null
+ */
+ @NonNull
+ public ScheduleCustomAudienceUpdateInput build() {
+ Objects.requireNonNull(mUpdateUri);
+ Objects.requireNonNull(mCallerPackageName);
+ Objects.requireNonNull(mMinDelay);
+ Objects.requireNonNull(mPartialCustomAudienceList);
+
+ return new ScheduleCustomAudienceUpdateInput(this);
+ }
+ }
+}
diff --git a/android-35/android/adservices/customaudience/ScheduleCustomAudienceUpdateRequest.java b/android-35/android/adservices/customaudience/ScheduleCustomAudienceUpdateRequest.java
new file mode 100644
index 0000000..3038801
--- /dev/null
+++ b/android-35/android/adservices/customaudience/ScheduleCustomAudienceUpdateRequest.java
@@ -0,0 +1,181 @@
+/*
+ * 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.customaudience;
+
+import static com.android.adservices.flags.Flags.FLAG_FLEDGE_SCHEDULE_CUSTOM_AUDIENCE_UPDATE_ENABLED;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.net.Uri;
+
+import java.time.Duration;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * The request object wrapping the required and optional parameters to schedule a deferred update
+ * for Custom Audience on device. Allows AdTechs to provide an Update Uri, and the minimum Delay
+ * Time to schedule the update.
+ */
+@FlaggedApi(FLAG_FLEDGE_SCHEDULE_CUSTOM_AUDIENCE_UPDATE_ENABLED)
+public final class ScheduleCustomAudienceUpdateRequest {
+ @NonNull private final Uri mUpdateUri;
+ @NonNull private final Duration mMinDelay;
+ @NonNull private final List<PartialCustomAudience> mPartialCustomAudienceList;
+
+ private ScheduleCustomAudienceUpdateRequest(
+ @NonNull ScheduleCustomAudienceUpdateRequest.Builder builder) {
+ Objects.requireNonNull(builder);
+
+ this.mUpdateUri = builder.mUpdateUri;
+ this.mMinDelay = builder.mMinDelay;
+ this.mPartialCustomAudienceList = builder.mPartialCustomAudienceList;
+ }
+
+ /** Returns the {@link Uri} from which the Custom Audience is to be fetched */
+ @NonNull
+ public Uri getUpdateUri() {
+ return mUpdateUri;
+ }
+
+ /** Returns the {@link Duration} min time duration for which the update is deferred */
+ @NonNull
+ public Duration getMinDelay() {
+ return mMinDelay;
+ }
+
+ /**
+ * Returns the list of {@link PartialCustomAudience} which are sent along with the request to
+ * download the update for Custom Audience
+ */
+ @NonNull
+ public List<PartialCustomAudience> getPartialCustomAudienceList() {
+ return mPartialCustomAudienceList;
+ }
+
+ /** Returns the hash of {@link ScheduleCustomAudienceUpdateRequest} object's data. */
+ @Override
+ public int hashCode() {
+ return Objects.hash(mUpdateUri, mMinDelay, mPartialCustomAudienceList);
+ }
+
+ /**
+ * @return {@code true} only if two {@link ScheduleCustomAudienceUpdateRequest} objects contain
+ * the same information.
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof ScheduleCustomAudienceUpdateRequest)) return false;
+ ScheduleCustomAudienceUpdateRequest that = (ScheduleCustomAudienceUpdateRequest) o;
+ return mUpdateUri.equals(that.mUpdateUri)
+ && mMinDelay.equals(that.mMinDelay)
+ && Objects.equals(mPartialCustomAudienceList, that.mPartialCustomAudienceList);
+ }
+
+ /**
+ * @return a human-readable representation of {@link ScheduleCustomAudienceUpdateRequest}.
+ */
+ @Override
+ public String toString() {
+ return "ScheduleCustomAudienceUpdateRequest {"
+ + "updateUri="
+ + mUpdateUri
+ + ", delayTimeMinutes="
+ + mMinDelay.toMinutes()
+ + ", partialCustomAudienceList="
+ + mPartialCustomAudienceList
+ + '}';
+ }
+
+ /** Builder for {@link ScheduleCustomAudienceUpdateRequest} objects. */
+ public static final class Builder {
+ @NonNull private Uri mUpdateUri;
+ @NonNull private Duration mMinDelay;
+ @NonNull private List<PartialCustomAudience> mPartialCustomAudienceList;
+
+ /**
+ * Instantiates a {@link ScheduleCustomAudienceUpdateRequest.Builder} with the following
+ *
+ * @param updateUri from which the update for Custom Audience is to be fetched
+ * @param minDelay minimum delay time duration for which the update is to be deferred
+ */
+ public Builder(
+ @NonNull Uri updateUri,
+ @NonNull Duration minDelay,
+ @NonNull List<PartialCustomAudience> partialCustomAudienceList) {
+ Objects.requireNonNull(updateUri);
+ Objects.requireNonNull(minDelay);
+ Objects.requireNonNull(partialCustomAudienceList);
+
+ this.mUpdateUri = updateUri;
+ this.mMinDelay = minDelay;
+ this.mPartialCustomAudienceList = partialCustomAudienceList;
+ }
+
+ /**
+ * Sets the {@link Uri} from which the update for Custom Audience is to be fetched
+ *
+ * <p>See {@link #getUpdateUri()} for details
+ */
+ @NonNull
+ public Builder setUpdateUri(@NonNull Uri updateUri) {
+ Objects.requireNonNull(updateUri);
+ this.mUpdateUri = updateUri;
+ return this;
+ }
+
+ /**
+ * Sets the {@link Duration} , min time for which the update is to be deferred
+ *
+ * <p>See {@link #getMinDelay()} for more details
+ */
+ @NonNull
+ public Builder setMinDelay(@NonNull Duration minDelay) {
+ Objects.requireNonNull(minDelay);
+ this.mMinDelay = minDelay;
+ return this;
+ }
+
+ /**
+ * Sets list of Partial Custom Audiences that are sent to the DSP server when making a
+ * request to download updates for Custom Audience
+ *
+ * <p>See {@link #getPartialCustomAudienceList()} for more details
+ */
+ @NonNull
+ public Builder setPartialCustomAudienceList(
+ @NonNull List<PartialCustomAudience> partialCustomAudiences) {
+ this.mPartialCustomAudienceList = partialCustomAudiences;
+ return this;
+ }
+
+ /**
+ * Builds an instance of {@link ScheduleCustomAudienceUpdateRequest}
+ *
+ * @throws NullPointerException if any of the non-null parameters is null
+ */
+ @NonNull
+ public ScheduleCustomAudienceUpdateRequest build() {
+ Objects.requireNonNull(mUpdateUri);
+ Objects.requireNonNull(mMinDelay);
+ Objects.requireNonNull(mPartialCustomAudienceList);
+
+ return new ScheduleCustomAudienceUpdateRequest(this);
+ }
+ }
+}
diff --git a/android-35/android/adservices/customaudience/TestCustomAudienceManager.java b/android-35/android/adservices/customaudience/TestCustomAudienceManager.java
new file mode 100644
index 0000000..26384d9
--- /dev/null
+++ b/android-35/android/adservices/customaudience/TestCustomAudienceManager.java
@@ -0,0 +1,192 @@
+/*
+ * 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.customaudience;
+
+import static android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE;
+
+import android.adservices.common.AdServicesStatusUtils;
+import android.adservices.common.FledgeErrorResponse;
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.os.Build;
+import android.os.OutcomeReceiver;
+import android.os.RemoteException;
+
+import androidx.annotation.RequiresApi;
+
+import com.android.adservices.LoggerFactory;
+
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/** TestCustomAudienceManager provides APIs for app and ad-SDKs to test custom audiences. */
+@RequiresApi(Build.VERSION_CODES.S)
+public class TestCustomAudienceManager {
+ private static final LoggerFactory.Logger sLogger = LoggerFactory.getFledgeLogger();
+
+ private final CustomAudienceManager mCustomAudienceManager;
+ private final String mCallerPackageName;
+
+ TestCustomAudienceManager(
+ @NonNull CustomAudienceManager customAudienceManager,
+ @NonNull String callerPackageName) {
+ Objects.requireNonNull(customAudienceManager);
+ Objects.requireNonNull(callerPackageName);
+
+ mCustomAudienceManager = customAudienceManager;
+ mCallerPackageName = callerPackageName;
+ }
+
+ /**
+ * Overrides the Custom Audience API to avoid fetching data from remote servers and use the data
+ * provided in {@link AddCustomAudienceOverrideRequest} instead. The {@link
+ * AddCustomAudienceOverrideRequest} is provided by the Ads SDK.
+ *
+ * <p>This method is intended to be used for end-to-end testing. This API is enabled only for
+ * apps in debug mode with developer options enabled.
+ *
+ * <p>This call will fail silently if the {@code owner} in the {@code request} is not the
+ * calling app's package name.
+ *
+ * @throws IllegalStateException if this API is not enabled for the caller
+ * <p>The receiver either returns a {@code void} for a successful run, or an {@link
+ * Exception} indicates the error.
+ */
+ @RequiresPermission(ACCESS_ADSERVICES_CUSTOM_AUDIENCE)
+ public void overrideCustomAudienceRemoteInfo(
+ @NonNull AddCustomAudienceOverrideRequest request,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OutcomeReceiver<Object, Exception> receiver) {
+ Objects.requireNonNull(request);
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(receiver);
+ try {
+ final ICustomAudienceService service = mCustomAudienceManager.getService();
+ service.overrideCustomAudienceRemoteInfo(
+ mCallerPackageName,
+ request.getBuyer(),
+ request.getName(),
+ request.getBiddingLogicJs(),
+ request.getBiddingLogicJsVersion(),
+ request.getTrustedBiddingSignals(),
+ new CustomAudienceOverrideCallback.Stub() {
+ @Override
+ public void onSuccess() {
+ executor.execute(() -> receiver.onResult(new Object()));
+ }
+
+ @Override
+ public void onFailure(FledgeErrorResponse failureParcel) {
+ executor.execute(
+ () ->
+ receiver.onError(
+ AdServicesStatusUtils.asException(
+ failureParcel)));
+ }
+ });
+ } catch (RemoteException e) {
+ sLogger.e(e, "Exception");
+ receiver.onError(new IllegalStateException("Internal Error!", e));
+ }
+ }
+ /**
+ * Removes an override in th Custom Audience API with associated the data in {@link
+ * RemoveCustomAudienceOverrideRequest}.
+ *
+ * <p>This method is intended to be used for end-to-end testing. This API is enabled only for
+ * apps in debug mode with developer options enabled.
+ *
+ * @throws IllegalStateException if this API is not enabled for the caller
+ * <p>The {@link RemoveCustomAudienceOverrideRequest} is provided by the Ads SDK. The
+ * receiver either returns a {@code void} for a successful run, or an {@link Exception}
+ * indicates the error.
+ */
+ @RequiresPermission(ACCESS_ADSERVICES_CUSTOM_AUDIENCE)
+ public void removeCustomAudienceRemoteInfoOverride(
+ @NonNull RemoveCustomAudienceOverrideRequest request,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OutcomeReceiver<Object, Exception> receiver) {
+ Objects.requireNonNull(request);
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(receiver);
+ try {
+ final ICustomAudienceService service = mCustomAudienceManager.getService();
+ service.removeCustomAudienceRemoteInfoOverride(
+ mCallerPackageName,
+ request.getBuyer(),
+ request.getName(),
+ new CustomAudienceOverrideCallback.Stub() {
+ @Override
+ public void onSuccess() {
+ executor.execute(() -> receiver.onResult(new Object()));
+ }
+
+ @Override
+ public void onFailure(FledgeErrorResponse failureParcel) {
+ executor.execute(
+ () ->
+ receiver.onError(
+ AdServicesStatusUtils.asException(
+ failureParcel)));
+ }
+ });
+ } catch (RemoteException e) {
+ sLogger.e(e, "Exception");
+ receiver.onError(new IllegalStateException("Internal Error!", e));
+ }
+ }
+ /**
+ * Removes all override data in the Custom Audience API.
+ *
+ * <p>This method is intended to be used for end-to-end testing. This API is enabled only for
+ * apps in debug mode with developer options enabled.
+ *
+ * @throws IllegalStateException if this API is not enabled for the caller
+ * <p>The receiver either returns a {@code void} for a successful run, or an {@link
+ * Exception} indicates the error.
+ */
+ @RequiresPermission(ACCESS_ADSERVICES_CUSTOM_AUDIENCE)
+ public void resetAllCustomAudienceOverrides(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OutcomeReceiver<Object, Exception> receiver) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(receiver);
+ try {
+ final ICustomAudienceService service = mCustomAudienceManager.getService();
+ service.resetAllCustomAudienceOverrides(
+ new CustomAudienceOverrideCallback.Stub() {
+ @Override
+ public void onSuccess() {
+ executor.execute(() -> receiver.onResult(new Object()));
+ }
+
+ @Override
+ public void onFailure(FledgeErrorResponse failureParcel) {
+ executor.execute(
+ () ->
+ receiver.onError(
+ AdServicesStatusUtils.asException(
+ failureParcel)));
+ }
+ });
+ } catch (RemoteException e) {
+ sLogger.e(e, "Exception");
+ receiver.onError(new IllegalStateException("Internal Error!", e));
+ }
+ }
+}
diff --git a/android-35/android/adservices/customaudience/TrustedBiddingData.java b/android-35/android/adservices/customaudience/TrustedBiddingData.java
new file mode 100644
index 0000000..0143a13
--- /dev/null
+++ b/android-35/android/adservices/customaudience/TrustedBiddingData.java
@@ -0,0 +1,159 @@
+/*
+ * 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.customaudience;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Represents data used during the ad selection process to fetch buyer bidding signals from a
+ * trusted key/value server. The fetched data is used during the ad selection process and consumed
+ * by buyer JavaScript logic running in an isolated execution environment.
+ */
+public final class TrustedBiddingData implements Parcelable {
+ @NonNull private final Uri mTrustedBiddingUri;
+ @NonNull
+ private final List<String> mTrustedBiddingKeys;
+
+ @NonNull
+ public static final Creator<TrustedBiddingData> CREATOR = new Creator<TrustedBiddingData>() {
+ @Override
+ public TrustedBiddingData createFromParcel(@NonNull Parcel in) {
+ Objects.requireNonNull(in);
+ return new TrustedBiddingData(in);
+ }
+
+ @Override
+ public TrustedBiddingData[] newArray(int size) {
+ return new TrustedBiddingData[size];
+ }
+ };
+
+ private TrustedBiddingData(@NonNull TrustedBiddingData.Builder builder) {
+ mTrustedBiddingUri = builder.mTrustedBiddingUri;
+ mTrustedBiddingKeys = builder.mTrustedBiddingKeys;
+ }
+
+ private TrustedBiddingData(@NonNull Parcel in) {
+ Objects.requireNonNull(in);
+ mTrustedBiddingUri = Uri.CREATOR.createFromParcel(in);
+ mTrustedBiddingKeys = in.createStringArrayList();
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ Objects.requireNonNull(dest);
+ mTrustedBiddingUri.writeToParcel(dest, flags);
+ dest.writeStringList(mTrustedBiddingKeys);
+ }
+
+ /** @hide */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * @return the URI pointing to the trusted key-value server holding bidding signals. The URI
+ * must use HTTPS.
+ */
+ @NonNull
+ public Uri getTrustedBiddingUri() {
+ return mTrustedBiddingUri;
+ }
+
+ /**
+ * @return the list of keys to query from the trusted key-value server holding bidding signals
+ */
+ @NonNull
+ public List<String> getTrustedBiddingKeys() {
+ return mTrustedBiddingKeys;
+ }
+
+ /**
+ * @return {@code true} if two {@link TrustedBiddingData} objects contain the same information
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof TrustedBiddingData)) return false;
+ TrustedBiddingData that = (TrustedBiddingData) o;
+ return mTrustedBiddingUri.equals(that.mTrustedBiddingUri)
+ && mTrustedBiddingKeys.equals(that.mTrustedBiddingKeys);
+ }
+
+ /**
+ * @return the hash of the {@link TrustedBiddingData} object's data
+ */
+ @Override
+ public int hashCode() {
+ return Objects.hash(mTrustedBiddingUri, mTrustedBiddingKeys);
+ }
+
+ /** Builder for {@link TrustedBiddingData} objects. */
+ public static final class Builder {
+ @Nullable private Uri mTrustedBiddingUri;
+ @Nullable private List<String> mTrustedBiddingKeys;
+
+ // TODO(b/232883403): We may need to add @NonNUll members as args.
+ public Builder() {
+ }
+
+ /**
+ * Sets the URI pointing to a trusted key-value server used to fetch bidding signals during
+ * the ad selection process. The URI must use HTTPS.
+ */
+ @NonNull
+ public Builder setTrustedBiddingUri(@NonNull Uri trustedBiddingUri) {
+ Objects.requireNonNull(trustedBiddingUri);
+ mTrustedBiddingUri = trustedBiddingUri;
+ return this;
+ }
+
+ /**
+ * Sets the list of keys to query the trusted key-value server with.
+ * <p>
+ * This list is permitted to be empty, but it must not be null.
+ */
+ @NonNull
+ public Builder setTrustedBiddingKeys(@NonNull List<String> trustedBiddingKeys) {
+ Objects.requireNonNull(trustedBiddingKeys);
+ mTrustedBiddingKeys = trustedBiddingKeys;
+ return this;
+ }
+
+ /**
+ * Builds the {@link TrustedBiddingData} object.
+ *
+ * @throws NullPointerException if any parameters are null when built
+ */
+ @NonNull
+ public TrustedBiddingData build() {
+ Objects.requireNonNull(mTrustedBiddingUri);
+ // Note that the list of keys is allowed to be empty, but not null
+ Objects.requireNonNull(mTrustedBiddingKeys);
+
+ return new TrustedBiddingData(this);
+ }
+ }
+}
diff --git a/android-35/android/adservices/exceptions/AdServicesException.java b/android-35/android/adservices/exceptions/AdServicesException.java
new file mode 100644
index 0000000..577eac7
--- /dev/null
+++ b/android-35/android/adservices/exceptions/AdServicesException.java
@@ -0,0 +1,33 @@
+/*
+ * 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.exceptions;
+import android.annotation.Nullable;
+/**
+ * Exception thrown by AdServices.
+ */
+public class AdServicesException extends Exception {
+ public AdServicesException(@Nullable String message, @Nullable Throwable e) {
+ super(message, e);
+ }
+ public AdServicesException(@Nullable String message) {
+ super(message);
+ }
+
+ /** @hide */
+ public AdServicesException() {
+ super();
+ }
+}
\ No newline at end of file
diff --git a/android-35/android/adservices/exceptions/AdServicesNetworkException.java b/android-35/android/adservices/exceptions/AdServicesNetworkException.java
new file mode 100644
index 0000000..a6efae4
--- /dev/null
+++ b/android-35/android/adservices/exceptions/AdServicesNetworkException.java
@@ -0,0 +1,128 @@
+/*
+ * 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.exceptions;
+
+import static java.util.Locale.ENGLISH;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Exception thrown by the service when a failed HTTP request is the cause of a failed API call.
+ *
+ * @hide
+ */
+public class AdServicesNetworkException extends AdServicesException {
+ /**
+ * Error code indicating that the service received an <a
+ * href="https://httpwg.org/specs/rfc9110.html#status.3xx">HTTP 3xx</a> status code.
+ */
+ public static final int ERROR_REDIRECTION = 3;
+
+ /**
+ * Error code indicating that the service received an <a
+ * href="https://httpwg.org/specs/rfc9110.html#status.4xx">HTTP 4xx</a> status code.
+ */
+ public static final int ERROR_CLIENT = 4;
+
+ /**
+ * Error code indicating that the user has sent too many requests in a given amount of time and
+ * the service received an <a href="https://httpwg.org/specs/rfc6585.html#status-429">HTTP
+ * 429</a> status code.
+ */
+ public static final int ERROR_TOO_MANY_REQUESTS = 429;
+
+ /**
+ * Error code indicating that the service received an <a
+ * href="https://httpwg.org/specs/rfc9110.html#status.4xx">HTTP 5xx</a> status code.
+ */
+ public static final int ERROR_SERVER = 5;
+
+ /** Error code indicating another type of error was encountered. */
+ public static final int ERROR_OTHER = 999;
+
+ /** Error codes indicating what caused the HTTP request to fail. */
+ @IntDef(
+ prefix = {"ERROR_"},
+ value = {
+ ERROR_REDIRECTION,
+ ERROR_CLIENT,
+ ERROR_TOO_MANY_REQUESTS,
+ ERROR_SERVER,
+ ERROR_OTHER
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ErrorCode {}
+
+ /** @hide */
+ public static final String INVALID_ERROR_CODE_MESSAGE = "Valid error code must be set.";
+
+ @ErrorCode private final int mErrorCode;
+
+ /**
+ * Constructs an {@link AdServicesNetworkException} that is caused by a failed HTTP request.
+ *
+ * @param errorCode relevant {@link ErrorCode} corresponding to the failure.
+ */
+ public AdServicesNetworkException(@ErrorCode int errorCode) {
+ super();
+
+ checkErrorCode(errorCode);
+ mErrorCode = errorCode;
+ }
+
+ /**
+ * @return the {@link ErrorCode} indicating what caused the HTTP request to fail.
+ */
+ @NonNull
+ @ErrorCode
+ public int getErrorCode() {
+ return mErrorCode;
+ }
+
+ /**
+ * @return a human-readable representation of {@link AdServicesNetworkException}.
+ */
+ @Override
+ public String toString() {
+ return String.format(
+ ENGLISH,
+ "%s: {Error code: %s}",
+ this.getClass().getCanonicalName(),
+ this.getErrorCode());
+ }
+
+ private void checkErrorCode(@ErrorCode int errorCode) {
+ switch (errorCode) {
+ case ERROR_REDIRECTION:
+ // Intentional fallthrough
+ case ERROR_CLIENT:
+ // Intentional fallthrough
+ case ERROR_TOO_MANY_REQUESTS:
+ // Intentional fallthrough
+ case ERROR_SERVER:
+ // Intentional fallthrough
+ case ERROR_OTHER:
+ break;
+ default:
+ throw new IllegalArgumentException(INVALID_ERROR_CODE_MESSAGE);
+ }
+ }
+}
diff --git a/android-35/android/adservices/exceptions/RetryableAdServicesNetworkException.java b/android-35/android/adservices/exceptions/RetryableAdServicesNetworkException.java
new file mode 100644
index 0000000..b9f1278
--- /dev/null
+++ b/android-35/android/adservices/exceptions/RetryableAdServicesNetworkException.java
@@ -0,0 +1,101 @@
+/*
+ * 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.exceptions;
+
+import static java.util.Locale.ENGLISH;
+
+import android.annotation.NonNull;
+
+import com.android.internal.util.Preconditions;
+
+import java.time.Duration;
+import java.util.Objects;
+
+/**
+ * Exception thrown by the service when the HTTP failure response that caused the API to fail
+ * contains a <a href="https://httpwg.org/specs/rfc6585.html#status-429">Retry-After header</a>.
+ *
+ * @hide
+ */
+public class RetryableAdServicesNetworkException extends AdServicesNetworkException {
+ /** @hide */
+ public static final Duration UNSET_RETRY_AFTER_VALUE = Duration.ZERO;
+
+ /** @hide */
+ public static final Duration DEFAULT_RETRY_AFTER_VALUE = Duration.ofMillis(30 * 1000);
+
+ /** @hide */
+ public static final String INVALID_RETRY_AFTER_MESSAGE =
+ "Retry-after time duration must be strictly greater than zero.";
+
+ // TODO: (b/298100114) make this final again
+ private Duration mRetryAfter;
+
+ /**
+ * Constructs an {@link RetryableAdServicesNetworkException} that is caused by a failed HTTP
+ * request.
+ *
+ * @param errorCode relevant {@link ErrorCode} corresponding to the failure.
+ * @param retryAfter time {@link Duration} to back-off until next retry.
+ */
+ public RetryableAdServicesNetworkException(
+ @ErrorCode int errorCode, @NonNull Duration retryAfter) {
+ super(errorCode);
+
+ Objects.requireNonNull(retryAfter);
+ Preconditions.checkArgument(
+ retryAfter.compareTo(UNSET_RETRY_AFTER_VALUE) > 0, INVALID_RETRY_AFTER_MESSAGE);
+
+ mRetryAfter = retryAfter;
+ }
+
+ /**
+ * If {@link #mRetryAfter} < {@code defaultDuration}, it gets set to {@code defaultDuration}. If
+ * {@link #mRetryAfter} > {@code maxDuration}, it gets set to {@code maxDuration}. If it falls
+ * in the range of both numbers, it stays the same.
+ *
+ * @hide
+ */
+ public void setRetryAfterToValidDuration(long defaultDuration, long maxDuration) {
+ // TODO: (b/298100114) this is a hack! this method should be removed after resolving the bug
+ mRetryAfter =
+ Duration.ofMillis(
+ Math.min(Math.max(mRetryAfter.toMillis(), defaultDuration), maxDuration));
+ }
+
+ /**
+ * @return the positive retry-after {@link Duration} if set or else {@link Duration#ZERO} by
+ * default.
+ */
+ @NonNull
+ public Duration getRetryAfter() {
+ return mRetryAfter;
+ }
+
+ /**
+ * @return a human-readable representation of {@link RetryableAdServicesNetworkException}.
+ */
+ @Override
+ public String toString() {
+ return String.format(
+ ENGLISH,
+ "%s: {Error code: %s, Retry after: %sms}",
+ this.getClass().getCanonicalName(),
+ this.getErrorCode(),
+ this.getRetryAfter().toMillis());
+ }
+}
diff --git a/android-35/android/adservices/exceptions/UnsupportedPayloadSizeException.java b/android-35/android/adservices/exceptions/UnsupportedPayloadSizeException.java
new file mode 100644
index 0000000..a67428e
--- /dev/null
+++ b/android-35/android/adservices/exceptions/UnsupportedPayloadSizeException.java
@@ -0,0 +1,39 @@
+/*
+ * 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.exceptions;
+
+import android.annotation.Nullable;
+
+/**
+ * Exception used when the size of a payload is not supported.
+ *
+ * @hide
+ */
+public class UnsupportedPayloadSizeException extends IllegalStateException {
+ private final int mPayloadSizeKb;
+
+ /** Constructs a {@link UnsupportedPayloadSizeException} */
+ public UnsupportedPayloadSizeException(int payloadSizeKb, @Nullable String message) {
+ super(message);
+ this.mPayloadSizeKb = payloadSizeKb;
+ }
+
+ /** Returns the size of the payload in Kb */
+ public int getPayloadSizeKb() {
+ return mPayloadSizeKb;
+ }
+}
diff --git a/android-35/android/adservices/extdata/AdServicesExtDataParams.java b/android-35/android/adservices/extdata/AdServicesExtDataParams.java
new file mode 100644
index 0000000..fab883a
--- /dev/null
+++ b/android-35/android/adservices/extdata/AdServicesExtDataParams.java
@@ -0,0 +1,311 @@
+/*
+ * 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.extdata;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.adservices.flags.Flags;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Container for the data fields handled by {@link AdServicesExtDataStorageService}.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_ADEXT_DATA_SERVICE_APIS_ENABLED)
+public final class AdServicesExtDataParams implements Parcelable {
+ /**
+ * Custom tri-state boolean type to represent true, false, and unknown
+ *
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(
+ prefix = "BOOLEAN_",
+ value = {BOOLEAN_TRUE, BOOLEAN_FALSE, BOOLEAN_UNKNOWN})
+ public @interface TriStateBoolean {}
+
+ /**
+ * Int value to represent true.
+ *
+ * @hide
+ */
+ public static final int BOOLEAN_TRUE = 1;
+
+ /**
+ * Int value to represent false.
+ *
+ * @hide
+ */
+ public static final int BOOLEAN_FALSE = 0;
+
+ /**
+ * Int value to represent unknown.
+ *
+ * @hide
+ */
+ public static final int BOOLEAN_UNKNOWN = -1;
+
+ /**
+ * Type to represent user manual interaction state.
+ *
+ * @hide
+ */
+ @IntDef(
+ prefix = "STATE_",
+ value = {
+ STATE_NO_MANUAL_INTERACTIONS_RECORDED,
+ STATE_UNKNOWN,
+ STATE_MANUAL_INTERACTIONS_RECORDED
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface UserManualInteraction {}
+
+ /**
+ * Int value to represent no manual interaction recorded state.
+ *
+ * @hide
+ */
+ public static final int STATE_NO_MANUAL_INTERACTIONS_RECORDED = -1;
+
+ /**
+ * Int value to represent unknown manual interaction state.
+ *
+ * @hide
+ */
+ public static final int STATE_UNKNOWN = 0;
+
+ /**
+ * Int value to represent manual interaction reported state.
+ *
+ * @hide
+ */
+ public static final int STATE_MANUAL_INTERACTIONS_RECORDED = 1;
+
+ @TriStateBoolean private final int mIsNotificationDisplayed;
+ @TriStateBoolean private final int mIsMeasurementConsented;
+ @TriStateBoolean private final int mIsU18Account;
+ @TriStateBoolean private final int mIsAdultAccount;
+ @UserManualInteraction private final int mManualInteractionWithConsentStatus;
+ private final long mMeasurementRollbackApexVersion;
+
+ /**
+ * Init AdServicesExtDataParams.
+ *
+ * @param isNotificationDisplayed 1 if notification is displayed, 0 if notification not
+ * displayed, -1 to represent no data.
+ * @param isMeasurementConsented 1 if measurement consented, 0 if not, -1 to represent no data.
+ * @param isU18Account 1 if account is U18, 0 if not, -1 if no data.
+ * @param isAdultAccount 1 if adult account, 0 if not, -1 if no data.
+ * @param manualInteractionWithConsentStatus 1 if user interacted, -1 if not, 0 if unknown.
+ * @param measurementRollbackApexVersion ExtServices apex version for measurement rollback
+ * handling. -1 if no data.
+ */
+ public AdServicesExtDataParams(
+ @TriStateBoolean int isNotificationDisplayed,
+ @TriStateBoolean int isMeasurementConsented,
+ @TriStateBoolean int isU18Account,
+ @TriStateBoolean int isAdultAccount,
+ @UserManualInteraction int manualInteractionWithConsentStatus,
+ long measurementRollbackApexVersion) {
+ mIsNotificationDisplayed = isNotificationDisplayed;
+ mIsMeasurementConsented = isMeasurementConsented;
+ mIsU18Account = isU18Account;
+ mIsAdultAccount = isAdultAccount;
+ mManualInteractionWithConsentStatus = manualInteractionWithConsentStatus;
+ mMeasurementRollbackApexVersion = measurementRollbackApexVersion;
+ }
+
+ private AdServicesExtDataParams(@NonNull Parcel in) {
+ mIsNotificationDisplayed = in.readInt();
+ mIsMeasurementConsented = in.readInt();
+ mIsU18Account = in.readInt();
+ mIsAdultAccount = in.readInt();
+ mManualInteractionWithConsentStatus = in.readInt();
+ mMeasurementRollbackApexVersion = in.readLong();
+ }
+
+ /** Creator for Parcelable. */
+ @NonNull
+ public static final Creator<AdServicesExtDataParams> CREATOR =
+ new Creator<AdServicesExtDataParams>() {
+ @Override
+ public AdServicesExtDataParams createFromParcel(Parcel in) {
+ return new AdServicesExtDataParams(in);
+ }
+
+ @Override
+ public AdServicesExtDataParams[] newArray(int size) {
+ return new AdServicesExtDataParams[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeInt(mIsNotificationDisplayed);
+ out.writeInt(mIsMeasurementConsented);
+ out.writeInt(mIsU18Account);
+ out.writeInt(mIsAdultAccount);
+ out.writeInt(mManualInteractionWithConsentStatus);
+ out.writeLong(mMeasurementRollbackApexVersion);
+ }
+
+ /** Returns 1 if notification was shown on R, 0 if not shown, -1 if unknown. */
+ @TriStateBoolean
+ public int getIsNotificationDisplayed() {
+ return mIsNotificationDisplayed;
+ }
+
+ /** Returns 1 if measurement was consented, 0 if not, -1 if unknown. */
+ @TriStateBoolean
+ public int getIsMeasurementConsented() {
+ return mIsMeasurementConsented;
+ }
+
+ /** Returns 1 if account is U18 account, 0 if not, -1 if unknown. */
+ @TriStateBoolean
+ public int getIsU18Account() {
+ return mIsU18Account;
+ }
+
+ /** Returns 1 if account is adult account, 0 if not, -1 if unknown. */
+ @TriStateBoolean
+ public int getIsAdultAccount() {
+ return mIsAdultAccount;
+ }
+
+ /** Returns 1 if user interacted, -1 if not, 0 if unknown. */
+ @UserManualInteraction
+ public int getManualInteractionWithConsentStatus() {
+ return mManualInteractionWithConsentStatus;
+ }
+
+ /**
+ * Returns ExtServices apex version for handling measurement rollback. -1 is returned if no data
+ * is available.
+ */
+ public long getMeasurementRollbackApexVersion() {
+ return mMeasurementRollbackApexVersion;
+ }
+
+ @SuppressLint("DefaultLocale")
+ @Override
+ public String toString() {
+ return String.format(
+ "AdServicesExtDataParams{"
+ + "mIsNotificationDisplayed=%d, "
+ + "mIsMsmtConsented=%d, "
+ + "mIsU18Account=%d, "
+ + "mIsAdultAccount=%d, "
+ + "mManualInteractionWithConsentStatus=%d, "
+ + "mMsmtRollbackApexVersion=%d}",
+ mIsNotificationDisplayed,
+ mIsMeasurementConsented,
+ mIsU18Account,
+ mIsAdultAccount,
+ mManualInteractionWithConsentStatus,
+ mMeasurementRollbackApexVersion);
+ }
+
+ /**
+ * Builder for {@link AdServicesExtDataParams} objects.
+ *
+ * @hide
+ */
+ public static final class Builder {
+ @TriStateBoolean private int mNotificationDisplayed;
+ @TriStateBoolean private int mMsmtConsent;
+ @TriStateBoolean private int mIsU18Account;
+ @TriStateBoolean private int mIsAdultAccount;
+ @UserManualInteraction private int mManualInteractionWithConsentStatus;
+ private long mMsmtRollbackApexVersion;
+
+ /** Set the isNotificationDisplayed. */
+ @NonNull
+ public AdServicesExtDataParams.Builder setNotificationDisplayed(
+ @TriStateBoolean int notificationDisplayed) {
+ mNotificationDisplayed = notificationDisplayed;
+ return this;
+ }
+
+ /** Set the isMeasurementConsented. */
+ @NonNull
+ public AdServicesExtDataParams.Builder setMsmtConsent(@TriStateBoolean int msmtConsent) {
+ mMsmtConsent = msmtConsent;
+ return this;
+ }
+
+ /** Set the isU18Account. */
+ @NonNull
+ public AdServicesExtDataParams.Builder setIsU18Account(@TriStateBoolean int isU18Account) {
+ mIsU18Account = isU18Account;
+ return this;
+ }
+
+ /** Set the isAdultAccount. */
+ @NonNull
+ public AdServicesExtDataParams.Builder setIsAdultAccount(
+ @TriStateBoolean int isAdultAccount) {
+ mIsAdultAccount = isAdultAccount;
+ return this;
+ }
+
+ /** Set the manualInteractionWithConsentStatus. */
+ @NonNull
+ public AdServicesExtDataParams.Builder setManualInteractionWithConsentStatus(
+ @UserManualInteraction int manualInteractionWithConsentStatus) {
+ mManualInteractionWithConsentStatus = manualInteractionWithConsentStatus;
+ return this;
+ }
+
+ /** Set the msmtRollbackApexVersion. */
+ @NonNull
+ public AdServicesExtDataParams.Builder setMsmtRollbackApexVersion(
+ long msmtRollbackApexVersion) {
+ mMsmtRollbackApexVersion = msmtRollbackApexVersion;
+ return this;
+ }
+
+ public Builder() {}
+
+ /** Builds a {@link AdServicesExtDataParams} instance. */
+ @NonNull
+ public AdServicesExtDataParams build() {
+ return new AdServicesExtDataParams(
+ mNotificationDisplayed,
+ mMsmtConsent,
+ mIsU18Account,
+ mIsAdultAccount,
+ mManualInteractionWithConsentStatus,
+ mMsmtRollbackApexVersion);
+ }
+ }
+}
diff --git a/android-35/android/adservices/extdata/AdServicesExtDataStorageService.java b/android-35/android/adservices/extdata/AdServicesExtDataStorageService.java
new file mode 100644
index 0000000..cd2644e
--- /dev/null
+++ b/android-35/android/adservices/extdata/AdServicesExtDataStorageService.java
@@ -0,0 +1,153 @@
+/*
+ * 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.extdata;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SdkConstant;
+import android.annotation.SystemApi;
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import com.android.adservices.flags.Flags;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * Abstract base class to implement AdServicesExtDataStorageService.
+ *
+ * <p>The implementor of this service needs to override the onGetAdServicesExtData and
+ * onPutAdServicesExtData methods
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_ADEXT_DATA_SERVICE_APIS_ENABLED)
+public abstract class AdServicesExtDataStorageService extends Service {
+ /**
+ * Supported data field IDs.
+ *
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(
+ prefix = "FIELD_",
+ value = {
+ FIELD_IS_NOTIFICATION_DISPLAYED,
+ FIELD_IS_MEASUREMENT_CONSENTED,
+ FIELD_IS_U18_ACCOUNT,
+ FIELD_IS_ADULT_ACCOUNT,
+ FIELD_MANUAL_INTERACTION_WITH_CONSENT_STATUS,
+ FIELD_MEASUREMENT_ROLLBACK_APEX_VERSION,
+ })
+ public @interface AdServicesExtDataFieldId {}
+
+ /** Field to represent whether AdServices consent notification has been shown on Android R. */
+ public static final int FIELD_IS_NOTIFICATION_DISPLAYED = 0;
+
+ /** Field to represent whether user provided consent for Measurement API. */
+ public static final int FIELD_IS_MEASUREMENT_CONSENTED = 1;
+
+ /** Field to represent whether account is U18. */
+ public static final int FIELD_IS_U18_ACCOUNT = 2;
+
+ /** Field to represent whether it's an adult account. */
+ public static final int FIELD_IS_ADULT_ACCOUNT = 3;
+
+ /** Field to represent whether user manually interacted with consent */
+ public static final int FIELD_MANUAL_INTERACTION_WITH_CONSENT_STATUS = 4;
+
+ /** Field to represent ExtServices apex version for measurement rollback handling. */
+ public static final int FIELD_MEASUREMENT_ROLLBACK_APEX_VERSION = 5;
+
+ /** The intent that the service must respond to. Add it to the intent filter of the service. */
+ @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+ public static final String SERVICE_INTERFACE =
+ "android.adservices.extdata.AdServicesExtDataStorageService";
+
+ public AdServicesExtDataStorageService() {}
+
+ @Nullable
+ @Override
+ public final IBinder onBind(@Nullable Intent intent) {
+ return mInterface.asBinder();
+ }
+
+ /** Abstract onGetAdServicesExtData method to get all stored ext data values from data store. */
+ @NonNull
+ public abstract AdServicesExtDataParams onGetAdServicesExtData();
+
+ /**
+ * Abstract onPutAdServicesExtData method to update values of fields in data store.
+ *
+ * @param adServicesExtDataParams data object that stores fields to be updated.
+ * @param adServicesExtDataFields explicit list of fields that need to be updated in data store.
+ */
+ public abstract void onPutAdServicesExtData(
+ @NonNull AdServicesExtDataParams adServicesExtDataParams,
+ @NonNull @AdServicesExtDataFieldId int[] adServicesExtDataFields);
+
+ private final IAdServicesExtDataStorageService mInterface =
+ new IAdServicesExtDataStorageService.Stub() {
+
+ @Override
+ public void getAdServicesExtData(@NonNull IGetAdServicesExtDataCallback callback)
+ throws RemoteException {
+ Objects.requireNonNull(callback);
+
+ try {
+ AdServicesExtDataParams adServicesExtDataParams = onGetAdServicesExtData();
+
+ GetAdServicesExtDataResult result =
+ new GetAdServicesExtDataResult.Builder()
+ .setAdServicesExtDataParams(adServicesExtDataParams)
+ .build();
+ callback.onResult(result);
+ } catch (Exception e) {
+ callback.onError(e.getMessage());
+ }
+ }
+
+ @Override
+ public void putAdServicesExtData(
+ @NonNull AdServicesExtDataParams params,
+ @NonNull @AdServicesExtDataFieldId int[] adServicesExtDataFields,
+ @NonNull IGetAdServicesExtDataCallback callback)
+ throws RemoteException {
+ Objects.requireNonNull(params);
+ Objects.requireNonNull(adServicesExtDataFields);
+ Objects.requireNonNull(callback);
+
+ try {
+ onPutAdServicesExtData(params, adServicesExtDataFields);
+ GetAdServicesExtDataResult result =
+ new GetAdServicesExtDataResult.Builder()
+ .setAdServicesExtDataParams(params)
+ .build();
+ callback.onResult(result);
+ } catch (Exception e) {
+ callback.onError(e.getMessage());
+ }
+ }
+ };
+}
diff --git a/android-35/android/adservices/extdata/GetAdServicesExtDataResult.java b/android-35/android/adservices/extdata/GetAdServicesExtDataResult.java
new file mode 100644
index 0000000..176d0d2
--- /dev/null
+++ b/android-35/android/adservices/extdata/GetAdServicesExtDataResult.java
@@ -0,0 +1,156 @@
+/*
+ * 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.extdata;
+
+import android.adservices.common.AdServicesResponse;
+import android.adservices.common.AdServicesStatusUtils;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.os.Parcel;
+
+import java.util.Objects;
+
+/**
+ * Represent the result from {@link AdServicesExtDataStorageService} API.
+ *
+ * @hide
+ */
+public final class GetAdServicesExtDataResult extends AdServicesResponse {
+ @NonNull private final AdServicesExtDataParams mAdServicesExtDataParams;
+
+ private GetAdServicesExtDataResult(
+ @AdServicesStatusUtils.StatusCode int resultCode,
+ @Nullable String errorMessage,
+ @NonNull AdServicesExtDataParams adServicesExtDataParams) {
+ super(resultCode, errorMessage);
+ mAdServicesExtDataParams = Objects.requireNonNull(adServicesExtDataParams);
+ }
+
+ private GetAdServicesExtDataResult(@NonNull Parcel in) {
+ super(in);
+ Objects.requireNonNull(in);
+ // Method deprecated starting from Android T; however, AdServicesExtDataStorageService is
+ // intended to only be used on Android S-.
+ mAdServicesExtDataParams =
+ in.readParcelable(AdServicesExtDataParams.class.getClassLoader());
+ }
+
+ /** Creator for Parcelable. */
+ @NonNull
+ public static final Creator<GetAdServicesExtDataResult> CREATOR =
+ new Creator<GetAdServicesExtDataResult>() {
+ @Override
+ public GetAdServicesExtDataResult createFromParcel(@NonNull Parcel in) {
+ Objects.requireNonNull(in);
+ return new GetAdServicesExtDataResult(in);
+ }
+
+ @Override
+ public GetAdServicesExtDataResult[] newArray(int size) {
+ return new GetAdServicesExtDataResult[size];
+ }
+ };
+
+ /** @hide */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** @hide */
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeInt(mStatusCode);
+ out.writeString(mErrorMessage);
+ out.writeParcelable(mAdServicesExtDataParams, flags);
+ }
+
+ /**
+ * Returns the error message associated with this result.
+ *
+ * <p>If {@link #isSuccess} is {@code true}, the error message is always {@code null}. The error
+ * message may be {@code null} even if {@link #isSuccess} is {@code false}.
+ */
+ @Nullable
+ public String getErrorMessage() {
+ return mErrorMessage;
+ }
+
+ /** Returns the AdServicesExtDataParams value. */
+ @NonNull
+ public AdServicesExtDataParams getAdServicesExtDataParams() {
+ return mAdServicesExtDataParams;
+ }
+
+ @SuppressLint("DefaultLocale")
+ @Override
+ public String toString() {
+ return String.format(
+ "GetAdServicesExtIntDataResult{"
+ + "mResultCode=%d, "
+ + "mErrorMessage=%s, "
+ + "mAdServicesExtDataParams=%s}",
+ mStatusCode, mErrorMessage, mAdServicesExtDataParams.toString());
+ }
+
+ /**
+ * Builder for {@link GetAdServicesExtDataResult} objects.
+ *
+ * @hide
+ */
+ public static final class Builder {
+ @AdServicesStatusUtils.StatusCode private int mStatusCode;
+
+ @Nullable private String mErrorMessage;
+ @NonNull private AdServicesExtDataParams mAdServicesExtDataParams;
+
+ public Builder() {}
+
+ /** Set the Result Code. */
+ @NonNull
+ public Builder setStatusCode(@AdServicesStatusUtils.StatusCode int statusCode) {
+ mStatusCode = statusCode;
+ return this;
+ }
+
+ /** Set the Error Message. */
+ @NonNull
+ public Builder setErrorMessage(@Nullable String errorMessage) {
+ mErrorMessage = errorMessage;
+ return this;
+ }
+
+ /** Set the AdServicesExtDataParams */
+ @NonNull
+ public Builder setAdServicesExtDataParams(AdServicesExtDataParams adServicesExtDataParams) {
+ mAdServicesExtDataParams = adServicesExtDataParams;
+ return this;
+ }
+
+ /** Builds a {@link GetAdServicesExtDataResult} instance. */
+ @NonNull
+ public GetAdServicesExtDataResult build() {
+ if (mAdServicesExtDataParams == null) {
+ throw new IllegalArgumentException("AdServicesExtDataParams is null");
+ }
+
+ return new GetAdServicesExtDataResult(
+ mStatusCode, mErrorMessage, mAdServicesExtDataParams);
+ }
+ }
+}
diff --git a/android-35/android/adservices/measurement/DeletionParam.java b/android-35/android/adservices/measurement/DeletionParam.java
new file mode 100644
index 0000000..00d5fad
--- /dev/null
+++ b/android-35/android/adservices/measurement/DeletionParam.java
@@ -0,0 +1,255 @@
+/*
+ * 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.measurement;
+
+import android.annotation.NonNull;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Class to hold deletion related request. This is an internal class for communication between the
+ * {@link MeasurementManager} and {@link IMeasurementService} impl.
+ *
+ * @hide
+ */
+public final class DeletionParam implements Parcelable {
+ private final List<Uri> mOriginUris;
+ private final List<Uri> mDomainUris;
+ private final Instant mStart;
+ private final Instant mEnd;
+ private final String mAppPackageName;
+ private final String mSdkPackageName;
+ @DeletionRequest.DeletionMode private final int mDeletionMode;
+ @DeletionRequest.MatchBehavior private final int mMatchBehavior;
+
+ private DeletionParam(@NonNull Builder builder) {
+ mOriginUris = builder.mOriginUris;
+ mDomainUris = builder.mDomainUris;
+ mDeletionMode = builder.mDeletionMode;
+ mMatchBehavior = builder.mMatchBehavior;
+ mStart = builder.mStart;
+ mEnd = builder.mEnd;
+ mAppPackageName = builder.mAppPackageName;
+ mSdkPackageName = builder.mSdkPackageName;
+ }
+
+ /** Unpack an DeletionRequest from a Parcel. */
+ private DeletionParam(Parcel in) {
+ mAppPackageName = in.readString();
+ mSdkPackageName = in.readString();
+
+ mDomainUris = new ArrayList<>();
+ in.readTypedList(mDomainUris, Uri.CREATOR);
+
+ mOriginUris = new ArrayList<>();
+ in.readTypedList(mOriginUris, Uri.CREATOR);
+
+ boolean hasStart = in.readBoolean();
+ if (hasStart) {
+ mStart = Instant.parse(in.readString());
+ } else {
+ mStart = null;
+ }
+
+ boolean hasEnd = in.readBoolean();
+ if (hasEnd) {
+ mEnd = Instant.parse(in.readString());
+ } else {
+ mEnd = null;
+ }
+
+ mDeletionMode = in.readInt();
+ mMatchBehavior = in.readInt();
+ }
+
+ /** Creator for Parcelable (via reflection). */
+ @NonNull
+ public static final Parcelable.Creator<DeletionParam> CREATOR =
+ new Parcelable.Creator<DeletionParam>() {
+ @Override
+ public DeletionParam createFromParcel(Parcel in) {
+ return new DeletionParam(in);
+ }
+
+ @Override
+ public DeletionParam[] newArray(int size) {
+ return new DeletionParam[size];
+ }
+ };
+
+ /** For Parcelable, no special marshalled objects. */
+ public int describeContents() {
+ return 0;
+ }
+
+ /** For Parcelable, write out to a Parcel in particular order. */
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ Objects.requireNonNull(out);
+ out.writeString(mAppPackageName);
+ out.writeString(mSdkPackageName);
+
+ out.writeTypedList(mDomainUris);
+
+ out.writeTypedList(mOriginUris);
+
+ if (mStart != null) {
+ out.writeBoolean(true);
+ out.writeString(mStart.toString());
+ } else {
+ out.writeBoolean(false);
+ }
+
+ if (mEnd != null) {
+ out.writeBoolean(true);
+ out.writeString(mEnd.toString());
+ } else {
+ out.writeBoolean(false);
+ }
+
+ out.writeInt(mDeletionMode);
+
+ out.writeInt(mMatchBehavior);
+ }
+
+ /**
+ * Publisher/Advertiser Origins for which data should be deleted. These will be matched as-is.
+ */
+ @NonNull
+ public List<Uri> getOriginUris() {
+ return mOriginUris;
+ }
+
+ /**
+ * Publisher/Advertiser domains for which data should be deleted. These will be pattern matched
+ * with regex SCHEME://(.*\.|)SITE .
+ */
+ @NonNull
+ public List<Uri> getDomainUris() {
+ return mDomainUris;
+ }
+
+ /** Deletion mode for matched records. */
+ @DeletionRequest.DeletionMode
+ public int getDeletionMode() {
+ return mDeletionMode;
+ }
+
+ /** Match behavior for provided origins/domains. */
+ @DeletionRequest.MatchBehavior
+ public int getMatchBehavior() {
+ return mMatchBehavior;
+ }
+
+ /**
+ * Instant in time the deletion starts, or {@link java.time.Instant#MIN} if starting at the
+ * oldest possible time.
+ */
+ @NonNull
+ public Instant getStart() {
+ return mStart;
+ }
+
+ /**
+ * Instant in time the deletion ends, or {@link java.time.Instant#MAX} if ending at the most
+ * recent time.
+ */
+ @NonNull
+ public Instant getEnd() {
+ return mEnd;
+ }
+
+ /** Package name of the app used for the deletion. */
+ @NonNull
+ public String getAppPackageName() {
+ return mAppPackageName;
+ }
+
+ /** Package name of the sdk used for the deletion. */
+ @NonNull
+ public String getSdkPackageName() {
+ return mSdkPackageName;
+ }
+
+ /** A builder for {@link DeletionParam}. */
+ public static final class Builder {
+ private final List<Uri> mOriginUris;
+ private final List<Uri> mDomainUris;
+ private final Instant mStart;
+ private final Instant mEnd;
+ private final String mAppPackageName;
+ private final String mSdkPackageName;
+ @DeletionRequest.DeletionMode private int mDeletionMode;
+ @DeletionRequest.MatchBehavior private int mMatchBehavior;
+
+ /**
+ * Builder constructor for {@link DeletionParam}.
+ *
+ * @param originUris see {@link DeletionParam#getOriginUris()}
+ * @param domainUris see {@link DeletionParam#getDomainUris()}
+ * @param start see {@link DeletionParam#getStart()}
+ * @param end see {@link DeletionParam#getEnd()}
+ * @param appPackageName see {@link DeletionParam#getAppPackageName()}
+ * @param sdkPackageName see {@link DeletionParam#getSdkPackageName()}
+ */
+ public Builder(
+ @NonNull List<Uri> originUris,
+ @NonNull List<Uri> domainUris,
+ @NonNull Instant start,
+ @NonNull Instant end,
+ @NonNull String appPackageName,
+ @NonNull String sdkPackageName) {
+ Objects.requireNonNull(originUris);
+ Objects.requireNonNull(domainUris);
+ Objects.requireNonNull(start);
+ Objects.requireNonNull(end);
+ Objects.requireNonNull(appPackageName);
+ Objects.requireNonNull(sdkPackageName);
+
+ mOriginUris = originUris;
+ mDomainUris = domainUris;
+ mStart = start;
+ mEnd = end;
+ mAppPackageName = appPackageName;
+ mSdkPackageName = sdkPackageName;
+ }
+
+ /** See {@link DeletionParam#getDeletionMode()}. */
+ @NonNull
+ public Builder setDeletionMode(@DeletionRequest.DeletionMode int deletionMode) {
+ mDeletionMode = deletionMode;
+ return this;
+ }
+
+ /** See {@link DeletionParam#getDeletionMode()}. */
+ @NonNull
+ public Builder setMatchBehavior(@DeletionRequest.MatchBehavior int matchBehavior) {
+ mMatchBehavior = matchBehavior;
+ return this;
+ }
+
+ /** Build the DeletionRequest. */
+ @NonNull
+ public DeletionParam build() {
+ return new DeletionParam(this);
+ }
+ }
+}
diff --git a/android-35/android/adservices/measurement/DeletionRequest.java b/android-35/android/adservices/measurement/DeletionRequest.java
new file mode 100644
index 0000000..0fc5f4f
--- /dev/null
+++ b/android-35/android/adservices/measurement/DeletionRequest.java
@@ -0,0 +1,210 @@
+/*
+ * 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.measurement;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.Uri;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/** Deletion Request. */
+public class DeletionRequest {
+
+ /**
+ * Deletion modes for matched records.
+ *
+ * @hide
+ */
+ @IntDef(value = {DELETION_MODE_ALL, DELETION_MODE_EXCLUDE_INTERNAL_DATA})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface DeletionMode {}
+
+ /**
+ * Matching Behaviors for params.
+ *
+ * @hide
+ */
+ @IntDef(value = {MATCH_BEHAVIOR_DELETE, MATCH_BEHAVIOR_PRESERVE})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface MatchBehavior {}
+
+ /** Deletion mode to delete all data associated with the selected records. */
+ public static final int DELETION_MODE_ALL = 0;
+
+ /**
+ * Deletion mode to delete all data except the internal data (e.g. rate limits) for the selected
+ * records.
+ */
+ public static final int DELETION_MODE_EXCLUDE_INTERNAL_DATA = 1;
+
+ /** Match behavior option to delete the supplied params (Origin/Domains). */
+ public static final int MATCH_BEHAVIOR_DELETE = 0;
+
+ /**
+ * Match behavior option to preserve the supplied params (Origin/Domains) and delete everything
+ * else.
+ */
+ public static final int MATCH_BEHAVIOR_PRESERVE = 1;
+
+ private final Instant mStart;
+ private final Instant mEnd;
+ private final List<Uri> mOriginUris;
+ private final List<Uri> mDomainUris;
+ private final @MatchBehavior int mMatchBehavior;
+ private final @DeletionMode int mDeletionMode;
+
+ private DeletionRequest(@NonNull Builder builder) {
+ mOriginUris = builder.mOriginUris;
+ mDomainUris = builder.mDomainUris;
+ mMatchBehavior = builder.mMatchBehavior;
+ mDeletionMode = builder.mDeletionMode;
+ mStart = builder.mStart;
+ mEnd = builder.mEnd;
+ }
+
+ /** Get the list of origin URIs. */
+ @NonNull
+ public List<Uri> getOriginUris() {
+ return mOriginUris;
+ }
+
+ /** Get the list of domain URIs. */
+ @NonNull
+ public List<Uri> getDomainUris() {
+ return mDomainUris;
+ }
+
+ /** Get the deletion mode. */
+ public @DeletionMode int getDeletionMode() {
+ return mDeletionMode;
+ }
+
+ /** Get the match behavior. */
+ public @MatchBehavior int getMatchBehavior() {
+ return mMatchBehavior;
+ }
+
+ /** Get the start of the deletion range. */
+ @NonNull
+ public Instant getStart() {
+ return mStart;
+ }
+
+ /** Get the end of the deletion range. */
+ @NonNull
+ public Instant getEnd() {
+ return mEnd;
+ }
+
+ /** Builder for {@link DeletionRequest} objects. */
+ public static final class Builder {
+ private Instant mStart = Instant.MIN;
+ private Instant mEnd = Instant.MAX;
+ private List<Uri> mOriginUris;
+ private List<Uri> mDomainUris;
+ @MatchBehavior private int mMatchBehavior;
+ @DeletionMode private int mDeletionMode;
+
+ public Builder() {}
+
+ /**
+ * Set the list of origin URI which will be used for matching. These will be matched with
+ * records using the same origin only, i.e. subdomains won't match. E.g. If originUri is
+ * {@code https://a.example.com}, then {@code https://a.example.com} will match; {@code
+ * https://example.com}, {@code https://b.example.com} and {@code https://abcexample.com}
+ * will NOT match.
+ */
+ public @NonNull Builder setOriginUris(@Nullable List<Uri> originUris) {
+ mOriginUris = originUris;
+ return this;
+ }
+
+ /**
+ * Set the list of domain URI which will be used for matching. These will be matched with
+ * records using the same domain or any subdomains. E.g. If domainUri is {@code
+ * https://example.com}, then {@code https://a.example.com}, {@code https://example.com} and
+ * {@code https://b.example.com} will match; {@code https://abcexample.com} will NOT match.
+ */
+ public @NonNull Builder setDomainUris(@Nullable List<Uri> domainUris) {
+ mDomainUris = domainUris;
+ return this;
+ }
+
+ /**
+ * Set the match behavior for the supplied params. {@link #MATCH_BEHAVIOR_DELETE}: This
+ * option will use the supplied params (Origin URIs & Domain URIs) for selecting records for
+ * deletion. {@link #MATCH_BEHAVIOR_PRESERVE}: This option will preserve the data associated
+ * with the supplied params (Origin URIs & Domain URIs) and select remaining records for
+ * deletion.
+ */
+ public @NonNull Builder setMatchBehavior(@MatchBehavior int matchBehavior) {
+ mMatchBehavior = matchBehavior;
+ return this;
+ }
+
+ /**
+ * Set the match behavior for the supplied params. {@link #DELETION_MODE_ALL}: All data
+ * associated with the selected records will be deleted. {@link
+ * #DELETION_MODE_EXCLUDE_INTERNAL_DATA}: All data except the internal system data (e.g.
+ * rate limits) associated with the selected records will be deleted.
+ */
+ public @NonNull Builder setDeletionMode(@DeletionMode int deletionMode) {
+ mDeletionMode = deletionMode;
+ return this;
+ }
+
+ /**
+ * Set the start of the deletion range. Passing in {@link java.time.Instant#MIN} will cause
+ * everything from the oldest record to the specified end be deleted. No set start will
+ * default to {@link java.time.Instant#MIN}.
+ */
+ public @NonNull Builder setStart(@NonNull Instant start) {
+ Objects.requireNonNull(start);
+ mStart = start;
+ return this;
+ }
+
+ /**
+ * Set the end of the deletion range. Passing in {@link java.time.Instant#MAX} will cause
+ * everything from the specified start until the newest record to be deleted. No set end
+ * will default to {@link java.time.Instant#MAX}.
+ */
+ public @NonNull Builder setEnd(@NonNull Instant end) {
+ Objects.requireNonNull(end);
+ mEnd = end;
+ return this;
+ }
+
+ /** Builds a {@link DeletionRequest} instance. */
+ public @NonNull DeletionRequest build() {
+ if (mDomainUris == null) {
+ mDomainUris = new ArrayList<>();
+ }
+ if (mOriginUris == null) {
+ mOriginUris = new ArrayList<>();
+ }
+ return new DeletionRequest(this);
+ }
+ }
+}
diff --git a/android-35/android/adservices/measurement/MeasurementCompatibleManager.java b/android-35/android/adservices/measurement/MeasurementCompatibleManager.java
new file mode 100644
index 0000000..58ac44e
--- /dev/null
+++ b/android-35/android/adservices/measurement/MeasurementCompatibleManager.java
@@ -0,0 +1,742 @@
+/*
+ * 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.measurement;
+
+import static android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_ATTRIBUTION;
+
+import android.adservices.adid.AdId;
+import android.adservices.adid.AdIdCompatibleManager;
+import android.adservices.common.AdServicesOutcomeReceiver;
+import android.adservices.common.AdServicesStatusUtils;
+import android.adservices.common.CallerMetadata;
+import android.adservices.common.SandboxedSdkContextUtils;
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
+import android.app.sdksandbox.SandboxedSdkContext;
+import android.content.Context;
+import android.net.Uri;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.view.InputEvent;
+
+import com.android.adservices.AdServicesCommon;
+import com.android.adservices.LogUtil;
+import com.android.adservices.ServiceBinder;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Objects;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * MeasurementManager provides APIs to manage source and trigger registrations.
+ *
+ * @hide
+ */
+public class MeasurementCompatibleManager {
+ private interface MeasurementAdIdCallback {
+ void onAdIdCallback(boolean isAdIdEnabled, @Nullable String adIdValue);
+ }
+
+ private static final long AD_ID_TIMEOUT_MS = 400;
+
+ private final Context mContext;
+ private final ServiceBinder<IMeasurementService> mServiceBinder;
+ private AdIdCompatibleManager mAdIdManager;
+ private final Executor mAdIdExecutor = Executors.newCachedThreadPool();
+
+ private static final String DEBUG_API_WARNING_MESSAGE =
+ "To enable debug api, include ACCESS_ADSERVICES_AD_ID "
+ + "permission and enable advertising ID under device settings";
+
+ /**
+ * This is for test purposes, it helps to mock the adIdManager.
+ *
+ * @hide
+ */
+ @NonNull
+ public static MeasurementCompatibleManager get(@NonNull Context context) {
+ return new MeasurementCompatibleManager(context);
+ }
+
+ /**
+ * This is for test purposes, it helps to mock the adIdManager.
+ *
+ * @hide
+ */
+ @VisibleForTesting
+ @NonNull
+ public static MeasurementCompatibleManager get(
+ @NonNull Context context, @NonNull AdIdCompatibleManager adIdManager) {
+ MeasurementCompatibleManager measurementManager = MeasurementCompatibleManager.get(context);
+ measurementManager.mAdIdManager = adIdManager;
+ return measurementManager;
+ }
+
+ /**
+ * Create MeasurementCompatibleManager.
+ *
+ * @hide
+ */
+ private MeasurementCompatibleManager(Context context) {
+ mContext = context;
+ mServiceBinder =
+ ServiceBinder.getServiceBinder(
+ context,
+ AdServicesCommon.ACTION_MEASUREMENT_SERVICE,
+ IMeasurementService.Stub::asInterface);
+ mAdIdManager = new AdIdCompatibleManager(context);
+ }
+
+ /**
+ * Retrieves an {@link IMeasurementService} implementation
+ *
+ * @hide
+ */
+ @VisibleForTesting
+ @NonNull
+ public IMeasurementService getService() throws IllegalStateException {
+ IMeasurementService service = mServiceBinder.getService();
+ if (service == null) {
+ throw new IllegalStateException("Unable to find the service");
+ }
+ return service;
+ }
+
+ /** Checks if Ad ID permission is enabled. */
+ private boolean isAdIdPermissionEnabled(AdId adId) {
+ return !AdId.ZERO_OUT.equals(adId.getAdId());
+ }
+
+ /**
+ * Register an attribution source / trigger.
+ *
+ * @hide
+ */
+ private void register(
+ @NonNull RegistrationRequest registrationRequest,
+ @NonNull IMeasurementService service,
+ @Nullable @CallbackExecutor Executor executor,
+ @Nullable AdServicesOutcomeReceiver<Object, Exception> callback) {
+ Objects.requireNonNull(registrationRequest);
+ requireExecutorForCallback(executor, callback);
+
+ String registrationType = "source";
+ if (registrationRequest.getRegistrationType() == RegistrationRequest.REGISTER_TRIGGER) {
+ registrationType = "trigger";
+ }
+ LogUtil.d("Registering " + registrationType);
+
+ try {
+ service.register(
+ registrationRequest,
+ generateCallerMetadataWithCurrentTime(),
+ new IMeasurementCallback.Stub() {
+ @Override
+ public void onResult() {
+ if (callback != null) {
+ executor.execute(() -> callback.onResult(new Object()));
+ }
+ }
+
+ @Override
+ public void onFailure(MeasurementErrorResponse failureParcel) {
+ if (callback != null) {
+ executor.execute(
+ () ->
+ callback.onError(
+ AdServicesStatusUtils.asException(
+ failureParcel)));
+ }
+ }
+ });
+ } catch (RemoteException e) {
+ LogUtil.e(e, "RemoteException");
+ if (callback != null) {
+ executor.execute(() -> callback.onError(new IllegalStateException(e)));
+ }
+ }
+ }
+
+ /**
+ * Register an attribution source (click or view).
+ *
+ * @param attributionSource the platform issues a request to this URI in order to fetch metadata
+ * associated with the attribution source. The source metadata is stored on device, making
+ * it eligible to be matched to future triggers.
+ * @param inputEvent either an {@link InputEvent} object (for a click event) or null (for a view
+ * event).
+ * @param executor used by callback to dispatch results.
+ * @param callback intended to notify asynchronously the API result.
+ */
+ @RequiresPermission(ACCESS_ADSERVICES_ATTRIBUTION)
+ public void registerSource(
+ @NonNull Uri attributionSource,
+ @Nullable InputEvent inputEvent,
+ @Nullable @CallbackExecutor Executor executor,
+ @Nullable AdServicesOutcomeReceiver<Object, Exception> callback) {
+ Objects.requireNonNull(attributionSource);
+ requireExecutorForCallback(executor, callback);
+
+ IMeasurementService service = getServiceWrapper(executor, callback);
+
+ if (service == null) {
+ // Error was sent in the callback by getServiceWrapper call
+ LogUtil.d("Measurement service not found");
+ return;
+ }
+
+ final RegistrationRequest.Builder builder =
+ new RegistrationRequest.Builder(
+ RegistrationRequest.REGISTER_SOURCE,
+ attributionSource,
+ getAppPackageName(),
+ getSdkPackageName())
+ .setRequestTime(SystemClock.uptimeMillis())
+ .setInputEvent(inputEvent);
+ // TODO(b/281546062): Can probably remove isAdIdEnabled, since whether adIdValue is null or
+ // not will determine if adId is enabled.
+ getAdId(
+ (isAdIdEnabled, adIdValue) ->
+ register(
+ builder.setAdIdPermissionGranted(isAdIdEnabled)
+ .setAdIdValue(adIdValue)
+ .build(),
+ service,
+ executor,
+ callback));
+ }
+
+ /**
+ * Register attribution sources(click or view) from an app context. This API will not process
+ * any redirects, all registration URLs should be supplied with the request.
+ *
+ * @param request app source registration request
+ * @param executor used by callback to dispatch results
+ * @param callback intended to notify asynchronously the API result
+ */
+ @RequiresPermission(ACCESS_ADSERVICES_ATTRIBUTION)
+ public void registerSource(
+ @NonNull SourceRegistrationRequest request,
+ @Nullable @CallbackExecutor Executor executor,
+ @Nullable AdServicesOutcomeReceiver<Object, Exception> callback) {
+ Objects.requireNonNull(request);
+ requireExecutorForCallback(executor, callback);
+
+ IMeasurementService service = getServiceWrapper(executor, callback);
+ if (service == null) {
+ // Error was sent in the callback by getServiceWrapper call
+ LogUtil.d("Measurement service not found");
+ return;
+ }
+
+ CallerMetadata callerMetadata = generateCallerMetadataWithCurrentTime();
+ IMeasurementCallback measurementCallback =
+ new IMeasurementCallback.Stub() {
+ @Override
+ public void onResult() {
+ if (callback != null) {
+ executor.execute(() -> callback.onResult(new Object()));
+ }
+ }
+
+ @Override
+ public void onFailure(MeasurementErrorResponse failureParcel) {
+ if (callback != null) {
+ executor.execute(
+ () ->
+ callback.onError(
+ AdServicesStatusUtils.asException(
+ failureParcel)));
+ }
+ }
+ };
+
+ final SourceRegistrationRequestInternal.Builder builder =
+ new SourceRegistrationRequestInternal.Builder(
+ request,
+ getAppPackageName(),
+ getSdkPackageName(),
+ SystemClock.uptimeMillis());
+
+ getAdId(
+ (isAdIdEnabled, adIdValue) -> {
+ try {
+ LogUtil.d("Registering app sources");
+ service.registerSource(
+ builder.setAdIdValue(adIdValue).build(),
+ callerMetadata,
+ measurementCallback);
+ } catch (RemoteException e) {
+ LogUtil.e(e, "RemoteException");
+ if (callback != null) {
+ executor.execute(() -> callback.onError(new IllegalStateException(e)));
+ }
+ }
+ });
+ }
+
+ /**
+ * Register an attribution source(click or view) from web context. This API will not process any
+ * redirects, all registration URLs should be supplied with the request. At least one of
+ * appDestination or webDestination parameters are required to be provided. If the registration
+ * is successful, {@code callback}'s {@link AdServicesOutcomeReceiver#onResult} is invoked with
+ * null. In case of failure, a {@link Exception} is sent through {@code callback}'s {@link
+ * AdServicesOutcomeReceiver#onError}. Both success and failure feedback are executed on the
+ * provided {@link Executor}.
+ *
+ * @param request source registration request
+ * @param executor used by callback to dispatch results.
+ * @param callback intended to notify asynchronously the API result.
+ */
+ @RequiresPermission(ACCESS_ADSERVICES_ATTRIBUTION)
+ public void registerWebSource(
+ @NonNull WebSourceRegistrationRequest request,
+ @Nullable Executor executor,
+ @Nullable AdServicesOutcomeReceiver<Object, Exception> callback) {
+ Objects.requireNonNull(request);
+ requireExecutorForCallback(executor, callback);
+
+ IMeasurementService service = getServiceWrapper(executor, callback);
+
+ if (service == null) {
+ // Error was sent in the callback by getServiceWrapper call
+ LogUtil.d("Measurement service not found");
+ return;
+ }
+
+ CallerMetadata callerMetadata = generateCallerMetadataWithCurrentTime();
+ IMeasurementCallback measurementCallback =
+ new IMeasurementCallback.Stub() {
+ @Override
+ public void onResult() {
+ if (callback != null) {
+ executor.execute(() -> callback.onResult(new Object()));
+ }
+ }
+
+ @Override
+ public void onFailure(MeasurementErrorResponse failureParcel) {
+ if (callback != null) {
+ executor.execute(
+ () ->
+ callback.onError(
+ AdServicesStatusUtils.asException(
+ failureParcel)));
+ }
+ }
+ };
+
+ final WebSourceRegistrationRequestInternal.Builder builder =
+ new WebSourceRegistrationRequestInternal.Builder(
+ request,
+ getAppPackageName(),
+ getSdkPackageName(),
+ SystemClock.uptimeMillis());
+
+ getAdId(
+ (isAdIdEnabled, adIdValue) ->
+ registerWebSourceWrapper(
+ builder.setAdIdPermissionGranted(isAdIdEnabled).build(),
+ service,
+ executor,
+ callerMetadata,
+ measurementCallback,
+ callback));
+ }
+
+ /** Wrapper method for registerWebSource. */
+ private void registerWebSourceWrapper(
+ @NonNull WebSourceRegistrationRequestInternal request,
+ @NonNull IMeasurementService service,
+ @Nullable Executor executor,
+ @NonNull CallerMetadata callerMetadata,
+ @NonNull IMeasurementCallback measurementCallback,
+ @Nullable AdServicesOutcomeReceiver<Object, Exception> callback) {
+ requireExecutorForCallback(executor, callback);
+ try {
+ LogUtil.d("Registering web source");
+ service.registerWebSource(request, callerMetadata, measurementCallback);
+ } catch (RemoteException e) {
+ LogUtil.e(e, "RemoteException");
+ if (callback != null) {
+ executor.execute(() -> callback.onError(new IllegalStateException(e)));
+ }
+ }
+ }
+
+ /**
+ * Register an attribution trigger(click or view) from web context. This API will not process
+ * any redirects, all registration URLs should be supplied with the request. If the registration
+ * is successful, {@code callback}'s {@link AdServicesOutcomeReceiver#onResult} is invoked with
+ * null. In case of failure, a {@link Exception} is sent through {@code callback}'s {@link
+ * AdServicesOutcomeReceiver#onError}. Both success and failure feedback are executed on the
+ * provided {@link Executor}.
+ *
+ * @param request trigger registration request
+ * @param executor used by callback to dispatch results
+ * @param callback intended to notify asynchronously the API result
+ */
+ @RequiresPermission(ACCESS_ADSERVICES_ATTRIBUTION)
+ public void registerWebTrigger(
+ @NonNull WebTriggerRegistrationRequest request,
+ @Nullable Executor executor,
+ @Nullable AdServicesOutcomeReceiver<Object, Exception> callback) {
+ Objects.requireNonNull(request);
+ requireExecutorForCallback(executor, callback);
+
+ IMeasurementService service = getServiceWrapper(executor, callback);
+
+ if (service == null) {
+ // Error was sent in the callback by getServiceWrapper call
+ LogUtil.d("Measurement service not found");
+ return;
+ }
+
+ CallerMetadata callerMetadata = generateCallerMetadataWithCurrentTime();
+ IMeasurementCallback measurementCallback =
+ new IMeasurementCallback.Stub() {
+ @Override
+ public void onResult() {
+ if (callback != null) {
+ executor.execute(() -> callback.onResult(new Object()));
+ }
+ }
+
+ @Override
+ public void onFailure(MeasurementErrorResponse failureParcel) {
+ if (callback != null) {
+ executor.execute(
+ () ->
+ callback.onError(
+ AdServicesStatusUtils.asException(
+ failureParcel)));
+ }
+ }
+ };
+
+ WebTriggerRegistrationRequestInternal.Builder builder =
+ new WebTriggerRegistrationRequestInternal.Builder(
+ request, getAppPackageName(), getSdkPackageName());
+
+ getAdId(
+ (isAdIdEnabled, adIdValue) ->
+ registerWebTriggerWrapper(
+ builder.setAdIdPermissionGranted(isAdIdEnabled).build(),
+ service,
+ executor,
+ callerMetadata,
+ measurementCallback,
+ callback));
+ }
+
+ /** Wrapper method for registerWebTrigger. */
+ private void registerWebTriggerWrapper(
+ @NonNull WebTriggerRegistrationRequestInternal request,
+ @NonNull IMeasurementService service,
+ @Nullable Executor executor,
+ @NonNull CallerMetadata callerMetadata,
+ @NonNull IMeasurementCallback measurementCallback,
+ @Nullable AdServicesOutcomeReceiver<Object, Exception> callback) {
+ requireExecutorForCallback(executor, callback);
+ try {
+ LogUtil.d("Registering web trigger");
+ service.registerWebTrigger(request, callerMetadata, measurementCallback);
+ } catch (RemoteException e) {
+ LogUtil.e(e, "RemoteException");
+ if (callback != null) {
+ executor.execute(() -> callback.onError(new IllegalStateException(e)));
+ }
+ }
+ }
+
+ /**
+ * Register a trigger (conversion).
+ *
+ * @param trigger the API issues a request to this URI to fetch metadata associated with the
+ * trigger. The trigger metadata is stored on-device, and is eligible to be matched with
+ * sources during the attribution process.
+ * @param executor used by callback to dispatch results.
+ * @param callback intended to notify asynchronously the API result.
+ */
+ @RequiresPermission(ACCESS_ADSERVICES_ATTRIBUTION)
+ public void registerTrigger(
+ @NonNull Uri trigger,
+ @Nullable @CallbackExecutor Executor executor,
+ @Nullable AdServicesOutcomeReceiver<Object, Exception> callback) {
+ Objects.requireNonNull(trigger);
+ requireExecutorForCallback(executor, callback);
+
+ IMeasurementService service = getServiceWrapper(executor, callback);
+
+ if (service == null) {
+ // Error was sent in the callback by getServiceWrapper call
+ LogUtil.d("Measurement service not found");
+ return;
+ }
+
+ final RegistrationRequest.Builder builder =
+ new RegistrationRequest.Builder(
+ RegistrationRequest.REGISTER_TRIGGER,
+ trigger,
+ getAppPackageName(),
+ getSdkPackageName());
+ // TODO(b/281546062)
+ getAdId(
+ (isAdIdEnabled, adIdValue) ->
+ register(
+ builder.setAdIdPermissionGranted(isAdIdEnabled)
+ .setAdIdValue(adIdValue)
+ .build(),
+ service,
+ executor,
+ callback));
+ }
+
+ /**
+ * Delete previously registered data.
+ *
+ * @hide
+ */
+ private void deleteRegistrations(
+ @NonNull DeletionParam deletionParam,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull AdServicesOutcomeReceiver<Object, Exception> callback) {
+ Objects.requireNonNull(deletionParam);
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+ final IMeasurementService service = getServiceWrapper(executor, callback);
+
+ if (service == null) {
+ // Error was sent in the callback by getServiceWrapper call
+ LogUtil.d("Measurement service not found");
+ return;
+ }
+
+ try {
+ service.deleteRegistrations(
+ deletionParam,
+ generateCallerMetadataWithCurrentTime(),
+ new IMeasurementCallback.Stub() {
+ @Override
+ public void onResult() {
+ executor.execute(() -> callback.onResult(new Object()));
+ }
+
+ @Override
+ public void onFailure(MeasurementErrorResponse failureParcel) {
+ executor.execute(
+ () ->
+ callback.onError(
+ AdServicesStatusUtils.asException(
+ failureParcel)));
+ }
+ });
+ } catch (RemoteException e) {
+ LogUtil.e(e, "RemoteException");
+ executor.execute(() -> callback.onError(new IllegalStateException(e)));
+ }
+ }
+
+ /**
+ * Delete previous registrations. If the deletion is successful, the callback's {@link
+ * AdServicesOutcomeReceiver#onResult} is invoked with null. In case of failure, a {@link
+ * Exception} is sent through the callback's {@link AdServicesOutcomeReceiver#onError}. Both
+ * success and failure feedback are executed on the provided {@link Executor}.
+ *
+ * @param deletionRequest The request for deleting data.
+ * @param executor The executor to run callback.
+ * @param callback intended to notify asynchronously the API result.
+ */
+ public void deleteRegistrations(
+ @NonNull DeletionRequest deletionRequest,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull AdServicesOutcomeReceiver<Object, Exception> callback) {
+ deleteRegistrations(
+ new DeletionParam.Builder(
+ deletionRequest.getOriginUris(),
+ deletionRequest.getDomainUris(),
+ deletionRequest.getStart(),
+ deletionRequest.getEnd(),
+ getAppPackageName(),
+ getSdkPackageName())
+ .setDeletionMode(deletionRequest.getDeletionMode())
+ .setMatchBehavior(deletionRequest.getMatchBehavior())
+ .build(),
+ executor,
+ callback);
+ }
+
+ /**
+ * Get Measurement API status.
+ *
+ * <p>The callback's {@code Integer} value is one of {@code MeasurementApiState}.
+ *
+ * @param executor used by callback to dispatch results.
+ * @param callback intended to notify asynchronously the API result.
+ */
+ @RequiresPermission(ACCESS_ADSERVICES_ATTRIBUTION)
+ public void getMeasurementApiStatus(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull AdServicesOutcomeReceiver<Integer, Exception> callback) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+
+ final IMeasurementService service;
+ try {
+ service = getService();
+ } catch (IllegalStateException e) {
+ LogUtil.e(e, "Failed to bind to measurement service");
+ executor.execute(
+ () -> callback.onResult(MeasurementManager.MEASUREMENT_API_STATE_DISABLED));
+ return;
+ } catch (RuntimeException e) {
+ LogUtil.e(e, "Unknown failure while binding measurement service");
+ executor.execute(() -> callback.onError(e));
+ return;
+ }
+
+ try {
+ service.getMeasurementApiStatus(
+ new StatusParam.Builder(getAppPackageName(), getSdkPackageName()).build(),
+ generateCallerMetadataWithCurrentTime(),
+ new IMeasurementApiStatusCallback.Stub() {
+ @Override
+ public void onResult(int result) {
+ executor.execute(() -> callback.onResult(result));
+ }
+ });
+ } catch (RemoteException e) {
+ LogUtil.e(e, "RemoteException");
+ executor.execute(
+ () -> callback.onResult(MeasurementManager.MEASUREMENT_API_STATE_DISABLED));
+ } catch (RuntimeException e) {
+ LogUtil.e(e, "Unknown failure while getting measurement status");
+ executor.execute(() -> callback.onError(e));
+ }
+ }
+
+ /**
+ * If the service is in an APK (as opposed to the system service), unbind it from the service to
+ * allow the APK process to die.
+ *
+ * @hide Not sure if we'll need this functionality in the final API. For now, we need it for
+ * performance testing to simulate "cold-start" situations.
+ */
+ @VisibleForTesting
+ public void unbindFromService() {
+ mServiceBinder.unbindFromService();
+ }
+
+ /** Returns the package name of the app from the SDK or app context */
+ private String getAppPackageName() {
+ SandboxedSdkContext sandboxedSdkContext =
+ SandboxedSdkContextUtils.getAsSandboxedSdkContext(mContext);
+ return sandboxedSdkContext == null
+ ? mContext.getPackageName()
+ : sandboxedSdkContext.getClientPackageName();
+ }
+
+ /** Returns the package name of the sdk from the SDK or empty if no SDK found */
+ private String getSdkPackageName() {
+ SandboxedSdkContext sandboxedSdkContext =
+ SandboxedSdkContextUtils.getAsSandboxedSdkContext(mContext);
+ return sandboxedSdkContext == null ? "" : sandboxedSdkContext.getSdkPackageName();
+ }
+
+ private CallerMetadata generateCallerMetadataWithCurrentTime() {
+ return new CallerMetadata.Builder()
+ .setBinderElapsedTimestamp(SystemClock.elapsedRealtime())
+ .build();
+ }
+
+ /** Get Service wrapper, propagates error to the caller */
+ @Nullable
+ private IMeasurementService getServiceWrapper(
+ @Nullable @CallbackExecutor Executor executor,
+ @Nullable AdServicesOutcomeReceiver<Object, Exception> callback) {
+ requireExecutorForCallback(executor, callback);
+ IMeasurementService service = null;
+ try {
+ service = getService();
+ } catch (RuntimeException e) {
+ LogUtil.e(e, "Failed binding to measurement service");
+ if (callback != null) {
+ executor.execute(() -> callback.onError(e));
+ }
+ }
+ return service;
+ }
+
+ private static void requireExecutorForCallback(
+ Executor executor, AdServicesOutcomeReceiver<Object, Exception> callback) {
+ if (callback != null && executor == null) {
+ throw new IllegalArgumentException(
+ "Executor should be provided when callback is provided.");
+ }
+ }
+
+ /* Make AdId call with timeout */
+ @SuppressLint("MissingPermission")
+ private void getAdId(MeasurementAdIdCallback measurementAdIdCallback) {
+ CountDownLatch countDownLatch = new CountDownLatch(1);
+ AtomicBoolean isAdIdEnabled = new AtomicBoolean();
+ AtomicReference<String> adIdValue = new AtomicReference<>();
+ mAdIdManager.getAdId(
+ mAdIdExecutor,
+ new AdServicesOutcomeReceiver<>() {
+ @Override
+ public void onResult(AdId adId) {
+ isAdIdEnabled.set(isAdIdPermissionEnabled(adId));
+ adIdValue.set(adId.getAdId().equals(AdId.ZERO_OUT) ? null : adId.getAdId());
+ LogUtil.d("AdId permission enabled %b", isAdIdEnabled.get());
+ countDownLatch.countDown();
+ }
+
+ @Override
+ public void onError(Exception error) {
+ boolean isExpected =
+ error instanceof IllegalStateException
+ || error instanceof SecurityException;
+ if (isExpected) {
+ LogUtil.w(DEBUG_API_WARNING_MESSAGE);
+ } else {
+ LogUtil.w(error, DEBUG_API_WARNING_MESSAGE);
+ }
+
+ countDownLatch.countDown();
+ }
+ });
+
+ boolean timedOut = false;
+ try {
+ timedOut = !countDownLatch.await(AD_ID_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ LogUtil.w(e, "InterruptedException while waiting for AdId");
+ }
+ if (timedOut) {
+ LogUtil.w("AdId call timed out");
+ }
+ measurementAdIdCallback.onAdIdCallback(isAdIdEnabled.get(), adIdValue.get());
+ }
+}
diff --git a/android-35/android/adservices/measurement/MeasurementErrorResponse.java b/android-35/android/adservices/measurement/MeasurementErrorResponse.java
new file mode 100644
index 0000000..204ed1f
--- /dev/null
+++ b/android-35/android/adservices/measurement/MeasurementErrorResponse.java
@@ -0,0 +1,105 @@
+/*
+ * 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.measurement;
+
+import static android.adservices.common.AdServicesStatusUtils.STATUS_SUCCESS;
+
+import android.adservices.common.AdServicesResponse;
+import android.adservices.common.AdServicesStatusUtils;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Represents a generic response for Measurement APIs.
+ *
+ * @hide
+ */
+public final class MeasurementErrorResponse extends AdServicesResponse {
+ @NonNull
+ public static final Creator<MeasurementErrorResponse> CREATOR =
+ new Parcelable.Creator<MeasurementErrorResponse>() {
+ @Override
+ public MeasurementErrorResponse createFromParcel(@NonNull Parcel in) {
+ Objects.requireNonNull(in);
+ return new MeasurementErrorResponse(in);
+ }
+
+ @Override
+ public MeasurementErrorResponse[] newArray(int size) {
+ return new MeasurementErrorResponse[size];
+ }
+ };
+
+ protected MeasurementErrorResponse(@NonNull Builder builder) {
+ super(builder.mStatusCode, builder.mErrorMessage);
+ }
+
+ protected MeasurementErrorResponse(@NonNull Parcel in) {
+ super(in);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** @hide */
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ Objects.requireNonNull(dest);
+
+ dest.writeInt(mStatusCode);
+ dest.writeString(mErrorMessage);
+ }
+
+ /**
+ * Builder for {@link MeasurementErrorResponse} objects.
+ *
+ * @hide
+ */
+ public static final class Builder {
+ @AdServicesStatusUtils.StatusCode private int mStatusCode = STATUS_SUCCESS;
+ @Nullable private String mErrorMessage;
+
+ public Builder() {}
+
+ /** Set the Status Code. */
+ @NonNull
+ public MeasurementErrorResponse.Builder setStatusCode(
+ @AdServicesStatusUtils.StatusCode int statusCode) {
+ mStatusCode = statusCode;
+ return this;
+ }
+
+ /** Set the Error Message. */
+ @NonNull
+ public MeasurementErrorResponse.Builder setErrorMessage(@Nullable String errorMessage) {
+ mErrorMessage = errorMessage;
+ return this;
+ }
+
+ /** Builds a {@link MeasurementErrorResponse} instance. */
+ @NonNull
+ public MeasurementErrorResponse build() {
+ return new MeasurementErrorResponse(this);
+ }
+ }
+}
diff --git a/android-35/android/adservices/measurement/MeasurementManager.java b/android-35/android/adservices/measurement/MeasurementManager.java
new file mode 100644
index 0000000..d403d84
--- /dev/null
+++ b/android-35/android/adservices/measurement/MeasurementManager.java
@@ -0,0 +1,425 @@
+/*
+ * 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.measurement;
+
+import static android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_ATTRIBUTION;
+
+import android.adservices.common.AdServicesOutcomeReceiver;
+import android.adservices.common.OutcomeReceiverConverter;
+import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.app.sdksandbox.SandboxedSdkContext;
+import android.content.Context;
+import android.net.Uri;
+import android.os.Build;
+import android.os.OutcomeReceiver;
+import android.view.InputEvent;
+
+import androidx.annotation.RequiresApi;
+
+import com.android.adservices.flags.Flags;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/** MeasurementManager provides APIs to manage source and trigger registrations. */
+public class MeasurementManager {
+ /** @hide */
+ public static final String MEASUREMENT_SERVICE = "measurement_service";
+
+ /**
+ * This state indicates that Measurement APIs are unavailable. Invoking them will result in an
+ * {@link UnsupportedOperationException}.
+ */
+ public static final int MEASUREMENT_API_STATE_DISABLED = 0;
+
+ /**
+ * This state indicates that Measurement APIs are enabled.
+ */
+ public static final int MEASUREMENT_API_STATE_ENABLED = 1;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(
+ prefix = "MEASUREMENT_API_STATE_",
+ value = {
+ MEASUREMENT_API_STATE_DISABLED,
+ MEASUREMENT_API_STATE_ENABLED,
+ })
+ public @interface MeasurementApiState {}
+
+ private MeasurementCompatibleManager mImpl;
+
+ /**
+ * Factory method for creating an instance of MeasurementManager.
+ *
+ * @param context The {@link Context} to use
+ * @return A {@link MeasurementManager} instance
+ */
+ @NonNull
+ public static MeasurementManager get(@NonNull Context context) {
+ // On T+, context.getSystemService() does more than just call constructor.
+ return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
+ ? context.getSystemService(MeasurementManager.class)
+ : new MeasurementManager(context);
+ }
+
+ /**
+ * Create MeasurementManager.
+ *
+ * @hide
+ */
+ public MeasurementManager(Context context) {
+ // In case the MeasurementManager is initiated from inside a sdk_sandbox process the
+ // fields will be immediately rewritten by the initialize method below.
+ initialize(context);
+ }
+
+ /**
+ * Create MeasurementManager
+ *
+ * @param compatibleManager the underlying implementation that can be mocked for tests
+ * @hide
+ */
+ @VisibleForTesting
+ public MeasurementManager(@NonNull MeasurementCompatibleManager compatibleManager) {
+ Objects.requireNonNull(compatibleManager);
+ mImpl = compatibleManager;
+ }
+
+ /**
+ * Initializes {@link MeasurementManager} with the given {@code context}.
+ *
+ * <p>This method is called by the {@link SandboxedSdkContext} to propagate the correct context.
+ * For more information check the javadoc on the {@link
+ * android.app.sdksandbox.SdkSandboxSystemServiceRegistry}.
+ *
+ * @hide
+ * @see android.app.sdksandbox.SdkSandboxSystemServiceRegistry
+ */
+ public MeasurementManager initialize(@NonNull Context context) {
+ mImpl = MeasurementCompatibleManager.get(context);
+ return this;
+ }
+
+ /**
+ * Register an attribution source (click or view).
+ *
+ * @param attributionSource the platform issues a request to this URI in order to fetch metadata
+ * associated with the attribution source. The source metadata is stored on device, making
+ * it eligible to be matched to future triggers.
+ * @param inputEvent either an {@link InputEvent} object (for a click event) or null (for a view
+ * event).
+ * @param executor used by callback to dispatch results.
+ * @param callback intended to notify asynchronously the API result.
+ * @throws IllegalArgumentException if the scheme for {@code attributionSource} is not HTTPS
+ */
+ @RequiresApi(Build.VERSION_CODES.S)
+ @RequiresPermission(ACCESS_ADSERVICES_ATTRIBUTION)
+ public void registerSource(
+ @NonNull Uri attributionSource,
+ @Nullable InputEvent inputEvent,
+ @Nullable @CallbackExecutor Executor executor,
+ @Nullable OutcomeReceiver<Object, Exception> callback) {
+ mImpl.registerSource(
+ attributionSource,
+ inputEvent,
+ executor,
+ OutcomeReceiverConverter.toAdServicesOutcomeReceiver(callback));
+ }
+
+ /**
+ * Register an attribution source (click or view). For use on Android R or lower.
+ *
+ * @param attributionSource the platform issues a request to this URI in order to fetch metadata
+ * associated with the attribution source. The source metadata is stored on device, making
+ * it eligible to be matched to future triggers.
+ * @param inputEvent either an {@link InputEvent} object (for a click event) or null (for a view
+ * event).
+ * @param executor used by callback to dispatch results.
+ * @param callback intended to notify asynchronously the API result.
+ */
+ @FlaggedApi(Flags.FLAG_ADSERVICES_OUTCOMERECEIVER_R_API_ENABLED)
+ @RequiresPermission(ACCESS_ADSERVICES_ATTRIBUTION)
+ public void registerSource(
+ @NonNull Uri attributionSource,
+ @Nullable InputEvent inputEvent,
+ @Nullable @CallbackExecutor Executor executor,
+ @Nullable AdServicesOutcomeReceiver<Object, Exception> callback) {
+ mImpl.registerSource(attributionSource, inputEvent, executor, callback);
+ }
+
+ /**
+ * Register attribution sources(click or view) from an app context. This API will not process
+ * any redirects, all registration URLs should be supplied with the request.
+ *
+ * @param request app source registration request
+ * @param executor used by callback to dispatch results
+ * @param callback intended to notify asynchronously the API result
+ */
+ @RequiresApi(Build.VERSION_CODES.S)
+ @RequiresPermission(ACCESS_ADSERVICES_ATTRIBUTION)
+ public void registerSource(
+ @NonNull SourceRegistrationRequest request,
+ @Nullable @CallbackExecutor Executor executor,
+ @Nullable OutcomeReceiver<Object, Exception> callback) {
+ mImpl.registerSource(
+ request, executor, OutcomeReceiverConverter.toAdServicesOutcomeReceiver(callback));
+ }
+
+ /**
+ * Register attribution sources(click or view) from an app context. This API will not process
+ * any redirects, all registration URLs should be supplied with the request. For use on Android
+ * R or lower.
+ *
+ * @param request app source registration request
+ * @param executor used by callback to dispatch results
+ * @param callback intended to notify asynchronously the API result
+ */
+ @FlaggedApi(Flags.FLAG_ADSERVICES_OUTCOMERECEIVER_R_API_ENABLED)
+ @RequiresPermission(ACCESS_ADSERVICES_ATTRIBUTION)
+ public void registerSource(
+ @NonNull SourceRegistrationRequest request,
+ @Nullable @CallbackExecutor Executor executor,
+ @Nullable AdServicesOutcomeReceiver<Object, Exception> callback) {
+ mImpl.registerSource(request, executor, callback);
+ }
+
+ /**
+ * Register an attribution source(click or view) from web context. This API will not process any
+ * redirects, all registration URLs should be supplied with the request. At least one of
+ * appDestination or webDestination parameters are required to be provided. If the registration
+ * is successful, {@code callback}'s {@link OutcomeReceiver#onResult} is invoked with null. In
+ * case of failure, a {@link Exception} is sent through {@code callback}'s {@link
+ * OutcomeReceiver#onError}. Both success and failure feedback are executed on the provided
+ * {@link Executor}.
+ *
+ * @param request source registration request
+ * @param executor used by callback to dispatch results.
+ * @param callback intended to notify asynchronously the API result.
+ */
+ @RequiresApi(Build.VERSION_CODES.S)
+ @RequiresPermission(ACCESS_ADSERVICES_ATTRIBUTION)
+ public void registerWebSource(
+ @NonNull WebSourceRegistrationRequest request,
+ @Nullable Executor executor,
+ @Nullable OutcomeReceiver<Object, Exception> callback) {
+ mImpl.registerWebSource(
+ request, executor, OutcomeReceiverConverter.toAdServicesOutcomeReceiver(callback));
+ }
+
+ /**
+ * Register an attribution source(click or view) from web context. This API will not process any
+ * redirects, all registration URLs should be supplied with the request. At least one of
+ * appDestination or webDestination parameters are required to be provided. If the registration
+ * is successful, {@code callback}'s {@link OutcomeReceiver#onResult} is invoked with null. In
+ * case of failure, a {@link Exception} is sent through {@code callback}'s {@link
+ * OutcomeReceiver#onError}. Both success and failure feedback are executed on the provided
+ * {@link Executor}.
+ *
+ * <p>For use on Android R or lower.
+ *
+ * @param request source registration request
+ * @param executor used by callback to dispatch results.
+ * @param callback intended to notify asynchronously the API result.
+ */
+ @FlaggedApi(Flags.FLAG_ADSERVICES_OUTCOMERECEIVER_R_API_ENABLED)
+ @RequiresPermission(ACCESS_ADSERVICES_ATTRIBUTION)
+ public void registerWebSource(
+ @NonNull WebSourceRegistrationRequest request,
+ @Nullable Executor executor,
+ @Nullable AdServicesOutcomeReceiver<Object, Exception> callback) {
+ mImpl.registerWebSource(request, executor, callback);
+ }
+
+ /**
+ * Register an attribution trigger(click or view) from web context. This API will not process
+ * any redirects, all registration URLs should be supplied with the request. If the registration
+ * is successful, {@code callback}'s {@link OutcomeReceiver#onResult} is invoked with null. In
+ * case of failure, a {@link Exception} is sent through {@code callback}'s {@link
+ * OutcomeReceiver#onError}. Both success and failure feedback are executed on the provided
+ * {@link Executor}.
+ *
+ * @param request trigger registration request
+ * @param executor used by callback to dispatch results
+ * @param callback intended to notify asynchronously the API result
+ */
+ @RequiresApi(Build.VERSION_CODES.S)
+ @RequiresPermission(ACCESS_ADSERVICES_ATTRIBUTION)
+ public void registerWebTrigger(
+ @NonNull WebTriggerRegistrationRequest request,
+ @Nullable Executor executor,
+ @Nullable OutcomeReceiver<Object, Exception> callback) {
+ mImpl.registerWebTrigger(
+ request, executor, OutcomeReceiverConverter.toAdServicesOutcomeReceiver(callback));
+ }
+
+ /**
+ * Register an attribution trigger(click or view) from web context. This API will not process
+ * any redirects, all registration URLs should be supplied with the request. If the registration
+ * is successful, {@code callback}'s {@link OutcomeReceiver#onResult} is invoked with null. In
+ * case of failure, a {@link Exception} is sent through {@code callback}'s {@link
+ * OutcomeReceiver#onError}. Both success and failure feedback are executed on the provided
+ * {@link Executor}.
+ *
+ * <p>For use on Android R or lower.
+ *
+ * @param request trigger registration request
+ * @param executor used by callback to dispatch results
+ * @param callback intended to notify asynchronously the API result
+ */
+ @FlaggedApi(Flags.FLAG_ADSERVICES_OUTCOMERECEIVER_R_API_ENABLED)
+ @RequiresPermission(ACCESS_ADSERVICES_ATTRIBUTION)
+ public void registerWebTrigger(
+ @NonNull WebTriggerRegistrationRequest request,
+ @Nullable Executor executor,
+ @Nullable AdServicesOutcomeReceiver<Object, Exception> callback) {
+ mImpl.registerWebTrigger(request, executor, callback);
+ }
+
+ /**
+ * Register a trigger (conversion).
+ *
+ * @param trigger the API issues a request to this URI to fetch metadata associated with the
+ * trigger. The trigger metadata is stored on-device, and is eligible to be matched with
+ * sources during the attribution process.
+ * @param executor used by callback to dispatch results.
+ * @param callback intended to notify asynchronously the API result.
+ * @throws IllegalArgumentException if the scheme for {@code trigger} is not HTTPS
+ */
+ @RequiresApi(Build.VERSION_CODES.S)
+ @RequiresPermission(ACCESS_ADSERVICES_ATTRIBUTION)
+ public void registerTrigger(
+ @NonNull Uri trigger,
+ @Nullable @CallbackExecutor Executor executor,
+ @Nullable OutcomeReceiver<Object, Exception> callback) {
+ mImpl.registerTrigger(
+ trigger, executor, OutcomeReceiverConverter.toAdServicesOutcomeReceiver(callback));
+ }
+
+ /**
+ * Register a trigger (conversion). For use on Android R or lower.
+ *
+ * @param trigger the API issues a request to this URI to fetch metadata associated with the
+ * trigger. The trigger metadata is stored on-device, and is eligible to be matched with
+ * sources during the attribution process.
+ * @param executor used by callback to dispatch results.
+ * @param callback intended to notify asynchronously the API result.
+ */
+ @FlaggedApi(Flags.FLAG_ADSERVICES_OUTCOMERECEIVER_R_API_ENABLED)
+ @RequiresPermission(ACCESS_ADSERVICES_ATTRIBUTION)
+ public void registerTrigger(
+ @NonNull Uri trigger,
+ @Nullable @CallbackExecutor Executor executor,
+ @Nullable AdServicesOutcomeReceiver<Object, Exception> callback) {
+ mImpl.registerTrigger(trigger, executor, callback);
+ }
+
+ /**
+ * Delete previous registrations. If the deletion is successful, the callback's {@link
+ * OutcomeReceiver#onResult} is invoked with null. In case of failure, a {@link Exception} is
+ * sent through the callback's {@link OutcomeReceiver#onError}. Both success and failure
+ * feedback are executed on the provided {@link Executor}.
+ *
+ * @param deletionRequest The request for deleting data.
+ * @param executor The executor to run callback.
+ * @param callback intended to notify asynchronously the API result.
+ */
+ @RequiresApi(Build.VERSION_CODES.S)
+ public void deleteRegistrations(
+ @NonNull DeletionRequest deletionRequest,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OutcomeReceiver<Object, Exception> callback) {
+ mImpl.deleteRegistrations(
+ deletionRequest,
+ executor,
+ OutcomeReceiverConverter.toAdServicesOutcomeReceiver(callback));
+ }
+
+ /**
+ * Delete previous registrations. If the deletion is successful, the callback's {@link
+ * OutcomeReceiver#onResult} is invoked with null. In case of failure, a {@link Exception} is
+ * sent through the callback's {@link OutcomeReceiver#onError}. Both success and failure
+ * feedback are executed on the provided {@link Executor}.
+ *
+ * <p>For use on Android R or lower.
+ *
+ * @param deletionRequest The request for deleting data.
+ * @param executor The executor to run callback.
+ * @param callback intended to notify asynchronously the API result.
+ */
+ @FlaggedApi(Flags.FLAG_ADSERVICES_OUTCOMERECEIVER_R_API_ENABLED)
+ public void deleteRegistrations(
+ @NonNull DeletionRequest deletionRequest,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull AdServicesOutcomeReceiver<Object, Exception> callback) {
+ mImpl.deleteRegistrations(deletionRequest, executor, callback);
+ }
+
+ /**
+ * Get Measurement API status.
+ *
+ * <p>The callback's {@code Integer} value is one of {@code MeasurementApiState}.
+ *
+ * @param executor used by callback to dispatch results.
+ * @param callback intended to notify asynchronously the API result.
+ */
+ @RequiresApi(Build.VERSION_CODES.S)
+ @RequiresPermission(ACCESS_ADSERVICES_ATTRIBUTION)
+ public void getMeasurementApiStatus(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OutcomeReceiver<Integer, Exception> callback) {
+ mImpl.getMeasurementApiStatus(
+ executor, OutcomeReceiverConverter.toAdServicesOutcomeReceiver(callback));
+ }
+
+ /**
+ * Get Measurement API status.
+ *
+ * <p>The callback's {@code Integer} value is one of {@code MeasurementApiState}.
+ *
+ * <p>For use on Android R or lower.
+ *
+ * @param executor used by callback to dispatch results.
+ * @param callback intended to notify asynchronously the API result.
+ */
+ @FlaggedApi(Flags.FLAG_ADSERVICES_OUTCOMERECEIVER_R_API_ENABLED)
+ @RequiresPermission(ACCESS_ADSERVICES_ATTRIBUTION)
+ public void getMeasurementApiStatus(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull AdServicesOutcomeReceiver<Integer, Exception> callback) {
+ mImpl.getMeasurementApiStatus(executor, callback);
+ }
+
+ /**
+ * If the service is in an APK (as opposed to the system service), unbind it from the service to
+ * allow the APK process to die.
+ *
+ * @hide Not sure if we'll need this functionality in the final API. For now, we need it for
+ * performance testing to simulate "cold-start" situations.
+ */
+ @VisibleForTesting
+ public void unbindFromService() {
+ mImpl.unbindFromService();
+ }
+}
diff --git a/android-35/android/adservices/measurement/RegistrationRequest.java b/android-35/android/adservices/measurement/RegistrationRequest.java
new file mode 100644
index 0000000..30ba5b6
--- /dev/null
+++ b/android-35/android/adservices/measurement/RegistrationRequest.java
@@ -0,0 +1,280 @@
+/*
+ * 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.measurement;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.InputEvent;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+
+/**
+ * Class to hold input to measurement registration calls.
+ * @hide
+ */
+public final class RegistrationRequest implements Parcelable {
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ INVALID,
+ REGISTER_SOURCE,
+ REGISTER_TRIGGER,
+ })
+ public @interface RegistrationType {}
+ /** Invalid registration type used as a default. */
+ public static final int INVALID = 0;
+ /**
+ * A request to register an Attribution Source event (NOTE: AdServices type not
+ * android.context.AttributionSource).
+ */
+ public static final int REGISTER_SOURCE = 1;
+ /** A request to register a trigger event. */
+ public static final int REGISTER_TRIGGER = 2;
+
+ @RegistrationType private final int mRegistrationType;
+ private final Uri mRegistrationUri;
+ private final InputEvent mInputEvent;
+ private final String mAppPackageName;
+ private final String mSdkPackageName;
+ private final long mRequestTime;
+ private final boolean mIsAdIdPermissionGranted;
+ private final String mAdIdValue;
+
+ private RegistrationRequest(@NonNull Builder builder) {
+ mRegistrationType = builder.mRegistrationType;
+ mRegistrationUri = builder.mRegistrationUri;
+ mInputEvent = builder.mInputEvent;
+ mAppPackageName = builder.mAppPackageName;
+ mSdkPackageName = builder.mSdkPackageName;
+ mRequestTime = builder.mRequestTime;
+ mIsAdIdPermissionGranted = builder.mIsAdIdPermissionGranted;
+ mAdIdValue = builder.mAdIdValue;
+ }
+
+ /**
+ * Unpack an RegistrationRequest from a Parcel.
+ */
+ private RegistrationRequest(Parcel in) {
+ mRegistrationType = in.readInt();
+ mRegistrationUri = Uri.CREATOR.createFromParcel(in);
+ mAppPackageName = in.readString();
+ mSdkPackageName = in.readString();
+ boolean hasInputEvent = in.readBoolean();
+ if (hasInputEvent) {
+ mInputEvent = InputEvent.CREATOR.createFromParcel(in);
+ } else {
+ mInputEvent = null;
+ }
+ mRequestTime = in.readLong();
+ mIsAdIdPermissionGranted = in.readBoolean();
+ boolean hasAdIdValue = in.readBoolean();
+ if (hasAdIdValue) {
+ mAdIdValue = in.readString();
+ } else {
+ mAdIdValue = null;
+ }
+ }
+
+ /** Creator for Parcelable (via reflection). */
+ @NonNull
+ public static final Parcelable.Creator<RegistrationRequest> CREATOR =
+ new Parcelable.Creator<RegistrationRequest>() {
+ @Override
+ public RegistrationRequest createFromParcel(Parcel in) {
+ return new RegistrationRequest(in);
+ }
+
+ @Override
+ public RegistrationRequest[] newArray(int size) {
+ return new RegistrationRequest[size];
+ }
+ };
+
+ /**
+ * For Parcelable, no special marshalled objects.
+ */
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * For Parcelable, write out to a Parcel in particular order.
+ */
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ Objects.requireNonNull(out);
+ out.writeInt(mRegistrationType);
+ mRegistrationUri.writeToParcel(out, flags);
+ out.writeString(mAppPackageName);
+ out.writeString(mSdkPackageName);
+ if (mInputEvent != null) {
+ out.writeBoolean(true);
+ mInputEvent.writeToParcel(out, flags);
+ } else {
+ out.writeBoolean(false);
+ }
+ out.writeLong(mRequestTime);
+ out.writeBoolean(mIsAdIdPermissionGranted);
+ if (mAdIdValue != null) {
+ out.writeBoolean(true);
+ out.writeString(mAdIdValue);
+ } else {
+ out.writeBoolean(false);
+ }
+ }
+
+ /** Type of the registration. */
+ @RegistrationType
+ public int getRegistrationType() {
+ return mRegistrationType;
+ }
+
+ /** Source URI of the App / Publisher. */
+ @NonNull
+ public Uri getRegistrationUri() {
+ return mRegistrationUri;
+ }
+
+ /** InputEvent related to an ad event. */
+ @Nullable
+ public InputEvent getInputEvent() {
+ return mInputEvent;
+ }
+
+ /** Package name of the app used for the registration. */
+ @NonNull
+ public String getAppPackageName() {
+ return mAppPackageName;
+ }
+
+ /** Package name of the sdk used for the registration. */
+ @NonNull
+ public String getSdkPackageName() {
+ return mSdkPackageName;
+ }
+
+ /** Time the request was created, as millis since boot excluding time in deep sleep. */
+ @NonNull
+ public long getRequestTime() {
+ return mRequestTime;
+ }
+
+ /** Ad ID Permission */
+ @NonNull
+ public boolean isAdIdPermissionGranted() {
+ return mIsAdIdPermissionGranted;
+ }
+
+ /** Ad ID Value */
+ @Nullable
+ public String getAdIdValue() {
+ return mAdIdValue;
+ }
+
+ /**
+ * A builder for {@link RegistrationRequest}.
+ */
+ public static final class Builder {
+ @RegistrationType private final int mRegistrationType;
+ private final Uri mRegistrationUri;
+ private final String mAppPackageName;
+ private final String mSdkPackageName;
+ private InputEvent mInputEvent;
+ private long mRequestTime;
+ private boolean mIsAdIdPermissionGranted;
+ private String mAdIdValue;
+
+ /**
+ * Builder constructor for {@link RegistrationRequest}.
+ *
+ * @param type registration type, either source or trigger
+ * @param registrationUri registration uri endpoint for registering a source/trigger
+ * @param appPackageName app package name that is calling PP API
+ * @param sdkPackageName sdk package name that is calling PP API
+ * @throws IllegalArgumentException if the scheme for {@code registrationUri} is not HTTPS
+ * or if {@code type} is not one of {@code REGISTER_SOURCE} or {@code REGISTER_TRIGGER}
+ */
+ public Builder(
+ @RegistrationType int type,
+ @NonNull Uri registrationUri,
+ @NonNull String appPackageName,
+ @NonNull String sdkPackageName) {
+ if (type != REGISTER_SOURCE && type != REGISTER_TRIGGER) {
+ throw new IllegalArgumentException("Invalid registrationType");
+ }
+
+ Objects.requireNonNull(registrationUri);
+ if (registrationUri.getScheme() == null
+ || !registrationUri.getScheme().equalsIgnoreCase("https")) {
+ throw new IllegalArgumentException("registrationUri must have an HTTPS scheme");
+ }
+
+ Objects.requireNonNull(appPackageName);
+ Objects.requireNonNull(sdkPackageName);
+ mRegistrationType = type;
+ mRegistrationUri = registrationUri;
+ mAppPackageName = appPackageName;
+ mSdkPackageName = sdkPackageName;
+ }
+
+ /** See {@link RegistrationRequest#getInputEvent}. */
+ @NonNull
+ public Builder setInputEvent(@Nullable InputEvent event) {
+ mInputEvent = event;
+ return this;
+ }
+
+ /** See {@link RegistrationRequest#getRequestTime}. */
+ @NonNull
+ public Builder setRequestTime(long requestTime) {
+ mRequestTime = requestTime;
+ return this;
+ }
+
+ /** See {@link RegistrationRequest#isAdIdPermissionGranted()}. */
+ @NonNull
+ public Builder setAdIdPermissionGranted(boolean adIdPermissionGranted) {
+ mIsAdIdPermissionGranted = adIdPermissionGranted;
+ return this;
+ }
+
+ /** See {@link RegistrationRequest#getAdIdValue()}. */
+ @NonNull
+ public Builder setAdIdValue(@Nullable String adIdValue) {
+ mAdIdValue = adIdValue;
+ return this;
+ }
+
+ /** Build the RegistrationRequest. */
+ @NonNull
+ public RegistrationRequest build() {
+ // Ensure registrationType has been set,
+ // throws IllegalArgumentException if mRegistrationType
+ // isn't a valid choice.
+ if (mRegistrationType != REGISTER_SOURCE && mRegistrationType != REGISTER_TRIGGER) {
+ throw new IllegalArgumentException("Invalid registrationType");
+ }
+
+ return new RegistrationRequest(this);
+ }
+ }
+}
diff --git a/android-35/android/adservices/measurement/SourceRegistrationRequest.java b/android-35/android/adservices/measurement/SourceRegistrationRequest.java
new file mode 100644
index 0000000..57b2e7b
--- /dev/null
+++ b/android-35/android/adservices/measurement/SourceRegistrationRequest.java
@@ -0,0 +1,182 @@
+/*
+ * 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.measurement;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.InputEvent;
+
+import com.android.adservices.AdServicesParcelableUtil;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Class to hold input to measurement source registration calls.
+ */
+public final class SourceRegistrationRequest implements Parcelable {
+ private static final int REGISTRATION_URIS_MAX_COUNT = 20;
+ /** Registration URIs to fetch sources. */
+ @NonNull private final List<Uri> mRegistrationUris;
+
+ /**
+ * User Interaction {@link InputEvent} used by the AttributionReporting API to distinguish
+ * clicks from views. It will be an {@link InputEvent} object (for a click event) or null (for a
+ * view event).
+ */
+ @Nullable private final InputEvent mInputEvent;
+
+ private SourceRegistrationRequest(@NonNull Builder builder) {
+ mRegistrationUris = builder.mRegistrationUris;
+ mInputEvent = builder.mInputEvent;
+ }
+
+ private SourceRegistrationRequest(@NonNull Parcel in) {
+ Objects.requireNonNull(in);
+ List<Uri> registrationsUris = new ArrayList<>();
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
+ in.readList(registrationsUris, Uri.class.getClassLoader());
+ } else {
+ in.readList(registrationsUris, Uri.class.getClassLoader(), Uri.class);
+ }
+ mRegistrationUris = registrationsUris;
+ mInputEvent =
+ AdServicesParcelableUtil.readNullableFromParcel(
+ in, InputEvent.CREATOR::createFromParcel);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof SourceRegistrationRequest)) return false;
+ SourceRegistrationRequest that = (SourceRegistrationRequest) o;
+ return Objects.equals(mRegistrationUris, that.mRegistrationUris)
+ && Objects.equals(mInputEvent, that.mInputEvent);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mRegistrationUris, mInputEvent);
+ }
+
+ /** Registration URIs to fetch sources. */
+ @NonNull
+ public List<Uri> getRegistrationUris() {
+ return mRegistrationUris;
+ }
+
+ /**
+ * User Interaction {@link InputEvent} used by the AttributionReporting API to distinguish
+ * clicks from views. It will be an {@link InputEvent} object (for a click event) or null (for a
+ * view event)
+ */
+ @Nullable
+ public InputEvent getInputEvent() {
+ return mInputEvent;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ Objects.requireNonNull(out);
+ out.writeList(mRegistrationUris);
+ AdServicesParcelableUtil.writeNullableToParcel(
+ out, mInputEvent, (target, event) -> event.writeToParcel(target, flags));
+ }
+
+ /** Builder for {@link SourceRegistrationRequest}. */
+ public static final class Builder {
+ /** Registration {@link Uri}s to fetch sources. */
+ @NonNull private final List<Uri> mRegistrationUris;
+ /**
+ * User Interaction InputEvent used by the attribution reporting API to distinguish clicks
+ * from views.
+ */
+ @Nullable private InputEvent mInputEvent;
+
+ /**
+ * Builder constructor for {@link SourceRegistrationRequest}.
+ *
+ * @param registrationUris source registration {@link Uri}s
+ * @throws IllegalArgumentException if the scheme for one or more of the
+ * {@code registrationUris} is not HTTPS
+ */
+ public Builder(@NonNull List<Uri> registrationUris) {
+ Objects.requireNonNull(registrationUris);
+ if (registrationUris.isEmpty()
+ || registrationUris.size() > REGISTRATION_URIS_MAX_COUNT) {
+ throw new IllegalArgumentException(
+ String.format(
+ "Requests should have at least 1 and at most %d URIs."
+ + " Request has %d URIs.",
+ REGISTRATION_URIS_MAX_COUNT, registrationUris.size()));
+ }
+ for (Uri registrationUri : registrationUris) {
+ if (registrationUri.getScheme() == null
+ || !registrationUri.getScheme().equalsIgnoreCase("https")) {
+ throw new IllegalArgumentException(
+ "registrationUri must have an HTTPS scheme");
+ }
+ }
+ mRegistrationUris = registrationUris;
+ }
+
+ /**
+ * Setter corresponding to {@link #getInputEvent()}.
+ *
+ * @param inputEvent User Interaction {@link InputEvent} used by the AttributionReporting
+ * API to distinguish clicks from views. It will be an {@link InputEvent} object (for a
+ * click event) or null (for a view event)
+ * @return builder
+ */
+ @NonNull
+ public Builder setInputEvent(@Nullable InputEvent inputEvent) {
+ mInputEvent = inputEvent;
+ return this;
+ }
+
+ /** Pre-validates parameters and builds {@link SourceRegistrationRequest}. */
+ @NonNull
+ public SourceRegistrationRequest build() {
+ return new SourceRegistrationRequest(this);
+ }
+ }
+
+ /** Creator for Paracelable (via reflection). */
+ @NonNull
+ public static final Parcelable.Creator<SourceRegistrationRequest> CREATOR =
+ new Parcelable.Creator<>() {
+ @Override
+ public SourceRegistrationRequest createFromParcel(Parcel in) {
+ return new SourceRegistrationRequest(in);
+ }
+
+ @Override
+ public SourceRegistrationRequest[] newArray(int size) {
+ return new SourceRegistrationRequest[size];
+ }
+ };
+}
diff --git a/android-35/android/adservices/measurement/SourceRegistrationRequestInternal.java b/android-35/android/adservices/measurement/SourceRegistrationRequestInternal.java
new file mode 100644
index 0000000..25e2ee5
--- /dev/null
+++ b/android-35/android/adservices/measurement/SourceRegistrationRequestInternal.java
@@ -0,0 +1,189 @@
+/*
+ * 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.measurement;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.adservices.AdServicesParcelableUtil;
+
+import java.util.Objects;
+
+/**
+ * Internal source registration request object to communicate from {@link MeasurementManager} to
+ * {@link IMeasurementService}.
+ *
+ * @hide
+ */
+public class SourceRegistrationRequestInternal implements Parcelable {
+ /** Holds input to measurement source registration calls. */
+ @NonNull private final SourceRegistrationRequest mSourceRegistrationRequest;
+ /** Caller app package name. */
+ @NonNull private final String mAppPackageName;
+ /** Calling SDK package name. */
+ @NonNull private final String mSdkPackageName;
+ /** Time the request was created, in millis since boot excluding time in deep sleep. */
+ private final long mBootRelativeRequestTime;
+ /** Ad ID value if the permission is granted, null otherwise. */
+ @Nullable private final String mAdIdValue;
+
+ private SourceRegistrationRequestInternal(@NonNull Builder builder) {
+ mSourceRegistrationRequest = builder.mRegistrationRequest;
+ mAppPackageName = builder.mAppPackageName;
+ mSdkPackageName = builder.mSdkPackageName;
+ mBootRelativeRequestTime = builder.mBootRelativeRequestTime;
+ mAdIdValue = builder.mAdIdValue;
+ }
+
+ private SourceRegistrationRequestInternal(@NonNull Parcel in) {
+ Objects.requireNonNull(in);
+ mSourceRegistrationRequest = SourceRegistrationRequest.CREATOR.createFromParcel(in);
+ mAppPackageName = in.readString();
+ mSdkPackageName = in.readString();
+ mBootRelativeRequestTime = in.readLong();
+ mAdIdValue = AdServicesParcelableUtil.readNullableFromParcel(in, Parcel::readString);
+ }
+
+ /** Holds input to measurement source registration calls from app context. */
+ @NonNull
+ public SourceRegistrationRequest getSourceRegistrationRequest() {
+ return mSourceRegistrationRequest;
+ }
+
+ /** Caller app package name. */
+ @NonNull
+ public String getAppPackageName() {
+ return mAppPackageName;
+ }
+
+ /** Calling SDK package name. */
+ @NonNull
+ public String getSdkPackageName() {
+ return mSdkPackageName;
+ }
+
+ /** Time the request was created, in millis since boot excluding time in deep sleep. */
+ @NonNull
+ public long getBootRelativeRequestTime() {
+ return mBootRelativeRequestTime;
+ }
+
+ /** Ad ID value if the permission is granted, null otherwise. */
+ @Nullable
+ public String getAdIdValue() {
+ return mAdIdValue;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof SourceRegistrationRequestInternal)) return false;
+ SourceRegistrationRequestInternal that = (SourceRegistrationRequestInternal) o;
+ return Objects.equals(mSourceRegistrationRequest, that.mSourceRegistrationRequest)
+ && Objects.equals(mAppPackageName, that.mAppPackageName)
+ && Objects.equals(mSdkPackageName, that.mSdkPackageName)
+ && mBootRelativeRequestTime == that.mBootRelativeRequestTime
+ && Objects.equals(mAdIdValue, that.mAdIdValue);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(
+ mSourceRegistrationRequest,
+ mAppPackageName,
+ mSdkPackageName,
+ mBootRelativeRequestTime,
+ mAdIdValue);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ Objects.requireNonNull(out);
+ mSourceRegistrationRequest.writeToParcel(out, flags);
+ out.writeString(mAppPackageName);
+ out.writeString(mSdkPackageName);
+ out.writeLong(mBootRelativeRequestTime);
+ AdServicesParcelableUtil.writeNullableToParcel(out, mAdIdValue, Parcel::writeString);
+ }
+
+ /** Builder for {@link SourceRegistrationRequestInternal}. */
+ public static final class Builder {
+ /** External source registration request from client app SDK. */
+ @NonNull private final SourceRegistrationRequest mRegistrationRequest;
+ /** Package name of the app used for the registration. Used to determine the registrant. */
+ @NonNull private final String mAppPackageName;
+ /** Package name of the sdk used for the registration. */
+ @NonNull private final String mSdkPackageName;
+ /** Time the request was created, in millis since boot excluding time in deep sleep. */
+ private final long mBootRelativeRequestTime;
+ /** AD ID value if the permission was granted. */
+ @Nullable private String mAdIdValue;
+ /**
+ * Builder constructor for {@link SourceRegistrationRequestInternal}.
+ *
+ * @param registrationRequest external source registration request
+ * @param appPackageName app package name that is calling PP API
+ * @param sdkPackageName sdk package name that is calling PP API
+ */
+ public Builder(
+ @NonNull SourceRegistrationRequest registrationRequest,
+ @NonNull String appPackageName,
+ @NonNull String sdkPackageName,
+ long bootRelativeRequestTime) {
+ Objects.requireNonNull(registrationRequest);
+ Objects.requireNonNull(appPackageName);
+ Objects.requireNonNull(sdkPackageName);
+ mRegistrationRequest = registrationRequest;
+ mAppPackageName = appPackageName;
+ mSdkPackageName = sdkPackageName;
+ mBootRelativeRequestTime = bootRelativeRequestTime;
+ }
+
+ /** Pre-validates parameters and builds {@link SourceRegistrationRequestInternal}. */
+ @NonNull
+ public SourceRegistrationRequestInternal build() {
+ return new SourceRegistrationRequestInternal(this);
+ }
+
+ /** See {@link SourceRegistrationRequestInternal#getAdIdValue()}. */
+ public SourceRegistrationRequestInternal.Builder setAdIdValue(@Nullable String adIdValue) {
+ mAdIdValue = adIdValue;
+ return this;
+ }
+ }
+
+ /** Creator for Parcelable (via reflection). */
+ public static final Creator<SourceRegistrationRequestInternal> CREATOR =
+ new Creator<>() {
+ @Override
+ public SourceRegistrationRequestInternal createFromParcel(Parcel in) {
+ return new SourceRegistrationRequestInternal(in);
+ }
+
+ @Override
+ public SourceRegistrationRequestInternal[] newArray(int size) {
+ return new SourceRegistrationRequestInternal[size];
+ }
+ };
+}
diff --git a/android-35/android/adservices/measurement/StatusParam.java b/android-35/android/adservices/measurement/StatusParam.java
new file mode 100644
index 0000000..a7b3e94
--- /dev/null
+++ b/android-35/android/adservices/measurement/StatusParam.java
@@ -0,0 +1,109 @@
+/*
+ * 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.measurement;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Class to hold parameters needed for getting the Measurement API status. This is an internal class
+ * for communication between the {@link MeasurementManager} and {@link IMeasurementService} impl.
+ *
+ * @hide
+ */
+public final class StatusParam implements Parcelable {
+ private final String mAppPackageName;
+ private final String mSdkPackageName;
+
+ private StatusParam(@NonNull Builder builder) {
+ mAppPackageName = builder.mAppPackageName;
+ mSdkPackageName = builder.mSdkPackageName;
+ }
+
+ /** Unpack an StatusParam from a Parcel. */
+ private StatusParam(Parcel in) {
+ mAppPackageName = in.readString();
+ mSdkPackageName = in.readString();
+ }
+
+ /** Creator for Parcelable (via reflection). */
+ @NonNull
+ public static final Creator<StatusParam> CREATOR =
+ new Creator<StatusParam>() {
+ @Override
+ public StatusParam createFromParcel(Parcel in) {
+ return new StatusParam(in);
+ }
+
+ @Override
+ public StatusParam[] newArray(int size) {
+ return new StatusParam[size];
+ }
+ };
+
+ /** For Parcelable, no special marshalled objects. */
+ public int describeContents() {
+ return 0;
+ }
+
+ /** For Parcelable, write out to a Parcel in particular order. */
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ Objects.requireNonNull(out);
+ out.writeString(mAppPackageName);
+ out.writeString(mSdkPackageName);
+ }
+
+ /** Package name of the app used for getting the status. */
+ @NonNull
+ public String getAppPackageName() {
+ return mAppPackageName;
+ }
+
+ /** Package name of the sdk used for getting the status. */
+ @NonNull
+ public String getSdkPackageName() {
+ return mSdkPackageName;
+ }
+
+ /** A builder for {@link StatusParam}. */
+ public static final class Builder {
+ private final String mAppPackageName;
+ private final String mSdkPackageName;
+
+ /**
+ * Builder constructor for {@link StatusParam}.
+ *
+ * @param appPackageName see {@link StatusParam#getAppPackageName()}
+ * @param sdkPackageName see {@link StatusParam#getSdkPackageName()}
+ */
+ public Builder(@NonNull String appPackageName, @NonNull String sdkPackageName) {
+ Objects.requireNonNull(appPackageName);
+ Objects.requireNonNull(sdkPackageName);
+ mAppPackageName = appPackageName;
+ mSdkPackageName = sdkPackageName;
+ }
+
+ /** Build the StatusParam. */
+ @NonNull
+ public StatusParam build() {
+ return new StatusParam(this);
+ }
+ }
+}
diff --git a/android-35/android/adservices/measurement/WebSourceParams.java b/android-35/android/adservices/measurement/WebSourceParams.java
new file mode 100644
index 0000000..546220e
--- /dev/null
+++ b/android-35/android/adservices/measurement/WebSourceParams.java
@@ -0,0 +1,159 @@
+/*
+ * 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.measurement;
+
+import android.annotation.NonNull;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/** Class holding source registration parameters. */
+public final class WebSourceParams implements Parcelable {
+ /** Creator for Paracelable (via reflection). */
+ @NonNull
+ public static final Parcelable.Creator<WebSourceParams> CREATOR =
+ new Parcelable.Creator<WebSourceParams>() {
+ @Override
+ public WebSourceParams createFromParcel(Parcel in) {
+ return new WebSourceParams(in);
+ }
+
+ @Override
+ public WebSourceParams[] newArray(int size) {
+ return new WebSourceParams[size];
+ }
+ };
+ /**
+ * URI that the Attribution Reporting API sends a request to in order to obtain source
+ * registration parameters.
+ */
+ @NonNull private final Uri mRegistrationUri;
+ /**
+ * Used by the browser to indicate whether the debug key obtained from the registration URI is
+ * allowed to be used
+ */
+ private final boolean mDebugKeyAllowed;
+
+ private WebSourceParams(@NonNull Builder builder) {
+ mRegistrationUri = builder.mRegistrationUri;
+ mDebugKeyAllowed = builder.mDebugKeyAllowed;
+ }
+
+ /** Unpack a SourceRegistration from a Parcel. */
+ private WebSourceParams(@NonNull Parcel in) {
+ mRegistrationUri = Uri.CREATOR.createFromParcel(in);
+ mDebugKeyAllowed = in.readBoolean();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof WebSourceParams)) return false;
+ WebSourceParams that = (WebSourceParams) o;
+ return mDebugKeyAllowed == that.mDebugKeyAllowed
+ && Objects.equals(mRegistrationUri, that.mRegistrationUri);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mRegistrationUri, mDebugKeyAllowed);
+ }
+
+ /** Getter for registration Uri. */
+ @NonNull
+ public Uri getRegistrationUri() {
+ return mRegistrationUri;
+ }
+
+ /**
+ * Getter for debug allowed/disallowed flag. Its value as {@code true} means to allow parsing
+ * debug keys from registration responses and their addition in the generated reports.
+ */
+ public boolean isDebugKeyAllowed() {
+ return mDebugKeyAllowed;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ Objects.requireNonNull(out);
+ mRegistrationUri.writeToParcel(out, flags);
+ out.writeBoolean(mDebugKeyAllowed);
+ }
+
+ /** A builder for {@link WebSourceParams}. */
+ public static final class Builder {
+ /**
+ * URI that the Attribution Reporting API sends a request to in order to obtain source
+ * registration parameters.
+ */
+ @NonNull private final Uri mRegistrationUri;
+ /**
+ * Used by the browser to indicate whether the debug key obtained from the registration URI
+ * is allowed to be used
+ */
+ private boolean mDebugKeyAllowed;
+
+ /**
+ * Builder constructor for {@link WebSourceParams}. {@code mIsDebugKeyAllowed} is assigned
+ * false by default.
+ *
+ * @param registrationUri URI that the Attribution Reporting API sends a request to in order
+ * to obtain source registration parameters.
+ * @throws IllegalArgumentException if the scheme for {@code registrationUri} is not HTTPS
+ */
+ public Builder(@NonNull Uri registrationUri) {
+ Objects.requireNonNull(registrationUri);
+ if (registrationUri.getScheme() == null
+ || !registrationUri.getScheme().equalsIgnoreCase("https")) {
+ throw new IllegalArgumentException("registrationUri must have an HTTPS scheme");
+ }
+ mRegistrationUri = registrationUri;
+ mDebugKeyAllowed = false;
+ }
+
+ /**
+ * Setter for debug allow/disallow flag. Setting it to true will allow parsing debug keys
+ * from registration responses and their addition in the generated reports.
+ *
+ * @param debugKeyAllowed used by the browser to indicate whether the debug key obtained
+ * from the registration URI is allowed to be used
+ * @return builder
+ */
+ @NonNull
+ public Builder setDebugKeyAllowed(boolean debugKeyAllowed) {
+ this.mDebugKeyAllowed = debugKeyAllowed;
+ return this;
+ }
+
+ /**
+ * Built immutable {@link WebSourceParams}.
+ *
+ * @return immutable {@link WebSourceParams}
+ */
+ @NonNull
+ public WebSourceParams build() {
+ return new WebSourceParams(this);
+ }
+ }
+}
diff --git a/android-35/android/adservices/measurement/WebSourceRegistrationRequest.java b/android-35/android/adservices/measurement/WebSourceRegistrationRequest.java
new file mode 100644
index 0000000..946395a
--- /dev/null
+++ b/android-35/android/adservices/measurement/WebSourceRegistrationRequest.java
@@ -0,0 +1,353 @@
+/*
+ * 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.measurement;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.InputEvent;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/** Class to hold input to measurement source registration calls from web context. */
+public final class WebSourceRegistrationRequest implements Parcelable {
+ private static final String ANDROID_APP_SCHEME = "android-app";
+ private static final int WEB_SOURCE_PARAMS_MAX_COUNT = 80;
+
+ /** Creator for Paracelable (via reflection). */
+ @NonNull
+ public static final Parcelable.Creator<WebSourceRegistrationRequest> CREATOR =
+ new Parcelable.Creator<WebSourceRegistrationRequest>() {
+ @Override
+ public WebSourceRegistrationRequest createFromParcel(Parcel in) {
+ return new WebSourceRegistrationRequest(in);
+ }
+
+ @Override
+ public WebSourceRegistrationRequest[] newArray(int size) {
+ return new WebSourceRegistrationRequest[size];
+ }
+ };
+ /** Registration info to fetch sources. */
+ @NonNull private final List<WebSourceParams> mWebSourceParams;
+
+ /** Top level origin of publisher. */
+ @NonNull private final Uri mTopOriginUri;
+
+ /**
+ * User Interaction {@link InputEvent} used by the AttributionReporting API to distinguish
+ * clicks from views.
+ */
+ @Nullable private final InputEvent mInputEvent;
+
+ /**
+ * App destination of the source. It is the android app {@link Uri} where corresponding
+ * conversion is expected. This field is compared with the corresponding field in Source
+ * Registration Response, if matching fails the registration is rejected. If null is provided,
+ * no destination matching will be performed.
+ */
+ @Nullable private final Uri mAppDestination;
+
+ /**
+ * Web destination of the source. It is the website {@link Uri} where corresponding conversion
+ * is expected. This field is compared with the corresponding field in Source Registration
+ * Response, if matching fails the registration is rejected. If null is provided, no destination
+ * matching will be performed.
+ */
+ @Nullable private final Uri mWebDestination;
+
+ /** Verified destination by the caller. This is where the user actually landed. */
+ @Nullable private final Uri mVerifiedDestination;
+
+ private WebSourceRegistrationRequest(@NonNull Builder builder) {
+ mWebSourceParams = builder.mWebSourceParams;
+ mInputEvent = builder.mInputEvent;
+ mTopOriginUri = builder.mTopOriginUri;
+ mAppDestination = builder.mAppDestination;
+ mWebDestination = builder.mWebDestination;
+ mVerifiedDestination = builder.mVerifiedDestination;
+ }
+
+ private WebSourceRegistrationRequest(@NonNull Parcel in) {
+ Objects.requireNonNull(in);
+ ArrayList<WebSourceParams> sourceRegistrations = new ArrayList<>();
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
+ in.readList(sourceRegistrations, WebSourceParams.class.getClassLoader());
+ } else {
+ in.readList(
+ sourceRegistrations,
+ WebSourceParams.class.getClassLoader(),
+ WebSourceParams.class);
+ }
+ mWebSourceParams = sourceRegistrations;
+ mTopOriginUri = Uri.CREATOR.createFromParcel(in);
+ if (in.readBoolean()) {
+ mInputEvent = InputEvent.CREATOR.createFromParcel(in);
+ } else {
+ mInputEvent = null;
+ }
+ if (in.readBoolean()) {
+ mAppDestination = Uri.CREATOR.createFromParcel(in);
+ } else {
+ mAppDestination = null;
+ }
+ if (in.readBoolean()) {
+ mWebDestination = Uri.CREATOR.createFromParcel(in);
+ } else {
+ mWebDestination = null;
+ }
+ if (in.readBoolean()) {
+ mVerifiedDestination = Uri.CREATOR.createFromParcel(in);
+ } else {
+ mVerifiedDestination = null;
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof WebSourceRegistrationRequest)) return false;
+ WebSourceRegistrationRequest that = (WebSourceRegistrationRequest) o;
+ return Objects.equals(mWebSourceParams, that.mWebSourceParams)
+ && Objects.equals(mTopOriginUri, that.mTopOriginUri)
+ && Objects.equals(mInputEvent, that.mInputEvent)
+ && Objects.equals(mAppDestination, that.mAppDestination)
+ && Objects.equals(mWebDestination, that.mWebDestination)
+ && Objects.equals(mVerifiedDestination, that.mVerifiedDestination);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(
+ mWebSourceParams,
+ mTopOriginUri,
+ mInputEvent,
+ mAppDestination,
+ mWebDestination,
+ mVerifiedDestination);
+ }
+
+ /** Getter for source params. */
+ @NonNull
+ public List<WebSourceParams> getSourceParams() {
+ return mWebSourceParams;
+ }
+
+ /** Getter for top origin Uri. */
+ @NonNull
+ public Uri getTopOriginUri() {
+ return mTopOriginUri;
+ }
+
+ /** Getter for input event. */
+ @Nullable
+ public InputEvent getInputEvent() {
+ return mInputEvent;
+ }
+
+ /**
+ * Getter for the app destination. It is the android app {@link Uri} where corresponding
+ * conversion is expected. At least one of app destination or web destination is required.
+ */
+ @Nullable
+ public Uri getAppDestination() {
+ return mAppDestination;
+ }
+
+ /**
+ * Getter for web destination. It is the website {@link Uri} where corresponding conversion is
+ * expected. At least one of app destination or web destination is required.
+ */
+ @Nullable
+ public Uri getWebDestination() {
+ return mWebDestination;
+ }
+
+ /** Getter for verified destination. */
+ @Nullable
+ public Uri getVerifiedDestination() {
+ return mVerifiedDestination;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ Objects.requireNonNull(out);
+ out.writeList(mWebSourceParams);
+ mTopOriginUri.writeToParcel(out, flags);
+
+ if (mInputEvent != null) {
+ out.writeBoolean(true);
+ mInputEvent.writeToParcel(out, flags);
+ } else {
+ out.writeBoolean(false);
+ }
+ if (mAppDestination != null) {
+ out.writeBoolean(true);
+ mAppDestination.writeToParcel(out, flags);
+ } else {
+ out.writeBoolean(false);
+ }
+ if (mWebDestination != null) {
+ out.writeBoolean(true);
+ mWebDestination.writeToParcel(out, flags);
+ } else {
+ out.writeBoolean(false);
+ }
+ if (mVerifiedDestination != null) {
+ out.writeBoolean(true);
+ mVerifiedDestination.writeToParcel(out, flags);
+ } else {
+ out.writeBoolean(false);
+ }
+ }
+
+ /** Builder for {@link WebSourceRegistrationRequest}. */
+ public static final class Builder {
+ /** Registration info to fetch sources. */
+ @NonNull private final List<WebSourceParams> mWebSourceParams;
+ /** Top origin {@link Uri} of publisher. */
+ @NonNull private final Uri mTopOriginUri;
+ /**
+ * User Interaction InputEvent used by the AttributionReporting API to distinguish clicks
+ * from views.
+ */
+ @Nullable private InputEvent mInputEvent;
+ /**
+ * App destination of the source. It is the android app {@link Uri} where corresponding
+ * conversion is expected.
+ */
+ @Nullable private Uri mAppDestination;
+ /**
+ * Web destination of the source. It is the website {@link Uri} where corresponding
+ * conversion is expected.
+ */
+ @Nullable private Uri mWebDestination;
+ /**
+ * Verified destination by the caller. If available, sources should be checked against it.
+ */
+ @Nullable private Uri mVerifiedDestination;
+
+ /**
+ * Builder constructor for {@link WebSourceRegistrationRequest}.
+ *
+ * @param webSourceParams source parameters containing source registration parameters, the
+ * list should not be empty
+ * @param topOriginUri source publisher {@link Uri}
+ */
+ public Builder(@NonNull List<WebSourceParams> webSourceParams, @NonNull Uri topOriginUri) {
+ Objects.requireNonNull(webSourceParams);
+ Objects.requireNonNull(topOriginUri);
+ if (webSourceParams.isEmpty() || webSourceParams.size() > WEB_SOURCE_PARAMS_MAX_COUNT) {
+ throw new IllegalArgumentException(
+ "web source params size is not within bounds, size: "
+ + webSourceParams.size());
+ }
+ mWebSourceParams = webSourceParams;
+ mTopOriginUri = topOriginUri;
+ }
+
+ /**
+ * Setter for input event.
+ *
+ * @param inputEvent User Interaction InputEvent used by the AttributionReporting API to
+ * distinguish clicks from views.
+ * @return builder
+ */
+ @NonNull
+ public Builder setInputEvent(@Nullable InputEvent inputEvent) {
+ mInputEvent = inputEvent;
+ return this;
+ }
+
+ /**
+ * Setter for app destination. It is the android app {@link Uri} where corresponding
+ * conversion is expected. At least one of app destination or web destination is required.
+ *
+ * @param appDestination app destination {@link Uri}
+ * @return builder
+ */
+ @NonNull
+ public Builder setAppDestination(@Nullable Uri appDestination) {
+ if (appDestination != null) {
+ String scheme = appDestination.getScheme();
+ Uri destination;
+ if (scheme == null) {
+ destination = Uri.parse(ANDROID_APP_SCHEME + "://" + appDestination);
+ } else if (!scheme.equals(ANDROID_APP_SCHEME)) {
+ throw new IllegalArgumentException(
+ String.format(
+ "appDestination scheme must be %s " + "or null. Received: %s",
+ ANDROID_APP_SCHEME, scheme));
+ } else {
+ destination = appDestination;
+ }
+ mAppDestination = destination;
+ }
+ return this;
+ }
+
+ /**
+ * Setter for web destination. It is the website {@link Uri} where corresponding conversion
+ * is expected. At least one of app destination or web destination is required.
+ *
+ * @param webDestination web destination {@link Uri}
+ * @return builder
+ */
+ @NonNull
+ public Builder setWebDestination(@Nullable Uri webDestination) {
+ if (webDestination != null) {
+ validateScheme("Web destination", webDestination);
+ mWebDestination = webDestination;
+ }
+ return this;
+ }
+
+ /**
+ * Setter for verified destination.
+ *
+ * @param verifiedDestination verified destination
+ * @return builder
+ */
+ @NonNull
+ public Builder setVerifiedDestination(@Nullable Uri verifiedDestination) {
+ mVerifiedDestination = verifiedDestination;
+ return this;
+ }
+
+ /** Pre-validates parameters and builds {@link WebSourceRegistrationRequest}. */
+ @NonNull
+ public WebSourceRegistrationRequest build() {
+ return new WebSourceRegistrationRequest(this);
+ }
+ }
+
+ private static void validateScheme(String name, Uri uri) throws IllegalArgumentException {
+ if (uri.getScheme() == null) {
+ throw new IllegalArgumentException(name + " must have a scheme.");
+ }
+ }
+}
diff --git a/android-35/android/adservices/measurement/WebSourceRegistrationRequestInternal.java b/android-35/android/adservices/measurement/WebSourceRegistrationRequestInternal.java
new file mode 100644
index 0000000..1420520
--- /dev/null
+++ b/android-35/android/adservices/measurement/WebSourceRegistrationRequestInternal.java
@@ -0,0 +1,180 @@
+/*
+ * 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.measurement;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Internal source registration request object to communicate from {@link MeasurementManager} to
+ * {@link IMeasurementService}.
+ *
+ * @hide
+ */
+public class WebSourceRegistrationRequestInternal implements Parcelable {
+ /** Creator for Parcelable (via reflection). */
+ public static final Parcelable.Creator<WebSourceRegistrationRequestInternal> CREATOR =
+ new Parcelable.Creator<WebSourceRegistrationRequestInternal>() {
+ @Override
+ public WebSourceRegistrationRequestInternal createFromParcel(Parcel in) {
+ return new WebSourceRegistrationRequestInternal(in);
+ }
+
+ @Override
+ public WebSourceRegistrationRequestInternal[] newArray(int size) {
+ return new WebSourceRegistrationRequestInternal[size];
+ }
+ };
+ /** Holds input to measurement source registration calls from web context. */
+ @NonNull private final WebSourceRegistrationRequest mSourceRegistrationRequest;
+ /** Holds app package info of where the request is coming from. */
+ @NonNull private final String mAppPackageName;
+ /** Holds sdk package info of where the request is coming from. */
+ @NonNull private final String mSdkPackageName;
+ /** Time the request was created, as millis since boot excluding time in deep sleep. */
+ private final long mRequestTime;
+ /** AD ID Permission Granted. */
+ private final boolean mIsAdIdPermissionGranted;
+
+ private WebSourceRegistrationRequestInternal(@NonNull Builder builder) {
+ mSourceRegistrationRequest = builder.mSourceRegistrationRequest;
+ mAppPackageName = builder.mAppPackageName;
+ mSdkPackageName = builder.mSdkPackageName;
+ mRequestTime = builder.mRequestTime;
+ mIsAdIdPermissionGranted = builder.mIsAdIdPermissionGranted;
+ }
+
+ private WebSourceRegistrationRequestInternal(Parcel in) {
+ Objects.requireNonNull(in);
+ mSourceRegistrationRequest = WebSourceRegistrationRequest.CREATOR.createFromParcel(in);
+ mAppPackageName = in.readString();
+ mSdkPackageName = in.readString();
+ mRequestTime = in.readLong();
+ mIsAdIdPermissionGranted = in.readBoolean();
+ }
+
+ /** Getter for {@link #mSourceRegistrationRequest}. */
+ public WebSourceRegistrationRequest getSourceRegistrationRequest() {
+ return mSourceRegistrationRequest;
+ }
+
+ /** Getter for {@link #mAppPackageName}. */
+ public String getAppPackageName() {
+ return mAppPackageName;
+ }
+
+ /** Getter for {@link #mSdkPackageName}. */
+ public String getSdkPackageName() {
+ return mSdkPackageName;
+ }
+
+ /** Getter for {@link #mRequestTime}. */
+ public long getRequestTime() {
+ return mRequestTime;
+ }
+
+ /** Getter for {@link #mIsAdIdPermissionGranted}. */
+ public boolean isAdIdPermissionGranted() {
+ return mIsAdIdPermissionGranted;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof WebSourceRegistrationRequestInternal)) return false;
+ WebSourceRegistrationRequestInternal that = (WebSourceRegistrationRequestInternal) o;
+ return Objects.equals(mSourceRegistrationRequest, that.mSourceRegistrationRequest)
+ && Objects.equals(mAppPackageName, that.mAppPackageName)
+ && Objects.equals(mSdkPackageName, that.mSdkPackageName)
+ && mRequestTime == that.mRequestTime
+ && mIsAdIdPermissionGranted == that.mIsAdIdPermissionGranted;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(
+ mSourceRegistrationRequest,
+ mAppPackageName,
+ mSdkPackageName,
+ mIsAdIdPermissionGranted);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ Objects.requireNonNull(out);
+ mSourceRegistrationRequest.writeToParcel(out, flags);
+ out.writeString(mAppPackageName);
+ out.writeString(mSdkPackageName);
+ out.writeLong(mRequestTime);
+ out.writeBoolean(mIsAdIdPermissionGranted);
+ }
+
+ /** Builder for {@link WebSourceRegistrationRequestInternal}. */
+ public static final class Builder {
+ /** External source registration request from client app SDK. */
+ @NonNull private final WebSourceRegistrationRequest mSourceRegistrationRequest;
+ /** Package name of the app used for the registration. Used to determine the registrant. */
+ @NonNull private final String mAppPackageName;
+ /** Package name of the sdk used for the registration. */
+ @NonNull private final String mSdkPackageName;
+ /** Time the request was created, as millis since boot excluding time in deep sleep. */
+ private final long mRequestTime;
+ /** AD ID Permission Granted. */
+ private boolean mIsAdIdPermissionGranted;
+ /**
+ * Builder constructor for {@link WebSourceRegistrationRequestInternal}.
+ *
+ * @param sourceRegistrationRequest external source registration request
+ * @param appPackageName app package name that is calling PP API
+ * @param sdkPackageName sdk package name that is calling PP API
+ */
+ public Builder(
+ @NonNull WebSourceRegistrationRequest sourceRegistrationRequest,
+ @NonNull String appPackageName,
+ @NonNull String sdkPackageName,
+ long requestTime) {
+ Objects.requireNonNull(sourceRegistrationRequest);
+ Objects.requireNonNull(appPackageName);
+ Objects.requireNonNull(sdkPackageName);
+ mSourceRegistrationRequest = sourceRegistrationRequest;
+ mAppPackageName = appPackageName;
+ mSdkPackageName = sdkPackageName;
+ mRequestTime = requestTime;
+ }
+
+ /** Pre-validates parameters and builds {@link WebSourceRegistrationRequestInternal}. */
+ @NonNull
+ public WebSourceRegistrationRequestInternal build() {
+ return new WebSourceRegistrationRequestInternal(this);
+ }
+
+ /** See {@link WebSourceRegistrationRequestInternal#isAdIdPermissionGranted()}. */
+ public WebSourceRegistrationRequestInternal.Builder setAdIdPermissionGranted(
+ boolean isAdIdPermissionGranted) {
+ mIsAdIdPermissionGranted = isAdIdPermissionGranted;
+ return this;
+ }
+ }
+}
diff --git a/android-35/android/adservices/measurement/WebTriggerParams.java b/android-35/android/adservices/measurement/WebTriggerParams.java
new file mode 100644
index 0000000..fc501c2
--- /dev/null
+++ b/android-35/android/adservices/measurement/WebTriggerParams.java
@@ -0,0 +1,159 @@
+/*
+ * 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.measurement;
+
+import android.annotation.NonNull;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/** Class holding trigger registration parameters. */
+public final class WebTriggerParams implements Parcelable {
+ /** Creator for Paracelable (via reflection). */
+ @NonNull
+ public static final Creator<WebTriggerParams> CREATOR =
+ new Creator<WebTriggerParams>() {
+ @Override
+ public WebTriggerParams createFromParcel(Parcel in) {
+ return new WebTriggerParams(in);
+ }
+
+ @Override
+ public WebTriggerParams[] newArray(int size) {
+ return new WebTriggerParams[size];
+ }
+ };
+ /**
+ * URI that the Attribution Reporting API sends a request to in order to obtain trigger
+ * registration parameters.
+ */
+ @NonNull private final Uri mRegistrationUri;
+ /**
+ * Used by the browser to indicate whether the debug key obtained from the registration URI is
+ * allowed to be used.
+ */
+ private final boolean mDebugKeyAllowed;
+
+ private WebTriggerParams(@NonNull Builder builder) {
+ mRegistrationUri = builder.mRegistrationUri;
+ mDebugKeyAllowed = builder.mDebugKeyAllowed;
+ }
+
+ /** Unpack a TriggerRegistration from a Parcel. */
+ private WebTriggerParams(@NonNull Parcel in) {
+ mRegistrationUri = Uri.CREATOR.createFromParcel(in);
+ mDebugKeyAllowed = in.readBoolean();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof WebTriggerParams)) return false;
+ WebTriggerParams that = (WebTriggerParams) o;
+ return mDebugKeyAllowed == that.mDebugKeyAllowed
+ && Objects.equals(mRegistrationUri, that.mRegistrationUri);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mRegistrationUri, mDebugKeyAllowed);
+ }
+
+ /** Getter for registration Uri. */
+ @NonNull
+ public Uri getRegistrationUri() {
+ return mRegistrationUri;
+ }
+
+ /**
+ * Getter for debug allowed/disallowed flag. Its value as {@code true} means to allow parsing
+ * debug keys from registration responses and their addition in the generated reports.
+ */
+ public boolean isDebugKeyAllowed() {
+ return mDebugKeyAllowed;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ Objects.requireNonNull(out);
+ mRegistrationUri.writeToParcel(out, flags);
+ out.writeBoolean(mDebugKeyAllowed);
+ }
+
+ /** A builder for {@link WebTriggerParams}. */
+ public static final class Builder {
+ /**
+ * URI that the Attribution Reporting API sends a request to in order to obtain trigger
+ * registration parameters.
+ */
+ @NonNull private final Uri mRegistrationUri;
+ /**
+ * Used by the browser to indicate whether the debug key obtained from the registration URI
+ * is allowed to be used.
+ */
+ private boolean mDebugKeyAllowed;
+
+ /**
+ * Builder constructor for {@link WebTriggerParams}. {@code mIsDebugKeyAllowed} is assigned
+ * false by default.
+ *
+ * @param registrationUri URI that the Attribution Reporting API sends a request to in order
+ * to obtain trigger registration parameters
+ * @throws IllegalArgumentException if the scheme for {@code registrationUri} is not HTTPS
+ */
+ public Builder(@NonNull Uri registrationUri) {
+ Objects.requireNonNull(registrationUri);
+ if (registrationUri.getScheme() == null
+ || !registrationUri.getScheme().equalsIgnoreCase("https")) {
+ throw new IllegalArgumentException("registrationUri must have an HTTPS scheme");
+ }
+ mRegistrationUri = registrationUri;
+ mDebugKeyAllowed = false;
+ }
+
+ /**
+ * Setter for debug allow/disallow flag. Setting it to true will allow parsing debug keys
+ * from registration responses and their addition in the generated reports.
+ *
+ * @param debugKeyAllowed used by the browser to indicate whether the debug key obtained
+ * from the registration URI is allowed to be used
+ * @return builder
+ */
+ @NonNull
+ public Builder setDebugKeyAllowed(boolean debugKeyAllowed) {
+ mDebugKeyAllowed = debugKeyAllowed;
+ return this;
+ }
+
+ /**
+ * Builds immutable {@link WebTriggerParams}.
+ *
+ * @return immutable {@link WebTriggerParams}
+ */
+ @NonNull
+ public WebTriggerParams build() {
+ return new WebTriggerParams(this);
+ }
+ }
+}
diff --git a/android-35/android/adservices/measurement/WebTriggerRegistrationRequest.java b/android-35/android/adservices/measurement/WebTriggerRegistrationRequest.java
new file mode 100644
index 0000000..a7a70d4
--- /dev/null
+++ b/android-35/android/adservices/measurement/WebTriggerRegistrationRequest.java
@@ -0,0 +1,152 @@
+/*
+ * 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.measurement;
+
+import android.annotation.NonNull;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/** Class to hold input to measurement trigger registration calls from web context. */
+public final class WebTriggerRegistrationRequest implements Parcelable {
+ private static final int WEB_TRIGGER_PARAMS_MAX_COUNT = 80;
+
+ /** Creator for Paracelable (via reflection). */
+ @NonNull
+ public static final Parcelable.Creator<WebTriggerRegistrationRequest> CREATOR =
+ new Parcelable.Creator<WebTriggerRegistrationRequest>() {
+ @Override
+ public WebTriggerRegistrationRequest createFromParcel(Parcel in) {
+ return new WebTriggerRegistrationRequest(in);
+ }
+
+ @Override
+ public WebTriggerRegistrationRequest[] newArray(int size) {
+ return new WebTriggerRegistrationRequest[size];
+ }
+ };
+ /** Registration info to fetch sources. */
+ @NonNull private final List<WebTriggerParams> mWebTriggerParams;
+
+ /** Destination {@link Uri}. */
+ @NonNull private final Uri mDestination;
+
+ private WebTriggerRegistrationRequest(@NonNull Builder builder) {
+ mWebTriggerParams = builder.mWebTriggerParams;
+ mDestination = builder.mDestination;
+ }
+
+ private WebTriggerRegistrationRequest(Parcel in) {
+ Objects.requireNonNull(in);
+ ArrayList<WebTriggerParams> webTriggerParams = new ArrayList<>();
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
+ in.readList(webTriggerParams, WebTriggerParams.class.getClassLoader());
+ } else {
+ in.readList(
+ webTriggerParams,
+ WebTriggerParams.class.getClassLoader(),
+ WebTriggerParams.class);
+ }
+ mWebTriggerParams = webTriggerParams;
+ mDestination = Uri.CREATOR.createFromParcel(in);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof WebTriggerRegistrationRequest)) return false;
+ WebTriggerRegistrationRequest that = (WebTriggerRegistrationRequest) o;
+ return Objects.equals(mWebTriggerParams, that.mWebTriggerParams)
+ && Objects.equals(mDestination, that.mDestination);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mWebTriggerParams, mDestination);
+ }
+
+ /** Getter for trigger params. */
+ @NonNull
+ public List<WebTriggerParams> getTriggerParams() {
+ return mWebTriggerParams;
+ }
+
+ /** Getter for destination. */
+ @NonNull
+ public Uri getDestination() {
+ return mDestination;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ Objects.requireNonNull(out);
+ out.writeList(mWebTriggerParams);
+ mDestination.writeToParcel(out, flags);
+ }
+
+ /** Builder for {@link WebTriggerRegistrationRequest}. */
+ public static final class Builder {
+ /**
+ * Registration info to fetch triggers. Maximum 80 registrations allowed at once, to be in
+ * sync with Chrome platform.
+ */
+ @NonNull private List<WebTriggerParams> mWebTriggerParams;
+ /** Top level origin of publisher app. */
+ @NonNull private final Uri mDestination;
+
+ /**
+ * Builder constructor for {@link WebTriggerRegistrationRequest}.
+ *
+ * @param webTriggerParams contains trigger registration parameters, the list should not be
+ * empty
+ * @param destination trigger destination {@link Uri}
+ */
+ public Builder(@NonNull List<WebTriggerParams> webTriggerParams, @NonNull Uri destination) {
+ Objects.requireNonNull(webTriggerParams);
+ if (webTriggerParams.isEmpty()
+ || webTriggerParams.size() > WEB_TRIGGER_PARAMS_MAX_COUNT) {
+ throw new IllegalArgumentException(
+ "web trigger params size is not within bounds, size: "
+ + webTriggerParams.size());
+ }
+
+ Objects.requireNonNull(destination);
+ if (destination.getScheme() == null) {
+ throw new IllegalArgumentException("Destination origin must have a scheme.");
+ }
+ mWebTriggerParams = webTriggerParams;
+ mDestination = destination;
+
+ }
+
+ /** Pre-validates parameters and builds {@link WebTriggerRegistrationRequest}. */
+ @NonNull
+ public WebTriggerRegistrationRequest build() {
+ return new WebTriggerRegistrationRequest(this);
+ }
+ }
+}
diff --git a/android-35/android/adservices/measurement/WebTriggerRegistrationRequestInternal.java b/android-35/android/adservices/measurement/WebTriggerRegistrationRequestInternal.java
new file mode 100644
index 0000000..b92d54c
--- /dev/null
+++ b/android-35/android/adservices/measurement/WebTriggerRegistrationRequestInternal.java
@@ -0,0 +1,167 @@
+/*
+ * 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.measurement;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Internal trigger registration request object to communicate from {@link MeasurementManager} to
+ * {@link IMeasurementService}.
+ *
+ * @hide
+ */
+public class WebTriggerRegistrationRequestInternal implements Parcelable {
+ /** Creator for Parcelable (via reflection). */
+ @NonNull
+ public static final Creator<WebTriggerRegistrationRequestInternal> CREATOR =
+ new Creator<WebTriggerRegistrationRequestInternal>() {
+ @Override
+ public WebTriggerRegistrationRequestInternal createFromParcel(Parcel in) {
+ return new WebTriggerRegistrationRequestInternal(in);
+ }
+
+ @Override
+ public WebTriggerRegistrationRequestInternal[] newArray(int size) {
+ return new WebTriggerRegistrationRequestInternal[size];
+ }
+ };
+ /** Holds input to measurement trigger registration calls from web context. */
+ @NonNull private final WebTriggerRegistrationRequest mTriggerRegistrationRequest;
+ /** Holds app package info of where the request is coming from. */
+ @NonNull private final String mAppPackageName;
+ /** Holds sdk package info of where the request is coming from. */
+ @NonNull private final String mSdkPackageName;
+ /** AD ID Permission Granted. */
+ private final boolean mIsAdIdPermissionGranted;
+
+ private WebTriggerRegistrationRequestInternal(@NonNull Builder builder) {
+ mTriggerRegistrationRequest = builder.mTriggerRegistrationRequest;
+ mAppPackageName = builder.mAppPackageName;
+ mSdkPackageName = builder.mSdkPackageName;
+ mIsAdIdPermissionGranted = builder.mIsAdIdPermissionGranted;
+ }
+
+ private WebTriggerRegistrationRequestInternal(Parcel in) {
+ Objects.requireNonNull(in);
+ mTriggerRegistrationRequest = WebTriggerRegistrationRequest.CREATOR.createFromParcel(in);
+ mAppPackageName = in.readString();
+ mSdkPackageName = in.readString();
+ mIsAdIdPermissionGranted = in.readBoolean();
+ }
+
+ /** Getter for {@link #mTriggerRegistrationRequest}. */
+ public WebTriggerRegistrationRequest getTriggerRegistrationRequest() {
+ return mTriggerRegistrationRequest;
+ }
+
+ /** Getter for {@link #mAppPackageName}. */
+ public String getAppPackageName() {
+ return mAppPackageName;
+ }
+
+ /** Getter for {@link #mSdkPackageName}. */
+ public String getSdkPackageName() {
+ return mSdkPackageName;
+ }
+
+ /** Getter for {@link #mIsAdIdPermissionGranted}. */
+ public boolean isAdIdPermissionGranted() {
+ return mIsAdIdPermissionGranted;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof WebTriggerRegistrationRequestInternal)) return false;
+ WebTriggerRegistrationRequestInternal that = (WebTriggerRegistrationRequestInternal) o;
+ return Objects.equals(mTriggerRegistrationRequest, that.mTriggerRegistrationRequest)
+ && Objects.equals(mAppPackageName, that.mAppPackageName)
+ && Objects.equals(mSdkPackageName, that.mSdkPackageName)
+ && Objects.equals(mIsAdIdPermissionGranted, that.mIsAdIdPermissionGranted);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(
+ mTriggerRegistrationRequest,
+ mAppPackageName,
+ mSdkPackageName,
+ mIsAdIdPermissionGranted);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ Objects.requireNonNull(out);
+ mTriggerRegistrationRequest.writeToParcel(out, flags);
+ out.writeString(mAppPackageName);
+ out.writeString(mSdkPackageName);
+ out.writeBoolean(mIsAdIdPermissionGranted);
+ }
+
+ /** Builder for {@link WebTriggerRegistrationRequestInternal}. */
+ public static final class Builder {
+ /** External trigger registration request from client app SDK. */
+ @NonNull private final WebTriggerRegistrationRequest mTriggerRegistrationRequest;
+ /** Package name of the app used for the registration. Used to determine the registrant. */
+ @NonNull private final String mAppPackageName;
+ /** Package name of the sdk used for the registration. */
+ @NonNull private final String mSdkPackageName;
+ /** AD ID Permission Granted. */
+ private boolean mIsAdIdPermissionGranted;
+
+ /**
+ * Builder constructor for {@link WebTriggerRegistrationRequestInternal}.
+ *
+ * @param triggerRegistrationRequest external trigger registration request
+ * @param appPackageName app package name that is calling PP API
+ * @param sdkPackageName sdk package name that is calling PP API
+ */
+ public Builder(
+ @NonNull WebTriggerRegistrationRequest triggerRegistrationRequest,
+ @NonNull String appPackageName,
+ @NonNull String sdkPackageName) {
+ Objects.requireNonNull(triggerRegistrationRequest);
+ Objects.requireNonNull(appPackageName);
+ Objects.requireNonNull(sdkPackageName);
+ mTriggerRegistrationRequest = triggerRegistrationRequest;
+ mAppPackageName = appPackageName;
+ mSdkPackageName = sdkPackageName;
+ }
+
+ /** Pre-validates parameters and builds {@link WebTriggerRegistrationRequestInternal}. */
+ @NonNull
+ public WebTriggerRegistrationRequestInternal build() {
+ return new WebTriggerRegistrationRequestInternal(this);
+ }
+
+ /** See {@link WebTriggerRegistrationRequestInternal#isAdIdPermissionGranted()}. */
+ public WebTriggerRegistrationRequestInternal.Builder setAdIdPermissionGranted(
+ boolean isAdIdPermissionGranted) {
+ mIsAdIdPermissionGranted = isAdIdPermissionGranted;
+ return this;
+ }
+ }
+}
diff --git a/android-35/android/adservices/ondevicepersonalization/AppInfo.java b/android-35/android/adservices/ondevicepersonalization/AppInfo.java
new file mode 100644
index 0000000..57ce7f9
--- /dev/null
+++ b/android-35/android/adservices/ondevicepersonalization/AppInfo.java
@@ -0,0 +1,202 @@
+/*
+ * 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.os.Parcelable;
+
+import com.android.adservices.ondevicepersonalization.flags.Flags;
+import com.android.ondevicepersonalization.internal.util.AnnotationValidations;
+import com.android.ondevicepersonalization.internal.util.DataClass;
+
+/**
+ * Information about apps.
+ *
+ */
+@FlaggedApi(Flags.FLAG_ON_DEVICE_PERSONALIZATION_APIS_ENABLED)
+@DataClass(genHiddenBuilder = true, genEqualsHashCode = true)
+public final class AppInfo implements Parcelable {
+ /** Whether the app is installed. */
+ @NonNull boolean mInstalled = false;
+
+
+
+ // 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/AppInfo.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 */ 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.
+ */
+ @DataClass.Generated.Member
+ public @NonNull boolean isInstalled() {
+ return mInstalled;
+ }
+
+ @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(AppInfo other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ AppInfo that = (AppInfo) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && mInstalled == that.mInstalled;
+ }
+
+ @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 + Boolean.hashCode(mInstalled);
+ return _hash;
+ }
+
+ @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) { ... }
+
+ byte flg = 0;
+ if (mInstalled) flg |= 0x1;
+ dest.writeByte(flg);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ AppInfo(@NonNull android.os.Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ byte flg = in.readByte();
+ boolean installed = (flg & 0x1) != 0;
+
+ this.mInstalled = installed;
+ AnnotationValidations.validate(
+ NonNull.class, null, mInstalled);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<AppInfo> CREATOR
+ = new Parcelable.Creator<AppInfo>() {
+ @Override
+ public AppInfo[] newArray(int size) {
+ return new AppInfo[size];
+ }
+
+ @Override
+ public AppInfo createFromParcel(@NonNull android.os.Parcel in) {
+ return new AppInfo(in);
+ }
+ };
+
+ /**
+ * A builder for {@link AppInfo}
+ * @hide
+ */
+ @SuppressWarnings("WeakerAccess")
+ @DataClass.Generated.Member
+ public static final class Builder {
+
+ private @NonNull boolean mInstalled;
+
+ private long mBuilderFieldsSet = 0L;
+
+ public Builder() {
+ }
+
+ /**
+ * Whether the app is installed.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setInstalled(@NonNull boolean value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x1;
+ mInstalled = value;
+ return this;
+ }
+
+ /** Builds the instance. This builder should not be touched after calling this! */
+ public @NonNull AppInfo build() {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x2; // Mark builder used
+
+ if ((mBuilderFieldsSet & 0x1) == 0) {
+ mInstalled = false;
+ }
+ AppInfo o = new AppInfo(
+ mInstalled);
+ 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 = 1695492606666L,
+ codegenVersion = "1.0.23",
+ sourceFile = "packages/modules/OnDevicePersonalization/framework/java/android/adservices/ondevicepersonalization/AppInfo.java",
+ inputSignatures = " @android.annotation.NonNull boolean mInstalled\nclass AppInfo extends java.lang.Object implements [android.os.Parcelable]\[email protected](genHiddenBuilder=true, genEqualsHashCode=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/android-35/android/adservices/ondevicepersonalization/CalleeMetadata.java b/android-35/android/adservices/ondevicepersonalization/CalleeMetadata.java
new file mode 100644
index 0000000..f5ad7a0
--- /dev/null
+++ b/android-35/android/adservices/ondevicepersonalization/CalleeMetadata.java
@@ -0,0 +1,191 @@
+/*
+ * 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.NonNull;
+import android.os.Parcelable;
+
+import com.android.ondevicepersonalization.internal.util.DataClass;
+
+/**
+ * Wrapper class for additional information returned with IPC results.
+*
+* @hide
+*/
+@DataClass(genBuilder = true, genEqualsHashCode = true)
+public final class CalleeMetadata implements Parcelable {
+ /** Time elapsed in callee, as measured by callee. */
+ private long mElapsedTimeMillis = 0;
+
+
+
+ // 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/CalleeMetadata.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 */ CalleeMetadata(
+ long elapsedTimeMillis) {
+ this.mElapsedTimeMillis = elapsedTimeMillis;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * Time elapsed in callee, as measured by callee.
+ */
+ @DataClass.Generated.Member
+ public long getElapsedTimeMillis() {
+ return mElapsedTimeMillis;
+ }
+
+ @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(CalleeMetadata other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ CalleeMetadata that = (CalleeMetadata) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && mElapsedTimeMillis == that.mElapsedTimeMillis;
+ }
+
+ @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 + Long.hashCode(mElapsedTimeMillis);
+ return _hash;
+ }
+
+ @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.writeLong(mElapsedTimeMillis);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ CalleeMetadata(@NonNull android.os.Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ long elapsedTimeMillis = in.readLong();
+
+ this.mElapsedTimeMillis = elapsedTimeMillis;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<CalleeMetadata> CREATOR
+ = new Parcelable.Creator<CalleeMetadata>() {
+ @Override
+ public CalleeMetadata[] newArray(int size) {
+ return new CalleeMetadata[size];
+ }
+
+ @Override
+ public CalleeMetadata createFromParcel(@NonNull android.os.Parcel in) {
+ return new CalleeMetadata(in);
+ }
+ };
+
+ /**
+ * A builder for {@link CalleeMetadata}
+ */
+ @SuppressWarnings("WeakerAccess")
+ @DataClass.Generated.Member
+ public static final class Builder {
+
+ private long mElapsedTimeMillis;
+
+ private long mBuilderFieldsSet = 0L;
+
+ public Builder() {
+ }
+
+ /**
+ * Time elapsed in callee, as measured by callee.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setElapsedTimeMillis(long value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x1;
+ mElapsedTimeMillis = value;
+ return this;
+ }
+
+ /** Builds the instance. This builder should not be touched after calling this! */
+ public @NonNull CalleeMetadata build() {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x2; // Mark builder used
+
+ if ((mBuilderFieldsSet & 0x1) == 0) {
+ mElapsedTimeMillis = 0;
+ }
+ CalleeMetadata o = new CalleeMetadata(
+ mElapsedTimeMillis);
+ 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 = 1696885546254L,
+ codegenVersion = "1.0.23",
+ sourceFile = "packages/modules/OnDevicePersonalization/framework/java/android/adservices/ondevicepersonalization/CalleeMetadata.java",
+ inputSignatures = "private long mElapsedTimeMillis\nclass CalleeMetadata extends java.lang.Object implements [android.os.Parcelable]\[email protected](genBuilder=true, genEqualsHashCode=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/android-35/android/adservices/ondevicepersonalization/CallerMetadata.java b/android-35/android/adservices/ondevicepersonalization/CallerMetadata.java
new file mode 100644
index 0000000..68a9cd8
--- /dev/null
+++ b/android-35/android/adservices/ondevicepersonalization/CallerMetadata.java
@@ -0,0 +1,191 @@
+/*
+ * 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.NonNull;
+import android.os.Parcelable;
+
+import com.android.ondevicepersonalization.internal.util.DataClass;
+
+/**
+ * Wrapper class for additional information passed to IPC requests.
+*
+* @hide
+*/
+@DataClass(genBuilder = true, genEqualsHashCode = true)
+public final class CallerMetadata implements Parcelable {
+ /** Start time of the operation. */
+ private long mStartTimeMillis = 0;
+
+
+
+ // 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/CallerMetadata.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 */ CallerMetadata(
+ long startTimeMillis) {
+ this.mStartTimeMillis = startTimeMillis;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * Start time of the operation.
+ */
+ @DataClass.Generated.Member
+ public long getStartTimeMillis() {
+ return mStartTimeMillis;
+ }
+
+ @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(CallerMetadata other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ CallerMetadata that = (CallerMetadata) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && mStartTimeMillis == that.mStartTimeMillis;
+ }
+
+ @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 + Long.hashCode(mStartTimeMillis);
+ return _hash;
+ }
+
+ @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.writeLong(mStartTimeMillis);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ CallerMetadata(@NonNull android.os.Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ long startTimeMillis = in.readLong();
+
+ this.mStartTimeMillis = startTimeMillis;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<CallerMetadata> CREATOR
+ = new Parcelable.Creator<CallerMetadata>() {
+ @Override
+ public CallerMetadata[] newArray(int size) {
+ return new CallerMetadata[size];
+ }
+
+ @Override
+ public CallerMetadata createFromParcel(@NonNull android.os.Parcel in) {
+ return new CallerMetadata(in);
+ }
+ };
+
+ /**
+ * A builder for {@link CallerMetadata}
+ */
+ @SuppressWarnings("WeakerAccess")
+ @DataClass.Generated.Member
+ public static final class Builder {
+
+ private long mStartTimeMillis;
+
+ private long mBuilderFieldsSet = 0L;
+
+ public Builder() {
+ }
+
+ /**
+ * Start time of the operation.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setStartTimeMillis(long value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x1;
+ mStartTimeMillis = value;
+ return this;
+ }
+
+ /** Builds the instance. This builder should not be touched after calling this! */
+ public @NonNull CallerMetadata build() {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x2; // Mark builder used
+
+ if ((mBuilderFieldsSet & 0x1) == 0) {
+ mStartTimeMillis = 0;
+ }
+ CallerMetadata o = new CallerMetadata(
+ mStartTimeMillis);
+ 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 = 1696884555838L,
+ codegenVersion = "1.0.23",
+ sourceFile = "packages/modules/OnDevicePersonalization/framework/java/android/adservices/ondevicepersonalization/CallerMetadata.java",
+ inputSignatures = "private long mStartTimeMillis\nclass CallerMetadata extends java.lang.Object implements [android.os.Parcelable]\[email protected](genBuilder=true, genEqualsHashCode=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/android-35/android/adservices/ondevicepersonalization/Constants.java b/android-35/android/adservices/ondevicepersonalization/Constants.java
new file mode 100644
index 0000000..c7c1e45
--- /dev/null
+++ b/android-35/android/adservices/ondevicepersonalization/Constants.java
@@ -0,0 +1,124 @@
+/*
+ * 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;
+
+/**
+ * Constants used internally in the OnDevicePersonalization Module and not used in public APIs.
+ *
+ * @hide
+ */
+public class Constants {
+ // Status codes used within the ODP service or returned from service to manager classes.
+ // These will be mapped to existing platform exceptions or subclasses of
+ // OnDevicePersonalizationException in APIs.
+ public static final int STATUS_SUCCESS = 0;
+ public static final int STATUS_INTERNAL_ERROR = 100;
+ 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;
+ public static final int STATUS_PERSONALIZATION_DISABLED = 104;
+ public static final int STATUS_KEY_NOT_FOUND = 105;
+
+ // Operations implemented by IsolatedService.
+ public static final int OP_EXECUTE = 1;
+ public static final int OP_DOWNLOAD = 2;
+ public static final int OP_RENDER = 3;
+ public static final int OP_WEB_VIEW_EVENT = 4;
+ public static final int OP_TRAINING_EXAMPLE = 5;
+ public static final int OP_WEB_TRIGGER = 6;
+
+ // Keys for Bundle objects passed between processes.
+ public static final String EXTRA_APP_PARAMS_SERIALIZED =
+ "android.ondevicepersonalization.extra.app_params_serialized";
+ public static final String EXTRA_CALLEE_METADATA =
+ "android.ondevicepersonalization.extra.callee_metadata";
+ public static final String EXTRA_DATA_ACCESS_SERVICE_BINDER =
+ "android.ondevicepersonalization.extra.data_access_service_binder";
+ public static final String EXTRA_FEDERATED_COMPUTE_SERVICE_BINDER =
+ "android.ondevicepersonalization.extra.federated_computation_service_binder";
+ public static final String EXTRA_MODEL_SERVICE_BINDER =
+ "android.ondevicepersonalization.extra.model_service_binder";
+ public static final String EXTRA_DESTINATION_URL =
+ "android.ondevicepersonalization.extra.destination_url";
+ public static final String EXTRA_EVENT_PARAMS =
+ "android.ondevicepersonalization.extra.event_params";
+ public static final String EXTRA_INPUT = "android.ondevicepersonalization.extra.input";
+ public static final String EXTRA_LOOKUP_KEYS =
+ "android.ondevicepersonalization.extra.lookup_keys";
+ public static final String EXTRA_MEASUREMENT_WEB_TRIGGER_PARAMS =
+ "android.adservices.ondevicepersonalization.measurement_web_trigger_params";
+ 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_RESPONSE_DATA =
+ "android.ondevicepersonalization.extra.response_data";
+ public static final String EXTRA_RESULT = "android.ondevicepersonalization.extra.result";
+ public static final String EXTRA_SURFACE_PACKAGE_TOKEN_STRING =
+ "android.ondevicepersonalization.extra.surface_package_token_string";
+ public static final String EXTRA_USER_DATA = "android.ondevicepersonalization.extra.user_data";
+ public static final String EXTRA_VALUE = "android.ondevicepersonalization.extra.value";
+ public static final String EXTRA_MODEL_INPUTS =
+ "android.ondevicepersonalization.extra.model_inputs";
+ public static final String EXTRA_MODEL_OUTPUTS =
+ "android.ondevicepersonalization.extra.model_outputs";
+ // Inference related constants,
+ public static final String EXTRA_INFERENCE_INPUT =
+ "android.ondevicepersonalization.extra.inference_input";
+ public static final String EXTRA_MODEL_ID = "android.ondevicepersonalization.extra.model_id";
+
+ // API Names for API metrics logging. Must match the values in
+ // frameworks/proto_logging/stats/atoms/ondevicepersonalization/ondevicepersonalization_extension_atoms.proto
+ public static final int API_NAME_UNKNOWN = 0;
+ public static final int API_NAME_EXECUTE = 1;
+ public static final int API_NAME_REQUEST_SURFACE_PACKAGE = 2;
+ public static final int API_NAME_SERVICE_ON_EXECUTE = 3;
+ public static final int API_NAME_SERVICE_ON_DOWNLOAD_COMPLETED = 4;
+ public static final int API_NAME_SERVICE_ON_RENDER = 5;
+ public static final int API_NAME_SERVICE_ON_EVENT = 6;
+ public static final int API_NAME_SERVICE_ON_TRAINING_EXAMPLE = 7;
+ public static final int API_NAME_SERVICE_ON_WEB_TRIGGER = 8;
+ public static final int API_NAME_REMOTE_DATA_GET = 9;
+ public static final int API_NAME_REMOTE_DATA_KEYSET = 10;
+ public static final int API_NAME_LOCAL_DATA_GET = 11;
+ public static final int API_NAME_LOCAL_DATA_KEYSET = 12;
+ public static final int API_NAME_LOCAL_DATA_PUT = 13;
+ public static final int API_NAME_LOCAL_DATA_REMOVE = 14;
+ public static final int API_NAME_EVENT_URL_CREATE_WITH_RESPONSE = 15;
+ public static final int API_NAME_EVENT_URL_CREATE_WITH_REDIRECT = 16;
+ public static final int API_NAME_LOG_READER_GET_REQUESTS = 17;
+ public static final int API_NAME_LOG_READER_GET_JOINED_EVENTS = 18;
+ public static final int API_NAME_FEDERATED_COMPUTE_SCHEDULE = 19;
+ public static final int API_NAME_FEDERATED_COMPUTE_CANCEL = 21;
+ public static final int API_NAME_MODEL_MANAGER_RUN = 20;
+
+ // Data Access Service operations.
+ public static final int DATA_ACCESS_OP_REMOTE_DATA_LOOKUP = 1;
+ public static final int DATA_ACCESS_OP_REMOTE_DATA_KEYSET = 2;
+ public static final int DATA_ACCESS_OP_GET_EVENT_URL = 3;
+ public static final int DATA_ACCESS_OP_LOCAL_DATA_LOOKUP = 4;
+ public static final int DATA_ACCESS_OP_LOCAL_DATA_KEYSET = 5;
+ public static final int DATA_ACCESS_OP_LOCAL_DATA_PUT = 6;
+ public static final int DATA_ACCESS_OP_LOCAL_DATA_REMOVE = 7;
+ public static final int DATA_ACCESS_OP_GET_REQUESTS = 8;
+ public static final int DATA_ACCESS_OP_GET_JOINED_EVENTS = 9;
+ public static final int DATA_ACCESS_OP_GET_MODEL = 10;
+
+ // Measurement event types for measurement events received from the OS.
+ public static final int MEASUREMENT_EVENT_TYPE_WEB_TRIGGER = 1;
+
+ private Constants() {}
+}
diff --git a/android-35/android/adservices/ondevicepersonalization/DownloadCompletedInput.java b/android-35/android/adservices/ondevicepersonalization/DownloadCompletedInput.java
new file mode 100644
index 0000000..147d2da
--- /dev/null
+++ b/android-35/android/adservices/ondevicepersonalization/DownloadCompletedInput.java
@@ -0,0 +1,169 @@
+/*
+ * 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 data for {@link
+ * IsolatedWorker#onDownloadCompleted(DownloadCompletedInput, android.os.OutcomeReceiver)}.
+ *
+ */
+@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.
+ */
+ @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(
+ @NonNull KeyValueStore downloadedContents) {
+ this.mDownloadedContents = downloadedContents;
+ AnnotationValidations.validate(
+ NonNull.class, null, mDownloadedContents);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * 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")
+ DownloadCompletedInput that = (DownloadCompletedInput) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && java.util.Objects.equals(mDownloadedContents, that.mDownloadedContents);
+ }
+
+ @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;
+ }
+
+ /**
+ * 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/android-35/android/adservices/ondevicepersonalization/DownloadCompletedOutput.java b/android-35/android/adservices/ondevicepersonalization/DownloadCompletedOutput.java
new file mode 100644
index 0000000..538e17c
--- /dev/null
+++ b/android-35/android/adservices/ondevicepersonalization/DownloadCompletedOutput.java
@@ -0,0 +1,172 @@
+/*
+ * 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;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * The result returned by {@link
+ * IsolatedWorker#onDownloadCompleted(DownloadCompletedInput, android.os.OutcomeReceiver)}.
+ *
+ */
+@FlaggedApi(Flags.FLAG_ON_DEVICE_PERSONALIZATION_APIS_ENABLED)
+@DataClass(genBuilder = true, genEqualsHashCode = true)
+public final class DownloadCompletedOutput {
+ /**
+ * The keys to be retained in the REMOTE_DATA table. Any existing keys that are not
+ * present in this list are removed from the table.
+ */
+ @DataClass.PluralOf("retainedKey")
+ @NonNull private List<String> mRetainedKeys = Collections.emptyList();
+
+
+
+ // 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/DownloadCompletedOutput.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 */ DownloadCompletedOutput(
+ @NonNull List<String> retainedKeys) {
+ this.mRetainedKeys = retainedKeys;
+ AnnotationValidations.validate(
+ NonNull.class, null, mRetainedKeys);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * The keys to be retained in the REMOTE_DATA table. Any existing keys that are not
+ * present in this list are removed from the table.
+ */
+ @DataClass.Generated.Member
+ public @NonNull List<String> getRetainedKeys() {
+ return mRetainedKeys;
+ }
+
+ @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(DownloadCompletedOutput other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ DownloadCompletedOutput that = (DownloadCompletedOutput) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && java.util.Objects.equals(mRetainedKeys, that.mRetainedKeys);
+ }
+
+ @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(mRetainedKeys);
+ return _hash;
+ }
+
+ /**
+ * A builder for {@link DownloadCompletedOutput}
+ */
+ @SuppressWarnings("WeakerAccess")
+ @DataClass.Generated.Member
+ public static final class Builder {
+
+ private @NonNull List<String> mRetainedKeys;
+
+ private long mBuilderFieldsSet = 0L;
+
+ public Builder() {
+ }
+
+ /**
+ * The keys to be retained in the REMOTE_DATA table. Any existing keys that are not
+ * present in this list are removed from the table.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setRetainedKeys(@NonNull List<String> value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x1;
+ mRetainedKeys = value;
+ return this;
+ }
+
+ /** @see #setRetainedKeys */
+ @DataClass.Generated.Member
+ public @NonNull Builder addRetainedKey(@NonNull String value) {
+ if (mRetainedKeys == null) setRetainedKeys(new java.util.ArrayList<>());
+ mRetainedKeys.add(value);
+ return this;
+ }
+
+ /** Builds the instance. This builder should not be touched after calling this! */
+ public @NonNull DownloadCompletedOutput build() {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x2; // Mark builder used
+
+ if ((mBuilderFieldsSet & 0x1) == 0) {
+ mRetainedKeys = Collections.emptyList();
+ }
+ DownloadCompletedOutput o = new DownloadCompletedOutput(
+ mRetainedKeys);
+ 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 = 1698862918590L,
+ codegenVersion = "1.0.23",
+ sourceFile = "packages/modules/OnDevicePersonalization/framework/java/android/adservices/ondevicepersonalization/DownloadCompletedOutput.java",
+ inputSignatures = "private @com.android.ondevicepersonalization.internal.util.DataClass.PluralOf(\"retainedKey\") @android.annotation.NonNull java.util.List<java.lang.String> mRetainedKeys\nclass DownloadCompletedOutput extends java.lang.Object implements []\[email protected](genBuilder=true, genEqualsHashCode=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/android-35/android/adservices/ondevicepersonalization/DownloadCompletedOutputParcel.java b/android-35/android/adservices/ondevicepersonalization/DownloadCompletedOutputParcel.java
new file mode 100644
index 0000000..7a906f1
--- /dev/null
+++ b/android-35/android/adservices/ondevicepersonalization/DownloadCompletedOutputParcel.java
@@ -0,0 +1,142 @@
+/*
+ * 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.NonNull;
+import android.os.Parcelable;
+
+import com.android.ondevicepersonalization.internal.util.AnnotationValidations;
+import com.android.ondevicepersonalization.internal.util.DataClass;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Parcelable version of {@link DownloadCompletedOutput}.
+ * @hide
+ */
+@DataClass(genAidl = false, genBuilder = false)
+public final class DownloadCompletedOutputParcel implements Parcelable {
+ /**
+ * The keys to be retained in the REMOTE_DATA table. Any existing keys that are not
+ * present in this list are removed from the table.
+ */
+ @NonNull private List<String> mRetainedKeys = Collections.emptyList();
+
+ /** @hide */
+ public DownloadCompletedOutputParcel(@NonNull DownloadCompletedOutput value) {
+ this(value.getRetainedKeys());
+ }
+
+
+
+ // 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/DownloadCompletedOutputParcel.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /**
+ * Creates a new DownloadCompletedOutputParcel.
+ *
+ * @param retainedKeys
+ * The keys to be retained in the REMOTE_DATA table. Any existing keys that are not
+ * present in this list are removed from the table.
+ */
+ @DataClass.Generated.Member
+ public DownloadCompletedOutputParcel(
+ @NonNull List<String> retainedKeys) {
+ this.mRetainedKeys = retainedKeys;
+ AnnotationValidations.validate(
+ NonNull.class, null, mRetainedKeys);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * The keys to be retained in the REMOTE_DATA table. Any existing keys that are not
+ * present in this list are removed from the table.
+ */
+ @DataClass.Generated.Member
+ public @NonNull List<String> getRetainedKeys() {
+ return mRetainedKeys;
+ }
+
+ @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.writeStringList(mRetainedKeys);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ DownloadCompletedOutputParcel(@NonNull android.os.Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ List<String> retainedKeys = new java.util.ArrayList<>();
+ in.readStringList(retainedKeys);
+
+ this.mRetainedKeys = retainedKeys;
+ AnnotationValidations.validate(
+ NonNull.class, null, mRetainedKeys);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<DownloadCompletedOutputParcel> CREATOR
+ = new Parcelable.Creator<DownloadCompletedOutputParcel>() {
+ @Override
+ public DownloadCompletedOutputParcel[] newArray(int size) {
+ return new DownloadCompletedOutputParcel[size];
+ }
+
+ @Override
+ public DownloadCompletedOutputParcel createFromParcel(@NonNull android.os.Parcel in) {
+ return new DownloadCompletedOutputParcel(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1698783477713L,
+ codegenVersion = "1.0.23",
+ sourceFile = "packages/modules/OnDevicePersonalization/framework/java/android/adservices/ondevicepersonalization/DownloadCompletedOutputParcel.java",
+ inputSignatures = "private @android.annotation.NonNull java.util.List<java.lang.String> mRetainedKeys\nclass DownloadCompletedOutputParcel 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/android-35/android/adservices/ondevicepersonalization/DownloadInputParcel.java b/android-35/android/adservices/ondevicepersonalization/DownloadInputParcel.java
new file mode 100644
index 0000000..df8ce75
--- /dev/null
+++ b/android-35/android/adservices/ondevicepersonalization/DownloadInputParcel.java
@@ -0,0 +1,197 @@
+/*
+ * 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;
+
+import android.annotation.Nullable;
+import android.os.IBinder;
+import android.os.Parcelable;
+
+import com.android.ondevicepersonalization.internal.util.DataClass;
+
+/**
+ * The input sent to the {@link IsolatedService}.
+ *
+ * @hide
+ */
+@DataClass(genBuilder = true, genEqualsHashCode = true)
+public class DownloadInputParcel implements Parcelable {
+ /** DataAccessService binder for downloaded content */
+ @Nullable
+ IBinder mDataAccessServiceBinder = null;
+
+
+
+ // 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/DownloadInputParcel.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 */ DownloadInputParcel(
+ @Nullable IBinder dataAccessServiceBinder) {
+ this.mDataAccessServiceBinder = dataAccessServiceBinder;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * DataAccessService binder for downloaded content
+ */
+ @DataClass.Generated.Member
+ public @Nullable IBinder getDataAccessServiceBinder() {
+ return mDataAccessServiceBinder;
+ }
+
+ @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(DownloadInputParcel other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ DownloadInputParcel that = (DownloadInputParcel) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && java.util.Objects.equals(mDataAccessServiceBinder, that.mDataAccessServiceBinder);
+ }
+
+ @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(mDataAccessServiceBinder);
+ return _hash;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@android.annotation.NonNull android.os.Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ byte flg = 0;
+ if (mDataAccessServiceBinder != null) flg |= 0x1;
+ dest.writeByte(flg);
+ if (mDataAccessServiceBinder != null) dest.writeStrongBinder(mDataAccessServiceBinder);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ protected DownloadInputParcel(@android.annotation.NonNull android.os.Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ byte flg = in.readByte();
+ IBinder dataAccessServiceBinder = (flg & 0x1) == 0 ? null : (IBinder) in.readStrongBinder();
+
+ this.mDataAccessServiceBinder = dataAccessServiceBinder;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @android.annotation.NonNull Parcelable.Creator<DownloadInputParcel> CREATOR
+ = new Parcelable.Creator<DownloadInputParcel>() {
+ @Override
+ public DownloadInputParcel[] newArray(int size) {
+ return new DownloadInputParcel[size];
+ }
+
+ @Override
+ public DownloadInputParcel createFromParcel(@android.annotation.NonNull android.os.Parcel in) {
+ return new DownloadInputParcel(in);
+ }
+ };
+
+ /**
+ * A builder for {@link DownloadInputParcel}
+ */
+ @SuppressWarnings("WeakerAccess")
+ @DataClass.Generated.Member
+ public static class Builder {
+
+ private @Nullable IBinder mDataAccessServiceBinder;
+
+ private long mBuilderFieldsSet = 0L;
+
+ public Builder() {
+ }
+
+ /**
+ * DataAccessService binder for downloaded content
+ */
+ @DataClass.Generated.Member
+ public @android.annotation.NonNull Builder setDataAccessServiceBinder(@android.annotation.NonNull IBinder value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x1;
+ mDataAccessServiceBinder = value;
+ return this;
+ }
+
+ /** Builds the instance. This builder should not be touched after calling this! */
+ public @android.annotation.NonNull DownloadInputParcel build() {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x2; // Mark builder used
+
+ if ((mBuilderFieldsSet & 0x1) == 0) {
+ mDataAccessServiceBinder = null;
+ }
+ DownloadInputParcel o = new DownloadInputParcel(
+ mDataAccessServiceBinder);
+ 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 = 1705968510939L,
+ codegenVersion = "1.0.23",
+ sourceFile = "packages/modules/OnDevicePersonalization/framework/java/android/adservices/ondevicepersonalization/DownloadInputParcel.java",
+ inputSignatures = " @android.annotation.Nullable android.adservices.ondevicepersonalization.IBinder mDataAccessServiceBinder\nclass DownloadInputParcel extends java.lang.Object implements [android.os.Parcelable]\[email protected](genBuilder=true, genEqualsHashCode=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/android-35/android/adservices/ondevicepersonalization/EventInput.java b/android-35/android/adservices/ondevicepersonalization/EventInput.java
new file mode 100644
index 0000000..0ee3f67
--- /dev/null
+++ b/android-35/android/adservices/ondevicepersonalization/EventInput.java
@@ -0,0 +1,152 @@
+/*
+ * 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;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+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;
+
+/**
+ * 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
+ * {@link IsolatedWorker#onExecute(ExecuteInput, android.os.OutcomeReceiver)}.
+ */
+ @Nullable private RequestLogRecord mRequestLogRecord = null;
+
+ /**
+ * The Event URL parameters that the service passed to {@link
+ * EventUrlProvider#createEventTrackingUrlWithResponse(PersistableBundle, byte[], String)}
+ * or {@link EventUrlProvider#createEventTrackingUrlWithRedirect(PersistableBundle, Uri)}.
+ */
+ @NonNull private PersistableBundle mParameters = PersistableBundle.EMPTY;
+
+ /** @hide */
+ public EventInput(@NonNull EventInputParcel parcel) {
+ 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.
+ *
+ * @param requestLogRecord
+ * The {@link RequestLogRecord} that was returned as a result of
+ * {@link IsolatedWorker#onExecute(ExecuteInput, android.os.OutcomeReceiver)}.
+ * @param parameters
+ * 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
+ 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
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * The Event URL parameters that the service passed to {@link
+ * 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")
+ EventInput that = (EventInput) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && java.util.Objects.equals(mRequestLogRecord, that.mRequestLogRecord)
+ && java.util.Objects.equals(mParameters, that.mParameters);
+ }
+
+ @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/android-35/android/adservices/ondevicepersonalization/EventInputParcel.java b/android-35/android/adservices/ondevicepersonalization/EventInputParcel.java
new file mode 100644
index 0000000..a94bd66
--- /dev/null
+++ b/android-35/android/adservices/ondevicepersonalization/EventInputParcel.java
@@ -0,0 +1,220 @@
+/*
+ * 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;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcelable;
+import android.os.PersistableBundle;
+
+import com.android.ondevicepersonalization.internal.util.AnnotationValidations;
+import com.android.ondevicepersonalization.internal.util.DataClass;
+
+/**
+ * Parcelable version of {@link EventInput}.
+ * @hide
+ */
+@DataClass(genAidl = false, genHiddenBuilder = true)
+public final class EventInputParcel implements Parcelable {
+ /**
+ * The {@link RequestLogRecord} that was returned as a result of
+ * {@link IsolatedWorker#onExecute(ExecuteInput, android.os.OutcomeReceiver)}.
+ */
+ @Nullable private RequestLogRecord mRequestLogRecord = null;
+
+ /**
+ * The Event URL parameters that the service passed to {@link
+ * EventUrlProvider#createEventTrackingUrlWithResponse(PersistableBundle, byte[], String)}
+ * or {@link EventUrlProvider#createEventTrackingUrlWithRedirect(PersistableBundle, Uri)}.
+ */
+ @NonNull private PersistableBundle mParameters = PersistableBundle.EMPTY;
+
+
+
+ // 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/EventInputParcel.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 */ EventInputParcel(
+ @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
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * The Event URL parameters that the service passed to {@link
+ * 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 void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ byte flg = 0;
+ if (mRequestLogRecord != null) flg |= 0x1;
+ dest.writeByte(flg);
+ if (mRequestLogRecord != null) dest.writeTypedObject(mRequestLogRecord, flags);
+ dest.writeTypedObject(mParameters, flags);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ EventInputParcel(@NonNull android.os.Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ byte flg = in.readByte();
+ RequestLogRecord requestLogRecord = (flg & 0x1) == 0 ? null : (RequestLogRecord) in.readTypedObject(RequestLogRecord.CREATOR);
+ PersistableBundle parameters = (PersistableBundle) in.readTypedObject(PersistableBundle.CREATOR);
+
+ this.mRequestLogRecord = requestLogRecord;
+ this.mParameters = parameters;
+ AnnotationValidations.validate(
+ NonNull.class, null, mParameters);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<EventInputParcel> CREATOR
+ = new Parcelable.Creator<EventInputParcel>() {
+ @Override
+ public EventInputParcel[] newArray(int size) {
+ return new EventInputParcel[size];
+ }
+
+ @Override
+ public EventInputParcel createFromParcel(@NonNull android.os.Parcel in) {
+ return new EventInputParcel(in);
+ }
+ };
+
+ /**
+ * A builder for {@link EventInputParcel}
+ * @hide
+ */
+ @SuppressWarnings("WeakerAccess")
+ @DataClass.Generated.Member
+ public static final class Builder {
+
+ private @Nullable RequestLogRecord mRequestLogRecord;
+ private @NonNull PersistableBundle mParameters;
+
+ private long mBuilderFieldsSet = 0L;
+
+ public Builder() {
+ }
+
+ /**
+ * The {@link RequestLogRecord} that was returned as a result of
+ * {@link IsolatedWorker#onExecute(ExecuteInput, android.os.OutcomeReceiver)}.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setRequestLogRecord(@NonNull RequestLogRecord value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x1;
+ mRequestLogRecord = value;
+ return this;
+ }
+
+ /**
+ * The Event URL parameters that the service passed to {@link
+ * EventUrlProvider#createEventTrackingUrlWithResponse(PersistableBundle, byte[], String)}
+ * or {@link EventUrlProvider#createEventTrackingUrlWithRedirect(PersistableBundle, Uri)}.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setParameters(@NonNull PersistableBundle value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x2;
+ mParameters = value;
+ return this;
+ }
+
+ /** Builds the instance. This builder should not be touched after calling this! */
+ public @NonNull EventInputParcel build() {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x4; // Mark builder used
+
+ if ((mBuilderFieldsSet & 0x1) == 0) {
+ mRequestLogRecord = null;
+ }
+ if ((mBuilderFieldsSet & 0x2) == 0) {
+ mParameters = PersistableBundle.EMPTY;
+ }
+ EventInputParcel o = new EventInputParcel(
+ mRequestLogRecord,
+ mParameters);
+ return o;
+ }
+
+ private void checkNotUsed() {
+ if ((mBuilderFieldsSet & 0x4) != 0) {
+ throw new IllegalStateException(
+ "This Builder should not be reused. Use a new Builder instance instead");
+ }
+ }
+ }
+
+ @DataClass.Generated(
+ time = 1698875208124L,
+ codegenVersion = "1.0.23",
+ sourceFile = "packages/modules/OnDevicePersonalization/framework/java/android/adservices/ondevicepersonalization/EventInputParcel.java",
+ inputSignatures = "private @android.annotation.Nullable android.adservices.ondevicepersonalization.RequestLogRecord mRequestLogRecord\nprivate @android.annotation.NonNull android.os.PersistableBundle mParameters\nclass EventInputParcel extends java.lang.Object implements [android.os.Parcelable]\[email protected](genAidl=false, genHiddenBuilder=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/android-35/android/adservices/ondevicepersonalization/EventLogRecord.java b/android-35/android/adservices/ondevicepersonalization/EventLogRecord.java
new file mode 100644
index 0000000..54ab70c
--- /dev/null
+++ b/android-35/android/adservices/ondevicepersonalization/EventLogRecord.java
@@ -0,0 +1,432 @@
+/*
+ * 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;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ContentValues;
+import android.os.Parcelable;
+
+import com.android.adservices.ondevicepersonalization.flags.Flags;
+import com.android.ondevicepersonalization.internal.util.AnnotationValidations;
+import com.android.ondevicepersonalization.internal.util.DataClass;
+
+import java.time.Instant;
+
+// TODO(b/289102463): Add a link to the public doc for the EVENTS table when available.
+/**
+ * Data to be logged in the EVENTS table.
+ *
+ * Each record in the EVENTS table is associated with one row from an existing
+ * {@link RequestLogRecord} in the requests table {@link RequestLogRecord#getRows()}.
+ * The purpose of the EVENTS table is to add supplemental information to logged data
+ * from a prior request, e.g., logging an event when a link in a rendered WebView is
+ * clicked {@code IsolatedWorker#onEvent(EventInput, android.os.OutcomeReceiver)}.
+ * The contents of the EVENTS table can be
+ * consumed by Federated Learning facilitated model training, or Federated Analytics facilitated
+ * cross-device statistical analysis.
+ */
+@FlaggedApi(Flags.FLAG_ON_DEVICE_PERSONALIZATION_APIS_ENABLED)
+@DataClass(genBuilder = true, genEqualsHashCode = true)
+public final class EventLogRecord implements Parcelable {
+ /**
+ * The index of the row in an existing {@link RequestLogRecord} that this payload should be
+ * associated with.
+ **/
+ private @IntRange(from = 0) int mRowIndex = 0;
+
+ /**
+ * The service-assigned identifier that identifies this payload. Each row in
+ * {@link RequestLogRecord} can be associated with up to one event of a specified type.
+ * The platform drops events if another event with the same type already exists for a row
+ * in {@link RequestLogRecord}. Must be >0 and <128. This allows up to 127 events to be
+ * written for each row in {@link RequestLogRecord}. If unspecified, the default is 1.
+ */
+ private @IntRange(from = 1, to = 127) int mType = 1;
+
+ /**
+ * Time of the event in milliseconds.
+ * @hide
+ */
+ private long mTimeMillis = 0;
+
+ /**
+ * Additional data to be logged. Can be null if no additional data needs to be written as part
+ * of the event, and only the occurrence of the event needs to be logged.
+ */
+ @DataClass.MaySetToNull
+ @Nullable ContentValues mData = null;
+
+ /**
+ * The existing {@link RequestLogRecord} that this payload should be associated with. In an
+ * implementation of
+ * {@link IsolatedWorker#onExecute(ExecuteInput, android.os.OutcomeReceiver)}, this should be
+ * set to a value returned by {@link LogReader#getRequests(Instant, Instant)}. In an
+ * implementation of {@link IsolatedWorker#onEvent(EventInput, android.os.OutcomeReceiver)},
+ * this should be set to {@code null} because the payload will be automatically associated with
+ * the current {@link RequestLogRecord}.
+ *
+ */
+ @DataClass.MaySetToNull
+ @Nullable RequestLogRecord mRequestLogRecord = null;
+
+ /**
+ * Returns the timestamp of this record.
+ */
+ @NonNull public Instant getTime() {
+ return Instant.ofEpochMilli(getTimeMillis());
+ }
+
+ abstract static class BaseBuilder {
+ /**
+ * @hide
+ */
+ public abstract Builder setTimeMillis(long value);
+ }
+
+
+
+ // 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/EventLogRecord.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 */ EventLogRecord(
+ @IntRange(from = 0) int rowIndex,
+ @IntRange(from = 1, to = 127) int type,
+ long timeMillis,
+ @Nullable ContentValues data,
+ @Nullable RequestLogRecord requestLogRecord) {
+ this.mRowIndex = rowIndex;
+ AnnotationValidations.validate(
+ IntRange.class, null, mRowIndex,
+ "from", 0);
+ this.mType = type;
+ AnnotationValidations.validate(
+ IntRange.class, null, mType,
+ "from", 1,
+ "to", 127);
+ this.mTimeMillis = timeMillis;
+ this.mData = data;
+ this.mRequestLogRecord = requestLogRecord;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * The index of the row in an existing {@link RequestLogRecord} that this payload should be
+ * associated with.
+ */
+ @DataClass.Generated.Member
+ public @IntRange(from = 0) int getRowIndex() {
+ return mRowIndex;
+ }
+
+ /**
+ * The service-assigned identifier that identifies this payload. Each row in
+ * {@link RequestLogRecord} can be associated with up to one event of a specified type.
+ * The platform drops events if another event with the same type already exists for a row
+ * in {@link RequestLogRecord}. Must be >0 and <128. This allows up to 127 events to be
+ * written for each row in {@link RequestLogRecord}. If unspecified, the default is 1.
+ */
+ @DataClass.Generated.Member
+ public @IntRange(from = 1, to = 127) int getType() {
+ return mType;
+ }
+
+ /**
+ * Time of the event in milliseconds.
+ *
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public long getTimeMillis() {
+ return mTimeMillis;
+ }
+
+ /**
+ * Additional data to be logged. Can be null if no additional data needs to be written as part
+ * of the event, and only the occurrence of the event needs to be logged.
+ */
+ @DataClass.Generated.Member
+ public @Nullable ContentValues getData() {
+ return mData;
+ }
+
+ /**
+ * The existing {@link RequestLogRecord} that this payload should be associated with. In an
+ * implementation of
+ * {@link IsolatedWorker#onExecute(ExecuteInput, android.os.OutcomeReceiver)}, this should be
+ * set to a value returned by {@link LogReader#getRequests(Instant, Instant)}. In an
+ * implementation of {@link IsolatedWorker#onEvent(EventInput, android.os.OutcomeReceiver)},
+ * this should be set to {@code null} because the payload will be automatically associated with
+ * the current {@link RequestLogRecord}.
+ */
+ @DataClass.Generated.Member
+ public @Nullable RequestLogRecord getRequestLogRecord() {
+ return mRequestLogRecord;
+ }
+
+ @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(EventLogRecord other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ EventLogRecord that = (EventLogRecord) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && mRowIndex == that.mRowIndex
+ && mType == that.mType
+ && mTimeMillis == that.mTimeMillis
+ && java.util.Objects.equals(mData, that.mData)
+ && java.util.Objects.equals(mRequestLogRecord, that.mRequestLogRecord);
+ }
+
+ @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 + mRowIndex;
+ _hash = 31 * _hash + mType;
+ _hash = 31 * _hash + Long.hashCode(mTimeMillis);
+ _hash = 31 * _hash + java.util.Objects.hashCode(mData);
+ _hash = 31 * _hash + java.util.Objects.hashCode(mRequestLogRecord);
+ return _hash;
+ }
+
+ @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) { ... }
+
+ byte flg = 0;
+ if (mData != null) flg |= 0x8;
+ if (mRequestLogRecord != null) flg |= 0x10;
+ dest.writeByte(flg);
+ dest.writeInt(mRowIndex);
+ dest.writeInt(mType);
+ dest.writeLong(mTimeMillis);
+ if (mData != null) dest.writeTypedObject(mData, flags);
+ if (mRequestLogRecord != null) dest.writeTypedObject(mRequestLogRecord, flags);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ EventLogRecord(@NonNull android.os.Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ byte flg = in.readByte();
+ int rowIndex = in.readInt();
+ int type = in.readInt();
+ long timeMillis = in.readLong();
+ ContentValues data = (flg & 0x8) == 0 ? null : (ContentValues) in.readTypedObject(ContentValues.CREATOR);
+ RequestLogRecord requestLogRecord = (flg & 0x10) == 0 ? null : (RequestLogRecord) in.readTypedObject(RequestLogRecord.CREATOR);
+
+ this.mRowIndex = rowIndex;
+ AnnotationValidations.validate(
+ IntRange.class, null, mRowIndex,
+ "from", 0);
+ this.mType = type;
+ AnnotationValidations.validate(
+ IntRange.class, null, mType,
+ "from", 1,
+ "to", 127);
+ this.mTimeMillis = timeMillis;
+ this.mData = data;
+ this.mRequestLogRecord = requestLogRecord;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<EventLogRecord> CREATOR
+ = new Parcelable.Creator<EventLogRecord>() {
+ @Override
+ public EventLogRecord[] newArray(int size) {
+ return new EventLogRecord[size];
+ }
+
+ @Override
+ public EventLogRecord createFromParcel(@NonNull android.os.Parcel in) {
+ return new EventLogRecord(in);
+ }
+ };
+
+ /**
+ * A builder for {@link EventLogRecord}
+ */
+ @SuppressWarnings("WeakerAccess")
+ @DataClass.Generated.Member
+ public static final class Builder extends BaseBuilder {
+
+ private @IntRange(from = 0) int mRowIndex;
+ private @IntRange(from = 1, to = 127) int mType;
+ private long mTimeMillis;
+ private @Nullable ContentValues mData;
+ private @Nullable RequestLogRecord mRequestLogRecord;
+
+ private long mBuilderFieldsSet = 0L;
+
+ public Builder() {
+ }
+
+ /**
+ * The index of the row in an existing {@link RequestLogRecord} that this payload should be
+ * associated with.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setRowIndex(@IntRange(from = 0) int value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x1;
+ mRowIndex = value;
+ return this;
+ }
+
+ /**
+ * The service-assigned identifier that identifies this payload. Each row in
+ * {@link RequestLogRecord} can be associated with up to one event of a specified type.
+ * The platform drops events if another event with the same type already exists for a row
+ * in {@link RequestLogRecord}. Must be >0 and <128. This allows up to 127 events to be
+ * written for each row in {@link RequestLogRecord}. If unspecified, the default is 1.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setType(@IntRange(from = 1, to = 127) int value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x2;
+ mType = value;
+ return this;
+ }
+
+ /**
+ * Time of the event in milliseconds.
+ *
+ * @hide
+ */
+ @DataClass.Generated.Member
+ @Override
+ public @NonNull Builder setTimeMillis(long value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x4;
+ mTimeMillis = value;
+ return this;
+ }
+
+ /**
+ * Additional data to be logged. Can be null if no additional data needs to be written as part
+ * of the event, and only the occurrence of the event needs to be logged.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setData(@Nullable ContentValues value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x8;
+ mData = value;
+ return this;
+ }
+
+ /**
+ * The existing {@link RequestLogRecord} that this payload should be associated with. In an
+ * implementation of
+ * {@link IsolatedWorker#onExecute(ExecuteInput, android.os.OutcomeReceiver)}, this should be
+ * set to a value returned by {@link LogReader#getRequests(Instant, Instant)}. In an
+ * implementation of {@link IsolatedWorker#onEvent(EventInput, android.os.OutcomeReceiver)},
+ * this should be set to {@code null} because the payload will be automatically associated with
+ * the current {@link RequestLogRecord}.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setRequestLogRecord(@Nullable RequestLogRecord value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x10;
+ mRequestLogRecord = value;
+ return this;
+ }
+
+ /** Builds the instance. This builder should not be touched after calling this! */
+ public @NonNull EventLogRecord build() {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x20; // Mark builder used
+
+ if ((mBuilderFieldsSet & 0x1) == 0) {
+ mRowIndex = 0;
+ }
+ if ((mBuilderFieldsSet & 0x2) == 0) {
+ mType = 1;
+ }
+ if ((mBuilderFieldsSet & 0x4) == 0) {
+ mTimeMillis = 0;
+ }
+ if ((mBuilderFieldsSet & 0x8) == 0) {
+ mData = null;
+ }
+ if ((mBuilderFieldsSet & 0x10) == 0) {
+ mRequestLogRecord = null;
+ }
+ EventLogRecord o = new EventLogRecord(
+ mRowIndex,
+ mType,
+ mTimeMillis,
+ mData,
+ mRequestLogRecord);
+ return o;
+ }
+
+ private void checkNotUsed() {
+ if ((mBuilderFieldsSet & 0x20) != 0) {
+ throw new IllegalStateException(
+ "This Builder should not be reused. Use a new Builder instance instead");
+ }
+ }
+ }
+
+ @DataClass.Generated(
+ time = 1707253467187L,
+ codegenVersion = "1.0.23",
+ sourceFile = "packages/modules/OnDevicePersonalization/framework/java/android/adservices/ondevicepersonalization/EventLogRecord.java",
+ inputSignatures = "private @android.annotation.IntRange int mRowIndex\nprivate @android.annotation.IntRange int mType\nprivate long mTimeMillis\n @com.android.ondevicepersonalization.internal.util.DataClass.MaySetToNull @android.annotation.Nullable android.content.ContentValues mData\n @com.android.ondevicepersonalization.internal.util.DataClass.MaySetToNull @android.annotation.Nullable android.adservices.ondevicepersonalization.RequestLogRecord mRequestLogRecord\npublic @android.annotation.NonNull java.time.Instant getTime()\nclass EventLogRecord extends java.lang.Object implements [android.os.Parcelable]\npublic abstract android.adservices.ondevicepersonalization.EventLogRecord.Builder setTimeMillis(long)\nclass BaseBuilder extends java.lang.Object implements []\[email protected](genBuilder=true, genEqualsHashCode=true)\npublic abstract android.adservices.ondevicepersonalization.EventLogRecord.Builder setTimeMillis(long)\nclass BaseBuilder extends java.lang.Object implements []")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/android-35/android/adservices/ondevicepersonalization/EventOutput.java b/android-35/android/adservices/ondevicepersonalization/EventOutput.java
new file mode 100644
index 0000000..d44031e
--- /dev/null
+++ b/android-35/android/adservices/ondevicepersonalization/EventOutput.java
@@ -0,0 +1,159 @@
+/*
+ * 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;
+
+import android.annotation.FlaggedApi;
+import android.annotation.Nullable;
+
+import com.android.adservices.ondevicepersonalization.flags.Flags;
+import com.android.ondevicepersonalization.internal.util.DataClass;
+
+/**
+ * The result returned by {@link IsolatedWorker#onEvent(EventInput, android.os.OutcomeReceiver)}.
+ */
+@FlaggedApi(Flags.FLAG_ON_DEVICE_PERSONALIZATION_APIS_ENABLED)
+@DataClass(genBuilder = true, genEqualsHashCode = true)
+public final class EventOutput {
+ /**
+ * An {@link EventLogRecord} to be written to the EVENTS table, if not null. Each
+ * {@link EventLogRecord} is associated with a row in an existing {@link RequestLogRecord} that
+ * has been written to the REQUESTS table.
+ */
+ @DataClass.MaySetToNull
+ @Nullable EventLogRecord mEventLogRecord = null;
+
+
+
+ // 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/EventOutput.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 */ EventOutput(
+ @Nullable EventLogRecord eventLogRecord) {
+ this.mEventLogRecord = eventLogRecord;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * An {@link EventLogRecord} to be written to the EVENTS table, if not null. Each
+ * {@link EventLogRecord} is associated with a row in an existing {@link RequestLogRecord} that
+ * has been written to the REQUESTS table.
+ */
+ @DataClass.Generated.Member
+ public @Nullable EventLogRecord getEventLogRecord() {
+ return mEventLogRecord;
+ }
+
+ @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(EventOutput other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ EventOutput that = (EventOutput) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && java.util.Objects.equals(mEventLogRecord, that.mEventLogRecord);
+ }
+
+ @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(mEventLogRecord);
+ return _hash;
+ }
+
+ /**
+ * A builder for {@link EventOutput}
+ */
+ @SuppressWarnings("WeakerAccess")
+ @DataClass.Generated.Member
+ public static final class Builder {
+
+ private @Nullable EventLogRecord mEventLogRecord;
+
+ private long mBuilderFieldsSet = 0L;
+
+ public Builder() {
+ }
+
+ /**
+ * An {@link EventLogRecord} to be written to the EVENTS table, if not null. Each
+ * {@link EventLogRecord} is associated with a row in an existing {@link RequestLogRecord} that
+ * has been written to the REQUESTS table.
+ */
+ @DataClass.Generated.Member
+ public @android.annotation.NonNull Builder setEventLogRecord(@Nullable EventLogRecord value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x1;
+ mEventLogRecord = value;
+ return this;
+ }
+
+ /** Builds the instance. This builder should not be touched after calling this! */
+ public @android.annotation.NonNull EventOutput build() {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x2; // Mark builder used
+
+ if ((mBuilderFieldsSet & 0x1) == 0) {
+ mEventLogRecord = null;
+ }
+ EventOutput o = new EventOutput(
+ mEventLogRecord);
+ 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 = 1707253681044L,
+ codegenVersion = "1.0.23",
+ sourceFile = "packages/modules/OnDevicePersonalization/framework/java/android/adservices/ondevicepersonalization/EventOutput.java",
+ inputSignatures = " @com.android.ondevicepersonalization.internal.util.DataClass.MaySetToNull @android.annotation.Nullable android.adservices.ondevicepersonalization.EventLogRecord mEventLogRecord\nclass EventOutput extends java.lang.Object implements []\[email protected](genBuilder=true, genEqualsHashCode=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/android-35/android/adservices/ondevicepersonalization/EventOutputParcel.java b/android-35/android/adservices/ondevicepersonalization/EventOutputParcel.java
new file mode 100644
index 0000000..5432a6c
--- /dev/null
+++ b/android-35/android/adservices/ondevicepersonalization/EventOutputParcel.java
@@ -0,0 +1,141 @@
+/*
+ * 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;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcelable;
+
+import com.android.ondevicepersonalization.internal.util.DataClass;
+
+/**
+ * Parcelable version of {@link EventOutput}.
+ * @hide
+ */
+@DataClass(genAidl = false, genBuilder = false)
+public final class EventOutputParcel implements Parcelable {
+ /**
+ * An {@link EventLogRecord} to be written to the EVENTS table, if not null. Each
+ * {@link EventLogRecord} is associated with a row in an existing {@link RequestLogRecord} that
+ * has been written to the REQUESTS table.
+ */
+ @Nullable EventLogRecord mEventLogRecord = null;
+
+ /** @hide */
+ public EventOutputParcel(@NonNull EventOutput value) {
+ this(value.getEventLogRecord());
+ }
+
+
+
+ // 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/EventOutputParcel.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /**
+ * Creates a new EventOutputParcel.
+ *
+ * @param eventLogRecord
+ * An {@link EventLogRecord} to be written to the EVENTS table, if not null. Each
+ * {@link EventLogRecord} is associated with a row in an existing {@link RequestLogRecord} that
+ * has been written to the REQUESTS table.
+ */
+ @DataClass.Generated.Member
+ public EventOutputParcel(
+ @Nullable EventLogRecord eventLogRecord) {
+ this.mEventLogRecord = eventLogRecord;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * An {@link EventLogRecord} to be written to the EVENTS table, if not null. Each
+ * {@link EventLogRecord} is associated with a row in an existing {@link RequestLogRecord} that
+ * has been written to the REQUESTS table.
+ */
+ @DataClass.Generated.Member
+ public @Nullable EventLogRecord getEventLogRecord() {
+ return mEventLogRecord;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@android.annotation.NonNull android.os.Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ byte flg = 0;
+ if (mEventLogRecord != null) flg |= 0x1;
+ dest.writeByte(flg);
+ if (mEventLogRecord != null) dest.writeTypedObject(mEventLogRecord, flags);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ EventOutputParcel(@android.annotation.NonNull android.os.Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ byte flg = in.readByte();
+ EventLogRecord eventLogRecord = (flg & 0x1) == 0 ? null : (EventLogRecord) in.readTypedObject(EventLogRecord.CREATOR);
+
+ this.mEventLogRecord = eventLogRecord;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @android.annotation.NonNull Parcelable.Creator<EventOutputParcel> CREATOR
+ = new Parcelable.Creator<EventOutputParcel>() {
+ @Override
+ public EventOutputParcel[] newArray(int size) {
+ return new EventOutputParcel[size];
+ }
+
+ @Override
+ public EventOutputParcel createFromParcel(@android.annotation.NonNull android.os.Parcel in) {
+ return new EventOutputParcel(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1698864082503L,
+ codegenVersion = "1.0.23",
+ sourceFile = "packages/modules/OnDevicePersonalization/framework/java/android/adservices/ondevicepersonalization/EventOutputParcel.java",
+ inputSignatures = " @android.annotation.Nullable android.adservices.ondevicepersonalization.EventLogRecord mEventLogRecord\nclass EventOutputParcel 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/android-35/android/adservices/ondevicepersonalization/EventUrlProvider.java b/android-35/android/adservices/ondevicepersonalization/EventUrlProvider.java
new file mode 100644
index 0000000..5abd17b
--- /dev/null
+++ b/android-35/android/adservices/ondevicepersonalization/EventUrlProvider.java
@@ -0,0 +1,156 @@
+/*
+ * 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.adservices.ondevicepersonalization.aidl.IDataAccessService;
+import android.adservices.ondevicepersonalization.aidl.IDataAccessServiceCallback;
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.WorkerThread;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.PersistableBundle;
+import android.os.RemoteException;
+
+import com.android.adservices.ondevicepersonalization.flags.Flags;
+import com.android.ondevicepersonalization.internal.util.LoggerFactory;
+
+import java.util.Objects;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+
+/**
+ * Generates event tracking URLs for a request. The service can embed these URLs within the
+ * HTML output as needed. When the HTML is rendered within an ODP WebView, ODP will intercept
+ * requests to these URLs, call
+ * {@code IsolatedWorker#onEvent(EventInput, android.os.OutcomeReceiver)}, and log the returned
+ * output in the EVENTS table.
+ *
+ */
+@FlaggedApi(Flags.FLAG_ON_DEVICE_PERSONALIZATION_APIS_ENABLED)
+public class EventUrlProvider {
+ private static final LoggerFactory.Logger sLogger = LoggerFactory.getLogger();
+ private static final String TAG = EventUrlProvider.class.getSimpleName();
+ private static final long ASYNC_TIMEOUT_MS = 1000;
+
+ @NonNull private final IDataAccessService mDataAccessService;
+
+ /** @hide */
+ public EventUrlProvider(@NonNull IDataAccessService binder) {
+ mDataAccessService = Objects.requireNonNull(binder);
+ }
+
+ /**
+ * Creates an event tracking URL that returns the provided response. Returns HTTP Status
+ * 200 (OK) if the response data is not empty. Returns HTTP Status 204 (No Content) if the
+ * response data is empty.
+ *
+ * @param eventParams The data to be passed to
+ * {@code IsolatedWorker#onEvent(EventInput, android.os.OutcomeReceiver)}
+ * when the event occurs.
+ * @param responseData The content to be returned to the WebView when the URL is fetched.
+ * @param mimeType The Mime Type of the URL response.
+ * @return An ODP event URL that can be inserted into a WebView.
+ */
+ @WorkerThread
+ @NonNull public Uri createEventTrackingUrlWithResponse(
+ @NonNull PersistableBundle eventParams,
+ @Nullable byte[] responseData,
+ @Nullable String mimeType) {
+ final long startTimeMillis = System.currentTimeMillis();
+ Bundle params = new Bundle();
+ params.putParcelable(Constants.EXTRA_EVENT_PARAMS, eventParams);
+ params.putByteArray(Constants.EXTRA_RESPONSE_DATA, responseData);
+ params.putString(Constants.EXTRA_MIME_TYPE, mimeType);
+ return getUrl(params, Constants.API_NAME_EVENT_URL_CREATE_WITH_RESPONSE, startTimeMillis);
+ }
+
+ /**
+ * Creates an event tracking URL that redirects to the provided destination URL when it is
+ * clicked in an ODP webview.
+ *
+ * @param eventParams The data to be passed to
+ * {@code IsolatedWorker#onEvent(EventInput, android.os.OutcomeReceiver)}
+ * when the event occurs
+ * @param destinationUrl The URL to redirect to.
+ * @return An ODP event URL that can be inserted into a WebView.
+ */
+ @WorkerThread
+ @NonNull public Uri createEventTrackingUrlWithRedirect(
+ @NonNull PersistableBundle eventParams,
+ @Nullable Uri destinationUrl) {
+ final long startTimeMillis = System.currentTimeMillis();
+ Bundle params = new Bundle();
+ params.putParcelable(Constants.EXTRA_EVENT_PARAMS, eventParams);
+ params.putString(Constants.EXTRA_DESTINATION_URL, destinationUrl.toString());
+ return getUrl(params, Constants.API_NAME_EVENT_URL_CREATE_WITH_REDIRECT, startTimeMillis);
+ }
+
+ @NonNull private Uri getUrl(
+ @NonNull Bundle params, int apiName, long startTimeMillis) {
+ int responseCode = Constants.STATUS_SUCCESS;
+ try {
+ BlockingQueue<CallbackResult> asyncResult = new ArrayBlockingQueue<>(1);
+
+ mDataAccessService.onRequest(
+ Constants.DATA_ACCESS_OP_GET_EVENT_URL,
+ params,
+ new IDataAccessServiceCallback.Stub() {
+ @Override
+ public void onSuccess(@NonNull Bundle result) {
+ asyncResult.add(new CallbackResult(result, 0));
+ }
+ @Override
+ public void onError(int errorCode) {
+ asyncResult.add(new CallbackResult(null, errorCode));
+ }
+ });
+ CallbackResult callbackResult = asyncResult.take();
+ Objects.requireNonNull(callbackResult);
+ if (callbackResult.mErrorCode != 0) {
+ throw new IllegalStateException("Error: " + callbackResult.mErrorCode);
+ }
+ Bundle result = Objects.requireNonNull(callbackResult.mResult);
+ Uri url = Objects.requireNonNull(
+ result.getParcelable(Constants.EXTRA_RESULT, Uri.class));
+ return url;
+ } catch (InterruptedException | RemoteException e) {
+ responseCode = Constants.STATUS_INTERNAL_ERROR;
+ throw new RuntimeException(e);
+ } finally {
+ try {
+ mDataAccessService.logApiCallStats(
+ apiName,
+ System.currentTimeMillis() - startTimeMillis,
+ responseCode);
+ } catch (Exception e) {
+ sLogger.d(e, TAG + ": failed to log metrics");
+ }
+ }
+ }
+
+ private static class CallbackResult {
+ final Bundle mResult;
+ final int mErrorCode;
+
+ CallbackResult(Bundle result, int errorCode) {
+ mResult = result;
+ mErrorCode = errorCode;
+ }
+ }
+}
diff --git a/android-35/android/adservices/ondevicepersonalization/ExecuteInput.java b/android-35/android/adservices/ondevicepersonalization/ExecuteInput.java
new file mode 100644
index 0000000..cda9262
--- /dev/null
+++ b/android-35/android/adservices/ondevicepersonalization/ExecuteInput.java
@@ -0,0 +1,77 @@
+/*
+ * 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 android.os.PersistableBundle;
+
+import com.android.adservices.ondevicepersonalization.flags.Flags;
+import com.android.ondevicepersonalization.internal.util.ByteArrayParceledSlice;
+import com.android.ondevicepersonalization.internal.util.PersistableBundleUtils;
+
+import java.util.Objects;
+
+/**
+ * The input data for {@link IsolatedWorker#onExecute(ExecuteInput, android.os.OutcomeReceiver)}.
+ *
+ */
+@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();
+ @NonNull private volatile PersistableBundle mAppParams = null;
+
+ /** @hide */
+ public ExecuteInput(@NonNull ExecuteInputParcel parcel) {
+ mAppPackageName = Objects.requireNonNull(parcel.getAppPackageName());
+ mSerializedAppParams = parcel.getSerializedAppParams();
+ }
+
+ /**
+ * The package name of the calling app.
+ */
+ @NonNull public String getAppPackageName() {
+ return mAppPackageName;
+ }
+
+ /**
+ * The parameters provided by the app to the {@link IsolatedService}. The service
+ * defines the expected keys in this {@link PersistableBundle}.
+ */
+ @NonNull public PersistableBundle getAppParams() {
+ if (mAppParams != null) {
+ return mAppParams;
+ }
+ synchronized (mAppParamsLock) {
+ if (mAppParams != null) {
+ return mAppParams;
+ }
+ try {
+ mAppParams = (mSerializedAppParams != null)
+ ? PersistableBundleUtils.fromByteArray(
+ mSerializedAppParams.getByteArray())
+ : PersistableBundle.EMPTY;
+ return mAppParams;
+ } catch (Exception e) {
+ throw new IllegalStateException(e);
+ }
+ }
+ }
+}
diff --git a/android-35/android/adservices/ondevicepersonalization/ExecuteInputParcel.java b/android-35/android/adservices/ondevicepersonalization/ExecuteInputParcel.java
new file mode 100644
index 0000000..2058c13
--- /dev/null
+++ b/android-35/android/adservices/ondevicepersonalization/ExecuteInputParcel.java
@@ -0,0 +1,216 @@
+/*
+ * 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.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcelable;
+
+import com.android.ondevicepersonalization.internal.util.AnnotationValidations;
+import com.android.ondevicepersonalization.internal.util.ByteArrayParceledSlice;
+import com.android.ondevicepersonalization.internal.util.DataClass;
+
+/**
+ * Parcelable version of {@link ExecuteInput}.
+ * @hide
+ */
+@DataClass(genAidl = false, genHiddenBuilder = true)
+public final class ExecuteInputParcel implements Parcelable {
+ /**
+ * The package name of the calling app.
+ */
+ @NonNull String mAppPackageName = "";
+
+ /**
+ * Serialized app params.
+ * @hide
+ */
+ @Nullable ByteArrayParceledSlice mSerializedAppParams = null;
+
+
+
+ // 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/ExecuteInputParcel.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 */ ExecuteInputParcel(
+ @NonNull String appPackageName,
+ @Nullable ByteArrayParceledSlice serializedAppParams) {
+ this.mAppPackageName = appPackageName;
+ AnnotationValidations.validate(
+ NonNull.class, null, mAppPackageName);
+ this.mSerializedAppParams = serializedAppParams;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * The package name of the calling app.
+ */
+ @DataClass.Generated.Member
+ public @NonNull String getAppPackageName() {
+ return mAppPackageName;
+ }
+
+ /**
+ * Serialized app params.
+ *
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public @Nullable ByteArrayParceledSlice getSerializedAppParams() {
+ return mSerializedAppParams;
+ }
+
+ @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) { ... }
+
+ byte flg = 0;
+ if (mSerializedAppParams != null) flg |= 0x2;
+ dest.writeByte(flg);
+ dest.writeString(mAppPackageName);
+ if (mSerializedAppParams != null) dest.writeTypedObject(mSerializedAppParams, flags);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ ExecuteInputParcel(@NonNull android.os.Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ byte flg = in.readByte();
+ String appPackageName = in.readString();
+ ByteArrayParceledSlice serializedAppParams = (flg & 0x2) == 0 ? null : (ByteArrayParceledSlice) in.readTypedObject(ByteArrayParceledSlice.CREATOR);
+
+ this.mAppPackageName = appPackageName;
+ AnnotationValidations.validate(
+ NonNull.class, null, mAppPackageName);
+ this.mSerializedAppParams = serializedAppParams;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<ExecuteInputParcel> CREATOR
+ = new Parcelable.Creator<ExecuteInputParcel>() {
+ @Override
+ public ExecuteInputParcel[] newArray(int size) {
+ return new ExecuteInputParcel[size];
+ }
+
+ @Override
+ public ExecuteInputParcel createFromParcel(@NonNull android.os.Parcel in) {
+ return new ExecuteInputParcel(in);
+ }
+ };
+
+ /**
+ * A builder for {@link ExecuteInputParcel}
+ * @hide
+ */
+ @SuppressWarnings("WeakerAccess")
+ @DataClass.Generated.Member
+ public static final class Builder {
+
+ private @NonNull String mAppPackageName;
+ private @Nullable ByteArrayParceledSlice mSerializedAppParams;
+
+ private long mBuilderFieldsSet = 0L;
+
+ public Builder() {
+ }
+
+ /**
+ * The package name of the calling app.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setAppPackageName(@NonNull String value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x1;
+ mAppPackageName = value;
+ return this;
+ }
+
+ /**
+ * Serialized app params.
+ *
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setSerializedAppParams(@NonNull ByteArrayParceledSlice value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x2;
+ mSerializedAppParams = value;
+ return this;
+ }
+
+ /** Builds the instance. This builder should not be touched after calling this! */
+ public @NonNull ExecuteInputParcel build() {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x4; // Mark builder used
+
+ if ((mBuilderFieldsSet & 0x1) == 0) {
+ mAppPackageName = "";
+ }
+ if ((mBuilderFieldsSet & 0x2) == 0) {
+ mSerializedAppParams = null;
+ }
+ ExecuteInputParcel o = new ExecuteInputParcel(
+ mAppPackageName,
+ mSerializedAppParams);
+ return o;
+ }
+
+ private void checkNotUsed() {
+ if ((mBuilderFieldsSet & 0x4) != 0) {
+ throw new IllegalStateException(
+ "This Builder should not be reused. Use a new Builder instance instead");
+ }
+ }
+ }
+
+ @DataClass.Generated(
+ time = 1708120245903L,
+ codegenVersion = "1.0.23",
+ sourceFile = "packages/modules/OnDevicePersonalization/framework/java/android/adservices/ondevicepersonalization/ExecuteInputParcel.java",
+ inputSignatures = " @android.annotation.NonNull java.lang.String mAppPackageName\n @android.annotation.Nullable com.android.ondevicepersonalization.internal.util.ByteArrayParceledSlice mSerializedAppParams\nclass ExecuteInputParcel extends java.lang.Object implements [android.os.Parcelable]\[email protected](genAidl=false, genHiddenBuilder=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/android-35/android/adservices/ondevicepersonalization/ExecuteOutput.java b/android-35/android/adservices/ondevicepersonalization/ExecuteOutput.java
new file mode 100644
index 0000000..808de96
--- /dev/null
+++ b/android-35/android/adservices/ondevicepersonalization/ExecuteOutput.java
@@ -0,0 +1,316 @@
+/*
+ * 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;
+
+import java.util.Collections;
+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.
+ */
+@FlaggedApi(Flags.FLAG_ON_DEVICE_PERSONALIZATION_APIS_ENABLED)
+@DataClass(genBuilder = true, genEqualsHashCode = true)
+public final class ExecuteOutput {
+ /**
+ * 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.
+ */
+ @DataClass.MaySetToNull
+ @Nullable private RequestLogRecord mRequestLogRecord = null;
+
+ /**
+ * 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.MaySetToNull
+ @Nullable private RenderingConfig mRenderingConfig = null;
+
+ /**
+ * A list of {@link EventLogRecord} objects to be written to the EVENTS table. Each
+ * {@link EventLogRecord} must be associated with an existing {@link RequestLogRecord} in
+ * the REQUESTS table, specified using
+ * {@link EventLogRecord.Builder#setRequestLogRecord(RequestLogRecord)}.
+ * If the {@link RequestLogRecord} is not specified, the {@link EventLogRecord} will not be
+ * written.
+ */
+ @DataClass.PluralOf("eventLogRecord")
+ @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.
+ */
+ @DataClass.MaySetToNull
+ @Nullable private byte[] mOutputData = null;
+
+
+
+ // 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/ExecuteOutput.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 */ ExecuteOutput(
+ @Nullable RequestLogRecord requestLogRecord,
+ @Nullable RenderingConfig renderingConfig,
+ @NonNull List<EventLogRecord> eventLogRecords,
+ @Nullable byte[] outputData) {
+ this.mRequestLogRecord = requestLogRecord;
+ this.mRenderingConfig = renderingConfig;
+ this.mEventLogRecords = eventLogRecords;
+ AnnotationValidations.validate(
+ NonNull.class, null, mEventLogRecords);
+ this.mOutputData = outputData;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * 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.
+ */
+ @DataClass.Generated.Member
+ public @Nullable RequestLogRecord getRequestLogRecord() {
+ return mRequestLogRecord;
+ }
+
+ /**
+ * 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 @Nullable RenderingConfig getRenderingConfig() {
+ return mRenderingConfig;
+ }
+
+ /**
+ * A list of {@link EventLogRecord} objects to be written to the EVENTS table. Each
+ * {@link EventLogRecord} must be associated with an existing {@link RequestLogRecord} in
+ * the REQUESTS table, specified using
+ * {@link EventLogRecord.Builder#setRequestLogRecord(RequestLogRecord)}.
+ * If the {@link RequestLogRecord} is not specified, the {@link EventLogRecord} will not be
+ * written.
+ */
+ @DataClass.Generated.Member
+ public @NonNull List<EventLogRecord> getEventLogRecords() {
+ return mEventLogRecords;
+ }
+
+ /**
+ * 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.
+ */
+ @DataClass.Generated.Member
+ public @Nullable byte[] getOutputData() {
+ return mOutputData;
+ }
+
+ @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(ExecuteOutput other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ ExecuteOutput that = (ExecuteOutput) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && 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);
+ }
+
+ @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(mRenderingConfig);
+ _hash = 31 * _hash + java.util.Objects.hashCode(mEventLogRecords);
+ _hash = 31 * _hash + java.util.Arrays.hashCode(mOutputData);
+ return _hash;
+ }
+
+ /**
+ * A builder for {@link ExecuteOutput}
+ */
+ @SuppressWarnings("WeakerAccess")
+ @DataClass.Generated.Member
+ public static final class Builder {
+
+ private @Nullable RequestLogRecord mRequestLogRecord;
+ private @Nullable RenderingConfig mRenderingConfig;
+ private @NonNull List<EventLogRecord> mEventLogRecords;
+ private @Nullable byte[] mOutputData;
+
+ private long mBuilderFieldsSet = 0L;
+
+ public Builder() {
+ }
+
+ /**
+ * 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.
+ */
+ @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.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setRenderingConfig(@Nullable RenderingConfig value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x2;
+ mRenderingConfig = value;
+ return this;
+ }
+
+ /**
+ * A list of {@link EventLogRecord} objects to be written to the EVENTS table. Each
+ * {@link EventLogRecord} must be associated with an existing {@link RequestLogRecord} in
+ * the REQUESTS table, specified using
+ * {@link EventLogRecord.Builder#setRequestLogRecord(RequestLogRecord)}.
+ * If the {@link RequestLogRecord} is not specified, the {@link EventLogRecord} will not be
+ * written.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setEventLogRecords(@NonNull List<EventLogRecord> value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x4;
+ mEventLogRecords = value;
+ return this;
+ }
+
+ /** @see #setEventLogRecords */
+ @DataClass.Generated.Member
+ public @NonNull Builder addEventLogRecord(@NonNull EventLogRecord value) {
+ if (mEventLogRecords == null) setEventLogRecords(new java.util.ArrayList<>());
+ mEventLogRecords.add(value);
+ return this;
+ }
+
+ /**
+ * 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.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setOutputData(@Nullable byte... value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x8;
+ mOutputData = 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
+
+ if ((mBuilderFieldsSet & 0x1) == 0) {
+ mRequestLogRecord = null;
+ }
+ if ((mBuilderFieldsSet & 0x2) == 0) {
+ mRenderingConfig = null;
+ }
+ if ((mBuilderFieldsSet & 0x4) == 0) {
+ mEventLogRecords = Collections.emptyList();
+ }
+ if ((mBuilderFieldsSet & 0x8) == 0) {
+ mOutputData = null;
+ }
+ ExecuteOutput o = new ExecuteOutput(
+ mRequestLogRecord,
+ mRenderingConfig,
+ mEventLogRecords,
+ mOutputData);
+ 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,
+ 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)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/android-35/android/adservices/ondevicepersonalization/ExecuteOutputParcel.java b/android-35/android/adservices/ondevicepersonalization/ExecuteOutputParcel.java
new file mode 100644
index 0000000..cf797ba
--- /dev/null
+++ b/android-35/android/adservices/ondevicepersonalization/ExecuteOutputParcel.java
@@ -0,0 +1,245 @@
+/*
+ * 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.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcelable;
+
+import com.android.ondevicepersonalization.internal.util.AnnotationValidations;
+import com.android.ondevicepersonalization.internal.util.DataClass;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Parcelable version of {@link ExecuteOutput}.
+ * @hide
+ */
+@DataClass(genAidl = false, genBuilder = false)
+public final class ExecuteOutputParcel implements Parcelable {
+ /**
+ * 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.
+ */
+ @Nullable private RequestLogRecord mRequestLogRecord = null;
+
+ /**
+ * 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.
+ */
+ @Nullable private RenderingConfig mRenderingConfig = null;
+
+ /**
+ * 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.
+ *
+ */
+ @DataClass.PluralOf("eventLogRecord")
+ @NonNull private List<EventLogRecord> mEventLogRecords = Collections.emptyList();
+
+ /**
+ * 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.
+ *
+ * @hide
+ */
+ @Nullable private byte[] mOutputData = null;
+
+ /** @hide */
+ public ExecuteOutputParcel(@NonNull ExecuteOutput value) {
+ this(value.getRequestLogRecord(), value.getRenderingConfig(), value.getEventLogRecords(),
+ value.getOutputData());
+ }
+
+
+
+ // 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
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@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.
+ */
+ @DataClass.Generated.Member
+ public ExecuteOutputParcel(
+ @Nullable RequestLogRecord requestLogRecord,
+ @Nullable RenderingConfig renderingConfig,
+ @NonNull List<EventLogRecord> eventLogRecords,
+ @Nullable byte[] outputData) {
+ this.mRequestLogRecord = requestLogRecord;
+ this.mRenderingConfig = renderingConfig;
+ this.mEventLogRecords = eventLogRecords;
+ AnnotationValidations.validate(
+ NonNull.class, null, mEventLogRecords);
+ this.mOutputData = outputData;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * 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.
+ */
+ @DataClass.Generated.Member
+ public @Nullable RequestLogRecord getRequestLogRecord() {
+ return mRequestLogRecord;
+ }
+
+ /**
+ * 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 @Nullable RenderingConfig getRenderingConfig() {
+ return mRenderingConfig;
+ }
+
+ /**
+ * 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.
+ */
+ @DataClass.Generated.Member
+ public @NonNull List<EventLogRecord> getEventLogRecords() {
+ return mEventLogRecords;
+ }
+
+ /**
+ * 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.
+ *
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public @Nullable byte[] getOutputData() {
+ return mOutputData;
+ }
+
+ @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) { ... }
+
+ byte flg = 0;
+ if (mRequestLogRecord != null) flg |= 0x1;
+ if (mRenderingConfig != null) flg |= 0x2;
+ dest.writeByte(flg);
+ if (mRequestLogRecord != null) dest.writeTypedObject(mRequestLogRecord, flags);
+ if (mRenderingConfig != null) dest.writeTypedObject(mRenderingConfig, flags);
+ dest.writeParcelableList(mEventLogRecords, flags);
+ dest.writeByteArray(mOutputData);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ ExecuteOutputParcel(@NonNull android.os.Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ byte flg = in.readByte();
+ RequestLogRecord requestLogRecord = (flg & 0x1) == 0 ? null : (RequestLogRecord) in.readTypedObject(RequestLogRecord.CREATOR);
+ RenderingConfig renderingConfig = (flg & 0x2) == 0 ? null : (RenderingConfig) in.readTypedObject(RenderingConfig.CREATOR);
+ List<EventLogRecord> eventLogRecords = new java.util.ArrayList<>();
+ in.readParcelableList(eventLogRecords, EventLogRecord.class.getClassLoader());
+ byte[] outputData = in.createByteArray();
+
+ this.mRequestLogRecord = requestLogRecord;
+ this.mRenderingConfig = renderingConfig;
+ this.mEventLogRecords = eventLogRecords;
+ AnnotationValidations.validate(
+ NonNull.class, null, mEventLogRecords);
+ this.mOutputData = outputData;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<ExecuteOutputParcel> CREATOR
+ = new Parcelable.Creator<ExecuteOutputParcel>() {
+ @Override
+ public ExecuteOutputParcel[] newArray(int size) {
+ return new ExecuteOutputParcel[size];
+ }
+
+ @Override
+ public ExecuteOutputParcel createFromParcel(@NonNull android.os.Parcel in) {
+ return new ExecuteOutputParcel(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1706684633171L,
+ 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)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/android-35/android/adservices/ondevicepersonalization/FederatedComputeInput.java b/android-35/android/adservices/ondevicepersonalization/FederatedComputeInput.java
new file mode 100644
index 0000000..b2051e6
--- /dev/null
+++ b/android-35/android/adservices/ondevicepersonalization/FederatedComputeInput.java
@@ -0,0 +1,146 @@
+/*
+ * 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 input data for {@link FederatedComputeScheduler#schedule}. */
+@DataClass(genBuilder = true, genEqualsHashCode = true)
+@FlaggedApi(Flags.FLAG_ON_DEVICE_PERSONALIZATION_APIS_ENABLED)
+public final class FederatedComputeInput {
+ // TODO(b/300461799): add federated compute server document.
+ /**
+ * 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/FederatedComputeInput.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 */ FederatedComputeInput(@NonNull String populationName) {
+ this.mPopulationName = populationName;
+ AnnotationValidations.validate(NonNull.class, null, mPopulationName);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * 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(@android.annotation.Nullable Object o) {
+ // You can override field equality logic by defining either of the methods like:
+ // boolean fieldNameEquals(FederatedComputeInput other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ FederatedComputeInput that = (FederatedComputeInput) o;
+ //noinspection PointlessBooleanExpression
+ return true && 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(mPopulationName);
+ return _hash;
+ }
+
+ /** A builder for {@link FederatedComputeInput} */
+ @SuppressWarnings("WeakerAccess")
+ @DataClass.Generated.Member
+ public static final class Builder {
+
+ private @NonNull String mPopulationName;
+
+ private long mBuilderFieldsSet = 0L;
+
+ public Builder() {}
+
+ /** Setter for {@link #getPopulationName}. */
+ @DataClass.Generated.Member
+ public @NonNull Builder setPopulationName(@NonNull String value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x1;
+ mPopulationName = value;
+ return this;
+ }
+
+ /** Builds the instance. This builder should not be touched after calling this! */
+ public @NonNull FederatedComputeInput build() {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x2; // Mark builder used
+
+ if ((mBuilderFieldsSet & 0x1) == 0) {
+ mPopulationName = "";
+ }
+ FederatedComputeInput o = new FederatedComputeInput(mPopulationName);
+ 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 = 1697578140247L,
+ codegenVersion = "1.0.23",
+ sourceFile =
+ "packages/modules/OnDevicePersonalization/framework/java/android/adservices/ondevicepersonalization/FederatedComputeInput.java",
+ inputSignatures =
+ "private @android.annotation.NonNull java.lang.String mPopulationName\nclass FederatedComputeInput extends java.lang.Object implements []\[email protected](genBuilder=true, genEqualsHashCode=true)")
+ @Deprecated
+ private void __metadata() {}
+
+ // @formatter:on
+ // End of generated code
+
+}
diff --git a/android-35/android/adservices/ondevicepersonalization/FederatedComputeScheduler.java b/android-35/android/adservices/ondevicepersonalization/FederatedComputeScheduler.java
new file mode 100644
index 0000000..2cb5c28
--- /dev/null
+++ b/android-35/android/adservices/ondevicepersonalization/FederatedComputeScheduler.java
@@ -0,0 +1,211 @@
+/*
+ * 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.adservices.ondevicepersonalization.aidl.IDataAccessService;
+import android.adservices.ondevicepersonalization.aidl.IFederatedComputeCallback;
+import android.adservices.ondevicepersonalization.aidl.IFederatedComputeService;
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.WorkerThread;
+import android.federatedcompute.common.TrainingOptions;
+import android.os.RemoteException;
+
+import com.android.adservices.ondevicepersonalization.flags.Flags;
+import com.android.ondevicepersonalization.internal.util.LoggerFactory;
+
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * Handles scheduling federated compute jobs. See {@link
+ * IsolatedService#getFederatedComputeScheduler}.
+ */
+@FlaggedApi(Flags.FLAG_ON_DEVICE_PERSONALIZATION_APIS_ENABLED)
+public class FederatedComputeScheduler {
+ private static final String TAG = FederatedComputeScheduler.class.getSimpleName();
+ private static final LoggerFactory.Logger sLogger = LoggerFactory.getLogger();
+
+ private final IFederatedComputeService mFcService;
+ private final IDataAccessService mDataAccessService;
+
+ /** @hide */
+ public FederatedComputeScheduler(
+ IFederatedComputeService binder, IDataAccessService dataService) {
+ mFcService = binder;
+ mDataAccessService = dataService;
+ }
+
+ // TODO(b/300461799): add federated compute server document.
+ // 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}.
+ *
+ * @param params parameters related to job scheduling.
+ * @param input the configuration of the federated compute. 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) {
+ throw new IllegalStateException(
+ "FederatedComputeScheduler not available for this instance.");
+ }
+
+ android.federatedcompute.common.TrainingInterval trainingInterval =
+ convertTrainingInterval(params.getTrainingInterval());
+ TrainingOptions trainingOptions =
+ new TrainingOptions.Builder()
+ .setPopulationName(input.getPopulationName())
+ .setTrainingInterval(trainingInterval)
+ .build();
+ CountDownLatch latch = new CountDownLatch(1);
+ final int[] err = {0};
+ try {
+ mFcService.schedule(
+ trainingOptions,
+ new IFederatedComputeCallback.Stub() {
+ @Override
+ public void onSuccess() {
+ latch.countDown();
+ }
+
+ @Override
+ public void onFailure(int i) {
+ err[0] = i;
+ latch.countDown();
+ }
+ });
+ latch.await();
+ if (err[0] != 0) {
+ throw new IllegalStateException("Internal failure occurred while scheduling job");
+ }
+ responseCode = Constants.STATUS_SUCCESS;
+ } catch (RemoteException | InterruptedException e) {
+ sLogger.e(TAG + ": Failed to schedule federated compute job", e);
+ throw new IllegalStateException(e);
+ } finally {
+ logApiCallStats(
+ Constants.API_NAME_FEDERATED_COMPUTE_SCHEDULE,
+ System.currentTimeMillis() - startTimeMillis,
+ responseCode);
+ }
+ }
+
+ /**
+ * 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
+ * IsolatedWorker}.
+ *
+ * @param input the configuration of the federated compute. It should be consistent with the
+ * federated compute server setup.
+ */
+ @WorkerThread
+ public void cancel(@NonNull FederatedComputeInput input) {
+ final long startTimeMillis = System.currentTimeMillis();
+ int responseCode = Constants.STATUS_INTERNAL_ERROR;
+ if (mFcService == null) {
+ throw new IllegalStateException(
+ "FederatedComputeScheduler not available for this instance.");
+ }
+ CountDownLatch latch = new CountDownLatch(1);
+ final int[] err = {0};
+ try {
+ mFcService.cancel(
+ input.getPopulationName(),
+ new IFederatedComputeCallback.Stub() {
+ @Override
+ public void onSuccess() {
+ latch.countDown();
+ }
+
+ @Override
+ public void onFailure(int i) {
+ err[0] = i;
+ latch.countDown();
+ }
+ });
+ latch.await();
+ if (err[0] != 0) {
+ throw new IllegalStateException("Internal failure occurred while cancelling job");
+ }
+ responseCode = Constants.STATUS_SUCCESS;
+ } catch (RemoteException | InterruptedException e) {
+ sLogger.e(TAG + ": Failed to cancel federated compute job", e);
+ throw new IllegalStateException(e);
+ } finally {
+ logApiCallStats(
+ Constants.API_NAME_FEDERATED_COMPUTE_CANCEL,
+ System.currentTimeMillis() - startTimeMillis,
+ responseCode);
+ }
+ }
+
+ private android.federatedcompute.common.TrainingInterval convertTrainingInterval(
+ TrainingInterval interval) {
+ return new android.federatedcompute.common.TrainingInterval.Builder()
+ .setMinimumIntervalMillis(interval.getMinimumInterval().toMillis())
+ .setSchedulingMode(convertSchedulingMode(interval))
+ .build();
+ }
+
+ private @android.federatedcompute.common.TrainingInterval.SchedulingMode int
+ convertSchedulingMode(TrainingInterval interval) {
+ switch (interval.getSchedulingMode()) {
+ case TrainingInterval.SCHEDULING_MODE_ONE_TIME:
+ return android.federatedcompute.common.TrainingInterval.SCHEDULING_MODE_ONE_TIME;
+ case TrainingInterval.SCHEDULING_MODE_RECURRENT:
+ return android.federatedcompute.common.TrainingInterval.SCHEDULING_MODE_RECURRENT;
+ default:
+ throw new IllegalStateException(
+ "Unsupported scheduling mode " + interval.getSchedulingMode());
+ }
+ }
+
+ private void logApiCallStats(int apiName, long duration, int responseCode) {
+ try {
+ mDataAccessService.logApiCallStats(apiName, duration, responseCode);
+ } catch (Exception e) {
+ sLogger.d(e, TAG + ": failed to log metrics");
+ }
+ }
+
+ /** The parameters related to job scheduling. */
+ public static class Params {
+ /**
+ * If training interval is scheduled for recurrent tasks, the earliest time this task could
+ * start is after the minimum training interval expires. E.g. If the task is set to run
+ * maximum once per day, the first run of this task will be one day after this task is
+ * scheduled. When a one time job is scheduled, the earliest next runtime is calculated
+ * based on federated compute default interval.
+ */
+ @NonNull private final TrainingInterval mTrainingInterval;
+
+ public Params(@NonNull TrainingInterval trainingInterval) {
+ mTrainingInterval = trainingInterval;
+ }
+
+ @NonNull
+ public TrainingInterval getTrainingInterval() {
+ return mTrainingInterval;
+ }
+ }
+}
diff --git a/android-35/android/adservices/ondevicepersonalization/InferenceInput.java b/android-35/android/adservices/ondevicepersonalization/InferenceInput.java
new file mode 100644
index 0000000..24f3f4f
--- /dev/null
+++ b/android-35/android/adservices/ondevicepersonalization/InferenceInput.java
@@ -0,0 +1,653 @@
+/*
+ * 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.IntDef;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+
+import com.android.adservices.ondevicepersonalization.flags.Flags;
+import com.android.ondevicepersonalization.internal.util.AnnotationValidations;
+import com.android.ondevicepersonalization.internal.util.DataClass;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Contains all the information needed for a run of model inference. The input of {@link
+ * ModelManager#run}.
+ */
+@FlaggedApi(Flags.FLAG_ON_DEVICE_PERSONALIZATION_APIS_ENABLED)
+@DataClass(genBuilder = true, genEqualsHashCode = true)
+public final class InferenceInput {
+ /** The configuration that controls runtime interpreter behavior. */
+ @NonNull private Params mParams;
+
+ /**
+ * An array of input data. The inputs should be in the same order as inputs of the model.
+ *
+ * <p>For example, if a model takes multiple inputs:
+ *
+ * <pre>{@code
+ * String[] input0 = {"foo", "bar"}; // string tensor shape is [2].
+ * int[] input1 = new int[]{3, 2, 1}; // int tensor shape is [3].
+ * Object[] inputData = {input0, input1, ...};
+ * }</pre>
+ *
+ * For TFLite, this field is mapped to inputs of runForMultipleInputsOutputs:
+ * https://www.tensorflow.org/lite/api_docs/java/org/tensorflow/lite/InterpreterApi#parameters_9
+ */
+ @NonNull private Object[] mInputData;
+
+ /**
+ * The number of input examples. Adopter can set this field to run batching inference. The batch
+ * size is 1 by default. The batch size should match the input data size.
+ */
+ private int mBatchSize = 1;
+
+ /**
+ * The empty InferenceOutput representing the expected output structure. For TFLite, the
+ * inference code will verify whether this expected output structure matches model output
+ * signature.
+ *
+ * <p>If a model produce string tensors:
+ *
+ * <pre>{@code
+ * String[] output = new String[3][2]; // Output tensor shape is [3, 2].
+ * HashMap<Integer, Object> outputs = new HashMap<>();
+ * outputs.put(0, output);
+ * expectedOutputStructure = new InferenceOutput.Builder().setDataOutputs(outputs).build();
+ * }</pre>
+ */
+ @NonNull private InferenceOutput mExpectedOutputStructure;
+
+ @DataClass(genBuilder = true, genHiddenConstructor = true, genEqualsHashCode = true)
+ public static class Params {
+ /**
+ * A {@link KeyValueStore} where pre-trained model is stored. Only supports TFLite model
+ * now.
+ */
+ @NonNull private KeyValueStore mKeyValueStore;
+
+ /**
+ * The key of the table where the corresponding value stores a pre-trained model. Only
+ * supports TFLite model now.
+ */
+ @NonNull private String mModelKey;
+
+ /** The model inference will run on CPU. */
+ public static final int DELEGATE_CPU = 1;
+
+ /**
+ * The delegate to run model inference.
+ *
+ * @hide
+ */
+ @IntDef(
+ prefix = "DELEGATE_",
+ value = {DELEGATE_CPU})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Delegate {}
+
+ /**
+ * The delegate to run model inference. If not set, the default value is {@link
+ * #DELEGATE_CPU}.
+ */
+ private @Delegate int mDelegateType = DELEGATE_CPU;
+
+ /** The model is a tensorflow lite model. */
+ public static final int MODEL_TYPE_TENSORFLOW_LITE = 1;
+
+ /**
+ * The type of the model.
+ *
+ * @hide
+ */
+ @IntDef(
+ prefix = "MODEL_TYPE",
+ value = {MODEL_TYPE_TENSORFLOW_LITE})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ModelType {}
+
+ /**
+ * The type of the pre-trained model. If not set, the default value is {@link
+ * #MODEL_TYPE_TENSORFLOW_LITE} . Only supports {@link #MODEL_TYPE_TENSORFLOW_LITE} for now.
+ */
+ private @ModelType int mModelType = MODEL_TYPE_TENSORFLOW_LITE;
+
+ /**
+ * The number of threads used for intraop parallelism on CPU, must be positive number.
+ * Adopters can set this field based on model architecture. The actual thread number depends
+ * on system resources and other constraints.
+ */
+ private @IntRange(from = 1) int mRecommendedNumThreads = 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/InferenceInput.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ // @formatter:off
+
+ /**
+ * Creates a new Params.
+ *
+ * @param keyValueStore A {@link KeyValueStore} where pre-trained model is stored. Only
+ * supports TFLite model now.
+ * @param modelKey The key of the table where the corresponding value stores a pre-trained
+ * model. Only supports TFLite model now.
+ * @param delegateType The delegate to run model inference. If not set, the default value is
+ * {@link #DELEGATE_CPU}.
+ * @param modelType The type of the pre-trained model. If not set, the default value is
+ * {@link #MODEL_TYPE_TENSORFLOW_LITE} . Only supports {@link
+ * #MODEL_TYPE_TENSORFLOW_LITE} for now.
+ * @param recommendedNumThreads The number of threads used for intraop parallelism on CPU,
+ * must be positive number. Adopters can set this field based on model architecture. The
+ * actual thread number depends on system resources and other constraints.
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public Params(
+ @NonNull KeyValueStore keyValueStore,
+ @NonNull String modelKey,
+ @Delegate int delegateType,
+ @ModelType int modelType,
+ @IntRange(from = 1) int recommendedNumThreads) {
+ this.mKeyValueStore = keyValueStore;
+ AnnotationValidations.validate(NonNull.class, null, mKeyValueStore);
+ this.mModelKey = modelKey;
+ AnnotationValidations.validate(NonNull.class, null, mModelKey);
+ this.mDelegateType = delegateType;
+ AnnotationValidations.validate(Delegate.class, null, mDelegateType);
+ this.mModelType = modelType;
+ AnnotationValidations.validate(ModelType.class, null, mModelType);
+ this.mRecommendedNumThreads = recommendedNumThreads;
+ AnnotationValidations.validate(IntRange.class, null, mRecommendedNumThreads, "from", 1);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * A {@link KeyValueStore} where pre-trained model is stored. Only supports TFLite model
+ * now.
+ */
+ @DataClass.Generated.Member
+ public @NonNull KeyValueStore getKeyValueStore() {
+ return mKeyValueStore;
+ }
+
+ /**
+ * The key of the table where the corresponding value stores a pre-trained model. Only
+ * supports TFLite model now.
+ */
+ @DataClass.Generated.Member
+ public @NonNull String getModelKey() {
+ return mModelKey;
+ }
+
+ /**
+ * The delegate to run model inference. If not set, the default value is {@link
+ * #DELEGATE_CPU}.
+ */
+ @DataClass.Generated.Member
+ public @Delegate int getDelegateType() {
+ return mDelegateType;
+ }
+
+ /**
+ * The type of the pre-trained model. If not set, the default value is {@link
+ * #MODEL_TYPE_TENSORFLOW_LITE} . Only supports {@link #MODEL_TYPE_TENSORFLOW_LITE} for now.
+ */
+ @DataClass.Generated.Member
+ public @ModelType int getModelType() {
+ return mModelType;
+ }
+
+ /**
+ * The number of threads used for intraop parallelism on CPU, must be positive number.
+ * Adopters can set this field based on model architecture. The actual thread number depends
+ * on system resources and other constraints.
+ */
+ @DataClass.Generated.Member
+ public @IntRange(from = 1) int getRecommendedNumThreads() {
+ return mRecommendedNumThreads;
+ }
+
+ @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(Params other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ Params that = (Params) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && java.util.Objects.equals(mKeyValueStore, that.mKeyValueStore)
+ && java.util.Objects.equals(mModelKey, that.mModelKey)
+ && mDelegateType == that.mDelegateType
+ && mModelType == that.mModelType
+ && mRecommendedNumThreads == that.mRecommendedNumThreads;
+ }
+
+ @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(mKeyValueStore);
+ _hash = 31 * _hash + java.util.Objects.hashCode(mModelKey);
+ _hash = 31 * _hash + mDelegateType;
+ _hash = 31 * _hash + mModelType;
+ _hash = 31 * _hash + mRecommendedNumThreads;
+ return _hash;
+ }
+
+ /** A builder for {@link Params} */
+ @SuppressWarnings("WeakerAccess")
+ @DataClass.Generated.Member
+ public static final class Builder {
+
+ private @NonNull KeyValueStore mKeyValueStore;
+ private @NonNull String mModelKey;
+ private @Delegate int mDelegateType;
+ private @ModelType int mModelType;
+ private @IntRange(from = 1) int mRecommendedNumThreads;
+
+ private long mBuilderFieldsSet = 0L;
+
+ /**
+ * Creates a new Builder.
+ *
+ * @param keyValueStore A {@link KeyValueStore} where pre-trained model is stored. Only
+ * supports TFLite model now.
+ * @param modelKey The key of the table where the corresponding value stores a
+ * pre-trained model. Only supports TFLite model now.
+ */
+ public Builder(@NonNull KeyValueStore keyValueStore, @NonNull String modelKey) {
+ mKeyValueStore = keyValueStore;
+ AnnotationValidations.validate(NonNull.class, null, mKeyValueStore);
+ mModelKey = modelKey;
+ AnnotationValidations.validate(NonNull.class, null, mModelKey);
+ }
+
+ /**
+ * A {@link KeyValueStore} where pre-trained model is stored. Only supports TFLite model
+ * now.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setKeyValueStore(@NonNull KeyValueStore value) {
+ mBuilderFieldsSet |= 0x1;
+ mKeyValueStore = value;
+ return this;
+ }
+
+ /**
+ * The key of the table where the corresponding value stores a pre-trained model. Only
+ * supports TFLite model now.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setModelKey(@NonNull String value) {
+ mBuilderFieldsSet |= 0x2;
+ mModelKey = value;
+ return this;
+ }
+
+ /**
+ * The delegate to run model inference. If not set, the default value is {@link
+ * #DELEGATE_CPU}.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setDelegateType(@Delegate int value) {
+ mBuilderFieldsSet |= 0x4;
+ mDelegateType = value;
+ return this;
+ }
+
+ /**
+ * The type of the pre-trained model. If not set, the default value is {@link
+ * #MODEL_TYPE_TENSORFLOW_LITE} . Only supports {@link #MODEL_TYPE_TENSORFLOW_LITE} for
+ * now.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setModelType(@ModelType int value) {
+ mBuilderFieldsSet |= 0x8;
+ mModelType = value;
+ return this;
+ }
+
+ /**
+ * The number of threads used for intraop parallelism on CPU, must be positive number.
+ * Adopters can set this field based on model architecture. The actual thread number
+ * depends on system resources and other constraints.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setRecommendedNumThreads(@IntRange(from = 1) int value) {
+ mBuilderFieldsSet |= 0x10;
+ mRecommendedNumThreads = value;
+ return this;
+ }
+
+ /** Builds the instance. */
+ public @NonNull Params build() {
+ mBuilderFieldsSet |= 0x20; // Mark builder used
+
+ if ((mBuilderFieldsSet & 0x4) == 0) {
+ mDelegateType = DELEGATE_CPU;
+ }
+ if ((mBuilderFieldsSet & 0x8) == 0) {
+ mModelType = MODEL_TYPE_TENSORFLOW_LITE;
+ }
+ if ((mBuilderFieldsSet & 0x10) == 0) {
+ mRecommendedNumThreads = 1;
+ }
+ Params o =
+ new Params(
+ mKeyValueStore,
+ mModelKey,
+ mDelegateType,
+ mModelType,
+ mRecommendedNumThreads);
+ return o;
+ }
+ }
+
+ @DataClass.Generated(
+ time = 1709250081597L,
+ codegenVersion = "1.0.23",
+ sourceFile =
+ "packages/modules/OnDevicePersonalization/framework/java/android/adservices/ondevicepersonalization/InferenceInput.java",
+ inputSignatures =
+ "private @android.annotation.NonNull android.adservices.ondevicepersonalization.KeyValueStore mKeyValueStore\nprivate @android.annotation.NonNull java.lang.String mModelKey\npublic static final int DELEGATE_CPU\nprivate @android.adservices.ondevicepersonalization.Params.Delegate int mDelegateType\npublic static final int MODEL_TYPE_TENSORFLOW_LITE\nprivate @android.adservices.ondevicepersonalization.Params.ModelType int mModelType\nprivate @android.annotation.IntRange int mRecommendedNumThreads\nclass Params extends java.lang.Object implements []\[email protected](genBuilder=true, genHiddenConstructor=true, genEqualsHashCode=true)")
+ @Deprecated
+ private void __metadata() {}
+
+ // @formatter:on
+ // End of generated code
+
+ }
+
+ // 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/InferenceInput.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 */ InferenceInput(
+ @NonNull Params params,
+ @NonNull Object[] inputData,
+ int batchSize,
+ @NonNull InferenceOutput expectedOutputStructure) {
+ this.mParams = params;
+ AnnotationValidations.validate(NonNull.class, null, mParams);
+ this.mInputData = inputData;
+ AnnotationValidations.validate(NonNull.class, null, mInputData);
+ this.mBatchSize = batchSize;
+ this.mExpectedOutputStructure = expectedOutputStructure;
+ AnnotationValidations.validate(NonNull.class, null, mExpectedOutputStructure);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /** The configuration that controls runtime interpreter behavior. */
+ @DataClass.Generated.Member
+ public @NonNull Params getParams() {
+ return mParams;
+ }
+
+ /**
+ * An array of input data. The inputs should be in the same order as inputs of the model.
+ *
+ * <p>For example, if a model takes multiple inputs:
+ *
+ * <pre>{@code
+ * String[] input0 = {"foo", "bar"}; // string tensor shape is [2].
+ * int[] input1 = new int[]{3, 2, 1}; // int tensor shape is [3].
+ * Object[] inputData = {input0, input1, ...};
+ * }</pre>
+ *
+ * For TFLite, this field is mapped to inputs of runForMultipleInputsOutputs:
+ * https://www.tensorflow.org/lite/api_docs/java/org/tensorflow/lite/InterpreterApi#parameters_9
+ */
+ @SuppressLint("ArrayReturn")
+ @DataClass.Generated.Member
+ public @NonNull Object[] getInputData() {
+ return mInputData;
+ }
+
+ /**
+ * The number of input examples. Adopter can set this field to run batching inference. The batch
+ * size is 1 by default. The batch size should match the input data size.
+ */
+ @DataClass.Generated.Member
+ public int getBatchSize() {
+ return mBatchSize;
+ }
+
+ /**
+ * The empty InferenceOutput representing the expected output structure. For TFLite, the
+ * inference code will verify whether this expected output structure matches model output
+ * signature.
+ *
+ * <p>If a model produce string tensors:
+ *
+ * <pre>{@code
+ * String[] output = new String[3][2]; // Output tensor shape is [3, 2].
+ * HashMap<Integer, Object> outputs = new HashMap<>();
+ * outputs.put(0, output);
+ * expectedOutputStructure = new InferenceOutput.Builder().setDataOutputs(outputs).build();
+ * }</pre>
+ */
+ @DataClass.Generated.Member
+ public @NonNull InferenceOutput getExpectedOutputStructure() {
+ return mExpectedOutputStructure;
+ }
+
+ @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(InferenceInput other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ InferenceInput that = (InferenceInput) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && java.util.Objects.equals(mParams, that.mParams)
+ && java.util.Arrays.equals(mInputData, that.mInputData)
+ && mBatchSize == that.mBatchSize
+ && java.util.Objects.equals(
+ mExpectedOutputStructure, that.mExpectedOutputStructure);
+ }
+
+ @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.Arrays.hashCode(mInputData);
+ _hash = 31 * _hash + mBatchSize;
+ _hash = 31 * _hash + java.util.Objects.hashCode(mExpectedOutputStructure);
+ return _hash;
+ }
+
+ /** A builder for {@link InferenceInput} */
+ @SuppressWarnings("WeakerAccess")
+ @DataClass.Generated.Member
+ public static final class Builder {
+
+ private @NonNull Params mParams;
+ private @NonNull Object[] mInputData;
+ private int mBatchSize;
+ private @NonNull InferenceOutput mExpectedOutputStructure;
+
+ private long mBuilderFieldsSet = 0L;
+
+ /**
+ * Creates a new Builder.
+ *
+ * @param params The configuration that controls runtime interpreter behavior.
+ * @param inputData An array of input data. The inputs should be in the same order as inputs
+ * of the model.
+ * <p>For example, if a model takes multiple inputs:
+ * <pre>{@code
+ * String[] input0 = {"foo", "bar"}; // string tensor shape is [2].
+ * int[] input1 = new int[]{3, 2, 1}; // int tensor shape is [3].
+ * Object[] inputData = {input0, input1, ...};
+ *
+ * }</pre>
+ * For TFLite, this field is mapped to inputs of runForMultipleInputsOutputs:
+ * https://www.tensorflow.org/lite/api_docs/java/org/tensorflow/lite/InterpreterApi#parameters_9
+ * @param expectedOutputStructure The empty InferenceOutput representing the expected output
+ * structure. For TFLite, the inference code will verify whether this expected output
+ * structure matches model output signature.
+ * <p>If a model produce string tensors:
+ * <pre>{@code
+ * String[] output = new String[3][2]; // Output tensor shape is [3, 2].
+ * HashMap<Integer, Object> outputs = new HashMap<>();
+ * outputs.put(0, output);
+ * expectedOutputStructure = new InferenceOutput.Builder().setDataOutputs(outputs).build();
+ *
+ * }</pre>
+ */
+ public Builder(
+ @NonNull Params params,
+ @SuppressLint("ArrayReturn") @NonNull Object[] inputData,
+ @NonNull InferenceOutput expectedOutputStructure) {
+ mParams = params;
+ AnnotationValidations.validate(NonNull.class, null, mParams);
+ mInputData = inputData;
+ AnnotationValidations.validate(NonNull.class, null, mInputData);
+ mExpectedOutputStructure = expectedOutputStructure;
+ AnnotationValidations.validate(NonNull.class, null, mExpectedOutputStructure);
+ }
+
+ /** The configuration that controls runtime interpreter behavior. */
+ @DataClass.Generated.Member
+ public @NonNull Builder setParams(@NonNull Params value) {
+ mBuilderFieldsSet |= 0x1;
+ mParams = value;
+ return this;
+ }
+
+ /**
+ * An array of input data. The inputs should be in the same order as inputs of the model.
+ *
+ * <p>For example, if a model takes multiple inputs:
+ *
+ * <pre>{@code
+ * String[] input0 = {"foo", "bar"}; // string tensor shape is [2].
+ * int[] input1 = new int[]{3, 2, 1}; // int tensor shape is [3].
+ * Object[] inputData = {input0, input1, ...};
+ * }</pre>
+ *
+ * For TFLite, this field is mapped to inputs of runForMultipleInputsOutputs:
+ * https://www.tensorflow.org/lite/api_docs/java/org/tensorflow/lite/InterpreterApi#parameters_9
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setInputData(@NonNull Object... value) {
+ mBuilderFieldsSet |= 0x2;
+ mInputData = value;
+ return this;
+ }
+
+ /**
+ * The number of input examples. Adopter can set this field to run batching inference. The
+ * batch size is 1 by default. The batch size should match the input data size.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setBatchSize(int value) {
+ mBuilderFieldsSet |= 0x4;
+ mBatchSize = value;
+ return this;
+ }
+
+ /**
+ * The empty InferenceOutput representing the expected output structure. For TFLite, the
+ * inference code will verify whether this expected output structure matches model output
+ * signature.
+ *
+ * <p>If a model produce string tensors:
+ *
+ * <pre>{@code
+ * String[] output = new String[3][2]; // Output tensor shape is [3, 2].
+ * HashMap<Integer, Object> outputs = new HashMap<>();
+ * outputs.put(0, output);
+ * expectedOutputStructure = new InferenceOutput.Builder().setDataOutputs(outputs).build();
+ * }</pre>
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setExpectedOutputStructure(@NonNull InferenceOutput value) {
+ mBuilderFieldsSet |= 0x8;
+ mExpectedOutputStructure = value;
+ return this;
+ }
+
+ /** Builds the instance. */
+ public @NonNull InferenceInput build() {
+ mBuilderFieldsSet |= 0x10; // Mark builder used
+
+ if ((mBuilderFieldsSet & 0x4) == 0) {
+ mBatchSize = 1;
+ }
+ InferenceInput o =
+ new InferenceInput(mParams, mInputData, mBatchSize, mExpectedOutputStructure);
+ return o;
+ }
+ }
+
+ @DataClass.Generated(
+ time = 1709250081618L,
+ codegenVersion = "1.0.23",
+ sourceFile =
+ "packages/modules/OnDevicePersonalization/framework/java/android/adservices/ondevicepersonalization/InferenceInput.java",
+ inputSignatures =
+ "private @android.annotation.NonNull android.adservices.ondevicepersonalization.Params mParams\nprivate @android.annotation.NonNull java.lang.Object[] mInputData\nprivate int mBatchSize\nprivate @android.annotation.NonNull android.adservices.ondevicepersonalization.InferenceOutput mExpectedOutputStructure\nclass InferenceInput extends java.lang.Object implements []\[email protected](genBuilder=true, genEqualsHashCode=true)")
+ @Deprecated
+ private void __metadata() {}
+
+ // @formatter:on
+ // End of generated code
+
+}
diff --git a/android-35/android/adservices/ondevicepersonalization/InferenceInputParcel.java b/android-35/android/adservices/ondevicepersonalization/InferenceInputParcel.java
new file mode 100644
index 0000000..ad7ff56
--- /dev/null
+++ b/android-35/android/adservices/ondevicepersonalization/InferenceInputParcel.java
@@ -0,0 +1,280 @@
+/*
+ * 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.IntRange;
+import android.annotation.NonNull;
+import android.os.Parcelable;
+
+import com.android.ondevicepersonalization.internal.util.AnnotationValidations;
+import com.android.ondevicepersonalization.internal.util.ByteArrayParceledListSlice;
+import com.android.ondevicepersonalization.internal.util.DataClass;
+
+/**
+ * Parcelable version of {@link InferenceInput}.
+ *
+ * @hide
+ */
+@DataClass(genAidl = false, genBuilder = false)
+public class InferenceInputParcel implements Parcelable {
+ /**
+ * The location of TFLite model. The model is usually store in REMOTE_DATA or LOCAL_DATA table.
+ */
+ @NonNull private ModelId mModelId;
+
+ /** The delegate to run model inference. If not specified, CPU delegate is used by default. */
+ private @InferenceInput.Params.Delegate int mDelegate;
+
+ /**
+ * The number of threads available to the interpreter. Only set and take effective when input
+ * tensors are on CPU. Setting cpuNumThread to 0 has the effect to disable multithreading, which
+ * is equivalent to setting cpuNumThread to 1. If set to the value -1, the number of threads
+ * used will be implementation-defined and platform-dependent.
+ */
+ private @IntRange(from = 1) int mCpuNumThread;
+
+ /** An array of input data. The inputs should be in the same order as inputs of the model. */
+ @NonNull private ByteArrayParceledListSlice mInputData;
+
+ /**
+ * The number of input examples. Adopter can set this field to run batching inference. The batch
+ * size is 1 by default.
+ */
+ private int mBatchSize;
+
+ private @InferenceInput.Params.ModelType int mModelType =
+ InferenceInput.Params.MODEL_TYPE_TENSORFLOW_LITE;
+
+ /**
+ * The empty InferenceOutput representing the expected output structure. For TFLite, the
+ * inference code will verify whether this expected output structure matches model output
+ * signature.
+ */
+ @NonNull private InferenceOutputParcel mExpectedOutputStructure;
+
+ /** @hide */
+ public InferenceInputParcel(@NonNull InferenceInput value) {
+ this(
+ new ModelId.Builder()
+ .setTableId(value.getParams().getKeyValueStore().getTableId())
+ .setKey(value.getParams().getModelKey())
+ .build(),
+ value.getParams().getDelegateType(),
+ value.getParams().getRecommendedNumThreads(),
+ ByteArrayParceledListSlice.create(value.getInputData()),
+ value.getBatchSize(),
+ value.getParams().getModelType(),
+ new InferenceOutputParcel(value.getExpectedOutputStructure()));
+ }
+
+ // 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/InferenceInputParcel.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ // @formatter:off
+
+ /**
+ * Creates a new InferenceInputParcel.
+ *
+ * @param modelId The location of TFLite model. The model is usually store in REMOTE_DATA or
+ * LOCAL_DATA table.
+ * @param delegate The delegate to run model inference. If not specified, CPU delegate is used
+ * by default.
+ * @param cpuNumThread The number of threads available to the interpreter. Only set and take
+ * effective when input tensors are on CPU. Setting cpuNumThread to 0 has the effect to
+ * disable multithreading, which is equivalent to setting cpuNumThread to 1. If set to the
+ * value -1, the number of threads used will be implementation-defined and
+ * platform-dependent.
+ * @param inputData An array of input data. The inputs should be in the same order as inputs of
+ * the model.
+ * @param batchSize The number of input examples. Adopter can set this field to run batching
+ * inference. The batch size is 1 by default.
+ * @param expectedOutputStructure The empty InferenceOutput representing the expected output
+ * structure. For TFLite, the inference code will verify whether this expected output
+ * structure matches model output signature.
+ */
+ @DataClass.Generated.Member
+ public InferenceInputParcel(
+ @NonNull ModelId modelId,
+ @InferenceInput.Params.Delegate int delegate,
+ @IntRange(from = 1) int cpuNumThread,
+ @NonNull ByteArrayParceledListSlice inputData,
+ int batchSize,
+ @InferenceInput.Params.ModelType int modelType,
+ @NonNull InferenceOutputParcel expectedOutputStructure) {
+ this.mModelId = modelId;
+ AnnotationValidations.validate(NonNull.class, null, mModelId);
+ this.mDelegate = delegate;
+ AnnotationValidations.validate(InferenceInput.Params.Delegate.class, null, mDelegate);
+ this.mCpuNumThread = cpuNumThread;
+ AnnotationValidations.validate(IntRange.class, null, mCpuNumThread, "from", 1);
+ this.mInputData = inputData;
+ AnnotationValidations.validate(NonNull.class, null, mInputData);
+ this.mBatchSize = batchSize;
+ this.mModelType = modelType;
+ AnnotationValidations.validate(InferenceInput.Params.ModelType.class, null, mModelType);
+ this.mExpectedOutputStructure = expectedOutputStructure;
+ AnnotationValidations.validate(NonNull.class, null, mExpectedOutputStructure);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * The location of TFLite model. The model is usually store in REMOTE_DATA or LOCAL_DATA table.
+ */
+ @DataClass.Generated.Member
+ public @NonNull ModelId getModelId() {
+ return mModelId;
+ }
+
+ /** The delegate to run model inference. If not specified, CPU delegate is used by default. */
+ @DataClass.Generated.Member
+ public @InferenceInput.Params.Delegate int getDelegate() {
+ return mDelegate;
+ }
+
+ /**
+ * The number of threads available to the interpreter. Only set and take effective when input
+ * tensors are on CPU. Setting cpuNumThread to 0 has the effect to disable multithreading, which
+ * is equivalent to setting cpuNumThread to 1. If set to the value -1, the number of threads
+ * used will be implementation-defined and platform-dependent.
+ */
+ @DataClass.Generated.Member
+ public @IntRange(from = 1) int getCpuNumThread() {
+ return mCpuNumThread;
+ }
+
+ /** An array of input data. The inputs should be in the same order as inputs of the model. */
+ @DataClass.Generated.Member
+ public @NonNull ByteArrayParceledListSlice getInputData() {
+ return mInputData;
+ }
+
+ /**
+ * The number of input examples. Adopter can set this field to run batching inference. The batch
+ * size is 1 by default.
+ */
+ @DataClass.Generated.Member
+ public int getBatchSize() {
+ return mBatchSize;
+ }
+
+ @DataClass.Generated.Member
+ public @InferenceInput.Params.ModelType int getModelType() {
+ return mModelType;
+ }
+
+ /**
+ * The empty InferenceOutput representing the expected output structure. For TFLite, the
+ * inference code will verify whether this expected output structure matches model output
+ * signature.
+ */
+ @DataClass.Generated.Member
+ public @NonNull InferenceOutputParcel getExpectedOutputStructure() {
+ return mExpectedOutputStructure;
+ }
+
+ @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.writeTypedObject(mModelId, flags);
+ dest.writeInt(mDelegate);
+ dest.writeInt(mCpuNumThread);
+ dest.writeTypedObject(mInputData, flags);
+ dest.writeInt(mBatchSize);
+ dest.writeInt(mModelType);
+ dest.writeTypedObject(mExpectedOutputStructure, flags);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() {
+ return 0;
+ }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ protected InferenceInputParcel(@NonNull android.os.Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ ModelId modelId = (ModelId) in.readTypedObject(ModelId.CREATOR);
+ int delegate = in.readInt();
+ int cpuNumThread = in.readInt();
+ ByteArrayParceledListSlice inputData =
+ (ByteArrayParceledListSlice) in.readTypedObject(ByteArrayParceledListSlice.CREATOR);
+ int batchSize = in.readInt();
+ int modelType = in.readInt();
+ InferenceOutputParcel expectedOutputStructure =
+ (InferenceOutputParcel) in.readTypedObject(InferenceOutputParcel.CREATOR);
+
+ this.mModelId = modelId;
+ AnnotationValidations.validate(NonNull.class, null, mModelId);
+ this.mDelegate = delegate;
+ AnnotationValidations.validate(InferenceInput.Params.Delegate.class, null, mDelegate);
+ this.mCpuNumThread = cpuNumThread;
+ AnnotationValidations.validate(IntRange.class, null, mCpuNumThread, "from", 1);
+ this.mInputData = inputData;
+ AnnotationValidations.validate(NonNull.class, null, mInputData);
+ this.mBatchSize = batchSize;
+ this.mModelType = modelType;
+ AnnotationValidations.validate(InferenceInput.Params.ModelType.class, null, mModelType);
+ this.mExpectedOutputStructure = expectedOutputStructure;
+ AnnotationValidations.validate(NonNull.class, null, mExpectedOutputStructure);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<InferenceInputParcel> CREATOR =
+ new Parcelable.Creator<InferenceInputParcel>() {
+ @Override
+ public InferenceInputParcel[] newArray(int size) {
+ return new InferenceInputParcel[size];
+ }
+
+ @Override
+ public InferenceInputParcel createFromParcel(@NonNull android.os.Parcel in) {
+ return new InferenceInputParcel(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1708579683131L,
+ codegenVersion = "1.0.23",
+ sourceFile =
+ "packages/modules/OnDevicePersonalization/framework/java/android/adservices/ondevicepersonalization/InferenceInputParcel.java",
+ inputSignatures =
+ "private @android.annotation.NonNull android.adservices.ondevicepersonalization.ModelId mModelId\nprivate @android.adservices.ondevicepersonalization.InferenceInput.Params.Delegate int mDelegate\nprivate @android.annotation.IntRange int mCpuNumThread\nprivate @android.annotation.NonNull com.android.ondevicepersonalization.internal.util.ByteArrayParceledListSlice mInputData\nprivate int mBatchSize\nprivate @android.adservices.ondevicepersonalization.InferenceInput.Params.ModelType int mModelType\nprivate @android.annotation.NonNull android.adservices.ondevicepersonalization.InferenceOutputParcel mExpectedOutputStructure\nclass InferenceInputParcel 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/android-35/android/adservices/ondevicepersonalization/InferenceOutput.java b/android-35/android/adservices/ondevicepersonalization/InferenceOutput.java
new file mode 100644
index 0000000..104a9ae
--- /dev/null
+++ b/android-35/android/adservices/ondevicepersonalization/InferenceOutput.java
@@ -0,0 +1,170 @@
+/*
+ * 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.NonNull;
+
+import com.android.adservices.ondevicepersonalization.flags.Flags;
+import com.android.ondevicepersonalization.internal.util.AnnotationValidations;
+import com.android.ondevicepersonalization.internal.util.DataClass;
+
+import java.util.Collections;
+import java.util.Map;
+
+/** The result returned by {@link ModelManager#run}. */
+@FlaggedApi(Flags.FLAG_ON_DEVICE_PERSONALIZATION_APIS_ENABLED)
+@DataClass(genBuilder = true, genEqualsHashCode = true)
+public final class InferenceOutput {
+ /**
+ * A map mapping output indices to multidimensional arrays of output.
+ *
+ * <p>For TFLite, this field is mapped to outputs of runForMultipleInputsOutputs:
+ * https://www.tensorflow.org/lite/api_docs/java/org/tensorflow/lite/InterpreterApi#parameters_9
+ */
+ @NonNull private Map<Integer, Object> mDataOutputs = Collections.emptyMap();
+
+ // 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/InferenceOutput.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 */ InferenceOutput(@NonNull Map<Integer, Object> dataOutputs) {
+ this.mDataOutputs = dataOutputs;
+ AnnotationValidations.validate(NonNull.class, null, mDataOutputs);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * A map mapping output indices to multidimensional arrays of output.
+ *
+ * <p>For TFLite, this field is mapped to outputs of runForMultipleInputsOutputs:
+ * https://www.tensorflow.org/lite/api_docs/java/org/tensorflow/lite/InterpreterApi#parameters_9
+ */
+ @DataClass.Generated.Member
+ public @NonNull Map<Integer, Object> getDataOutputs() {
+ return mDataOutputs;
+ }
+
+ @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(InferenceOutput other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ InferenceOutput that = (InferenceOutput) o;
+ //noinspection PointlessBooleanExpression
+ return true && java.util.Objects.equals(mDataOutputs, that.mDataOutputs);
+ }
+
+ @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(mDataOutputs);
+ return _hash;
+ }
+
+ /** A builder for {@link InferenceOutput} */
+ @SuppressWarnings("WeakerAccess")
+ @DataClass.Generated.Member
+ public static final class Builder {
+
+ private @NonNull Map<Integer, Object> mDataOutputs;
+
+ private long mBuilderFieldsSet = 0L;
+
+ public Builder() {}
+
+ /**
+ * A map mapping output indices to multidimensional arrays of output.
+ *
+ * <p>For TFLite, this field is mapped to outputs of runForMultipleInputsOutputs:
+ * https://www.tensorflow.org/lite/api_docs/java/org/tensorflow/lite/InterpreterApi#parameters_9
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setDataOutputs(@NonNull Map<Integer, Object> value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x1;
+ mDataOutputs = value;
+ return this;
+ }
+
+ /**
+ * @see #setDataOutputs
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder addDataOutput(int key, @NonNull Object value) {
+ // You can refine this method's name by providing item's singular name, e.g.:
+ // @DataClass.PluralOf("item")) mItems = ...
+
+ if (mDataOutputs == null) setDataOutputs(new java.util.LinkedHashMap());
+ mDataOutputs.put(key, value);
+ return this;
+ }
+
+ /** Builds the instance. This builder should not be touched after calling this! */
+ public @NonNull InferenceOutput build() {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x2; // Mark builder used
+
+ if ((mBuilderFieldsSet & 0x1) == 0) {
+ mDataOutputs = Collections.emptyMap();
+ }
+ InferenceOutput o = new InferenceOutput(mDataOutputs);
+ 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 = 1707187954917L,
+ codegenVersion = "1.0.23",
+ sourceFile =
+ "packages/modules/OnDevicePersonalization/framework/java/android/adservices/ondevicepersonalization/InferenceOutput.java",
+ inputSignatures =
+ "private @android.annotation.NonNull java.util.Map<java.lang.Integer,java.lang.Object> mDataOutputs\nclass InferenceOutput extends java.lang.Object implements []\[email protected](genBuilder=true, genEqualsHashCode=true)")
+ @Deprecated
+ private void __metadata() {}
+
+ // @formatter:on
+ // End of generated code
+
+}
diff --git a/android-35/android/adservices/ondevicepersonalization/InferenceOutputParcel.java b/android-35/android/adservices/ondevicepersonalization/InferenceOutputParcel.java
new file mode 100644
index 0000000..89f857b
--- /dev/null
+++ b/android-35/android/adservices/ondevicepersonalization/InferenceOutputParcel.java
@@ -0,0 +1,143 @@
+/*
+ * 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;
+
+import java.util.Collections;
+import java.util.Map;
+
+/**
+ * Parcelable version of {@link InferenceOutput}.
+ *
+ * @hide
+ */
+@DataClass(genAidl = false, genBuilder = false)
+public final class InferenceOutputParcel implements Parcelable {
+ /**
+ * A map mapping output indices to multidimensional arrays of output. For TFLite, this field is
+ * mapped to outputs of runForMultipleInputsOutputs:
+ * https://www.tensorflow.org/lite/api_docs/java/org/tensorflow/lite/InterpreterApi#parameters_9
+ */
+ @NonNull private Map<Integer, Object> mData = Collections.emptyMap();
+
+ /** @hide */
+ public InferenceOutputParcel(@NonNull InferenceOutput value) {
+ this(value.getDataOutputs());
+ }
+
+ // 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/InferenceOutputParcel.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ // @formatter:off
+
+ /**
+ * Creates a new InferenceOutputParcel.
+ *
+ * @param data A map mapping output indices to multidimensional arrays of output. For TFLite,
+ * this field is mapped to outputs of runForMultipleInputsOutputs:
+ * https://www.tensorflow.org/lite/api_docs/java/org/tensorflow/lite/InterpreterApi#parameters_9
+ */
+ @DataClass.Generated.Member
+ public InferenceOutputParcel(@NonNull Map<Integer, Object> data) {
+ this.mData = data;
+ AnnotationValidations.validate(NonNull.class, null, mData);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * A map mapping output indices to multidimensional arrays of output. For TFLite, this field is
+ * mapped to outputs of runForMultipleInputsOutputs:
+ * https://www.tensorflow.org/lite/api_docs/java/org/tensorflow/lite/InterpreterApi#parameters_9
+ */
+ @DataClass.Generated.Member
+ public @NonNull Map<Integer, Object> getData() {
+ return mData;
+ }
+
+ @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.writeMap(mData);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() {
+ return 0;
+ }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ protected InferenceOutputParcel(@NonNull android.os.Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ Map<Integer, Object> data = new java.util.LinkedHashMap<>();
+ in.readMap(data, Object.class.getClassLoader());
+
+ this.mData = data;
+ AnnotationValidations.validate(NonNull.class, null, mData);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<InferenceOutputParcel> CREATOR =
+ new Parcelable.Creator<InferenceOutputParcel>() {
+ @Override
+ public InferenceOutputParcel[] newArray(int size) {
+ return new InferenceOutputParcel[size];
+ }
+
+ @Override
+ public InferenceOutputParcel createFromParcel(@NonNull android.os.Parcel in) {
+ return new InferenceOutputParcel(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1706291599206L,
+ codegenVersion = "1.0.23",
+ sourceFile =
+ "packages/modules/OnDevicePersonalization/framework/java/android/adservices/ondevicepersonalization/InferenceOutputParcel.java",
+ inputSignatures =
+ "private @android.annotation.NonNull java.util.Map<java.lang.Integer,java.lang.Object> mData\nclass InferenceOutputParcel 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/android-35/android/adservices/ondevicepersonalization/IsolatedService.java b/android-35/android/adservices/ondevicepersonalization/IsolatedService.java
new file mode 100644
index 0000000..13cc7c2
--- /dev/null
+++ b/android-35/android/adservices/ondevicepersonalization/IsolatedService.java
@@ -0,0 +1,560 @@
+/*
+ * 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.adservices.ondevicepersonalization.aidl.IDataAccessService;
+import android.adservices.ondevicepersonalization.aidl.IFederatedComputeService;
+import android.adservices.ondevicepersonalization.aidl.IIsolatedModelService;
+import android.adservices.ondevicepersonalization.aidl.IIsolatedService;
+import android.adservices.ondevicepersonalization.aidl.IIsolatedServiceCallback;
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Service;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.OutcomeReceiver;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.os.SystemClock;
+
+import com.android.adservices.ondevicepersonalization.flags.Flags;
+import com.android.ondevicepersonalization.internal.util.LoggerFactory;
+import com.android.ondevicepersonalization.internal.util.OdpParceledListSlice;
+
+import java.util.Objects;
+import java.util.function.Function;
+
+// TODO(b/289102463): Add a link to the public ODP developer documentation.
+/**
+ * Base class for services that are started by ODP on a call to
+ * {@code OnDevicePersonalizationManager#execute(ComponentName, PersistableBundle,
+ * java.util.concurrent.Executor, OutcomeReceiver)}
+ * and run in an <a
+ * href="https://developer.android.com/guide/topics/manifest/service-element#isolated">isolated
+ * process</a>. The service can produce content to be displayed in a
+ * {@link android.view.SurfaceView} in a calling app and write persistent results to on-device
+ * storage, which can be consumed by Federated Analytics for cross-device statistical analysis or
+ * by Federated Learning for model training.
+ * Client apps use {@link OnDevicePersonalizationManager} to interact with an {@link
+ * IsolatedService}.
+ */
+@FlaggedApi(Flags.FLAG_ON_DEVICE_PERSONALIZATION_APIS_ENABLED)
+public abstract class IsolatedService extends Service {
+ private static final String TAG = IsolatedService.class.getSimpleName();
+ private static final LoggerFactory.Logger sLogger = LoggerFactory.getLogger();
+ private IBinder mBinder;
+
+ /** Creates a binder for an {@link IsolatedService}. */
+ @Override
+ public void onCreate() {
+ mBinder = new ServiceBinder();
+ }
+
+ /**
+ * Handles binding to the {@link IsolatedService}.
+ *
+ * @param intent The Intent that was used to bind to this service, as given to {@link
+ * android.content.Context#bindService Context.bindService}. Note that any extras that were
+ * included with the Intent at that point will <em>not</em> be seen here.
+ */
+ @Override
+ @Nullable
+ public IBinder onBind(@NonNull Intent intent) {
+ return mBinder;
+ }
+
+ /**
+ * Return an instance of an {@link IsolatedWorker} that handles client requests.
+ *
+ * @param requestToken an opaque token that identifies the current request to the service that
+ * must be passed to service methods that depend on per-request state.
+ */
+ @NonNull
+ public abstract IsolatedWorker onRequest(@NonNull RequestToken requestToken);
+
+ /**
+ * Returns a Data Access Object for the REMOTE_DATA table. The REMOTE_DATA table is a read-only
+ * key-value store that contains data that is periodically downloaded from an endpoint declared
+ * in the <download> tag in the ODP manifest of the service, as shown in the following example.
+ *
+ * <pre>{@code
+ * <!-- Contents of res/xml/OdpSettings.xml -->
+ * <on-device-personalization>
+ * <!-- Name of the service subclass -->
+ * <service "com.example.odpsample.SampleService">
+ * <!-- If this tag is present, ODP will periodically poll this URL and
+ * download content to populate REMOTE_DATA. Adopters that do not need to
+ * download content from their servers can skip this tag. -->
+ * <download-settings url="https://example.com/get" />
+ * </service>
+ * </on-device-personalization>
+ * }</pre>
+ *
+ * @param requestToken an opaque token that identifies the current request to the service.
+ * @return A {@link KeyValueStore} object that provides access to the REMOTE_DATA table. The
+ * methods in the returned {@link KeyValueStore} are blocking operations and should be
+ * called from a worker thread and not the main thread or a binder thread.
+ * @see #onRequest(RequestToken)
+ */
+ @NonNull
+ public final KeyValueStore getRemoteData(@NonNull RequestToken requestToken) {
+ return new RemoteDataImpl(requestToken.getDataAccessService());
+ }
+
+ /**
+ * Returns a Data Access Object for the LOCAL_DATA table. The LOCAL_DATA table is a persistent
+ * key-value store that the service can use to store any data. The contents of this table are
+ * visible only to the service running in an isolated process and cannot be sent outside the
+ * device.
+ *
+ * @param requestToken an opaque token that identifies the current request to the service.
+ * @return A {@link MutableKeyValueStore} object that provides access to the LOCAL_DATA table.
+ * The methods in the returned {@link MutableKeyValueStore} are blocking operations and
+ * should be called from a worker thread and not the main thread or a binder thread.
+ * @see #onRequest(RequestToken)
+ */
+ @NonNull
+ public final MutableKeyValueStore getLocalData(@NonNull RequestToken requestToken) {
+ return new LocalDataImpl(requestToken.getDataAccessService());
+ }
+
+ /**
+ * Returns a DAO for the REQUESTS and EVENTS tables that provides
+ * access to the rows that are readable by the IsolatedService.
+ *
+ * @param requestToken an opaque token that identifies the current request to the service.
+ * @return A {@link LogReader} object that provides access to the REQUESTS and EVENTS table.
+ * The methods in the returned {@link LogReader} are blocking operations and
+ * should be called from a worker thread and not the main thread or a binder thread.
+ * @see #onRequest(RequestToken)
+ */
+ @NonNull
+ public final LogReader getLogReader(@NonNull RequestToken requestToken) {
+ return new LogReader(requestToken.getDataAccessService());
+ }
+
+ /**
+ * Returns an {@link EventUrlProvider} for the current request. The {@link EventUrlProvider}
+ * provides URLs that can be embedded in HTML. When the HTML is rendered in an
+ * {@link android.webkit.WebView}, the platform intercepts requests to these URLs and invokes
+ * {@code IsolatedWorker#onEvent(EventInput, Consumer)}.
+ *
+ * @param requestToken an opaque token that identifies the current request to the service.
+ * @return An {@link EventUrlProvider} that returns event tracking URLs.
+ * @see #onRequest(RequestToken)
+ */
+ @NonNull
+ public final EventUrlProvider getEventUrlProvider(@NonNull RequestToken requestToken) {
+ return new EventUrlProvider(requestToken.getDataAccessService());
+ }
+
+ /**
+ * Returns the platform-provided {@link UserData} for the current request.
+ *
+ * @param requestToken an opaque token that identifies the current request to the service.
+ * @return A {@link UserData} object.
+ * @see #onRequest(RequestToken)
+ */
+ @Nullable
+ public final UserData getUserData(@NonNull RequestToken requestToken) {
+ return requestToken.getUserData();
+ }
+
+ /**
+ * Returns an {@link FederatedComputeScheduler} for the current request. The {@link
+ * FederatedComputeScheduler} can be used to schedule and cancel federated computation jobs.
+ * The federated computation includes federated learning and federated analytic jobs.
+ *
+ * @param requestToken an opaque token that identifies the current request to the service.
+ * @return An {@link FederatedComputeScheduler} that returns a federated computation job
+ * scheduler.
+ * @see #onRequest(RequestToken)
+ */
+ @NonNull
+ public final FederatedComputeScheduler getFederatedComputeScheduler(
+ @NonNull RequestToken requestToken) {
+ return new FederatedComputeScheduler(
+ requestToken.getFederatedComputeService(),
+ requestToken.getDataAccessService());
+ }
+
+ /**
+ * Returns an {@link ModelManager} for the current request. The {@link ModelManager} can be used
+ * to do model inference. It only supports Tensorflow Lite model inference now.
+ *
+ * @param requestToken an opaque token that identifies the current request to the service.
+ * @return An {@link ModelManager} that can be used for model inference.
+ */
+ @NonNull
+ public final ModelManager getModelManager(@NonNull RequestToken requestToken) {
+ return new ModelManager(
+ requestToken.getDataAccessService(), requestToken.getModelService());
+ }
+
+ // TODO(b/228200518): Add onBidRequest()/onBidResponse() methods.
+
+ class ServiceBinder extends IIsolatedService.Stub {
+ @Override
+ public void onRequest(
+ int operationCode,
+ @NonNull Bundle params,
+ @NonNull IIsolatedServiceCallback resultCallback) {
+ Objects.requireNonNull(params);
+ Objects.requireNonNull(resultCallback);
+ final long token = Binder.clearCallingIdentity();
+ // TODO(b/228200518): Ensure that caller is ODP Service.
+ // TODO(b/323592348): Add model inference in other flows.
+ try {
+ performRequest(operationCode, params, resultCallback);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ private void performRequest(
+ int operationCode,
+ @NonNull Bundle params,
+ @NonNull IIsolatedServiceCallback resultCallback) {
+
+ if (operationCode == Constants.OP_EXECUTE) {
+ performExecute(params, resultCallback);
+ } else if (operationCode == Constants.OP_DOWNLOAD) {
+ performDownload(params, resultCallback);
+ } else if (operationCode == Constants.OP_RENDER) {
+ performRender(params, resultCallback);
+ } else if (operationCode == Constants.OP_WEB_VIEW_EVENT) {
+ performOnWebViewEvent(params, resultCallback);
+ } else if (operationCode == Constants.OP_TRAINING_EXAMPLE) {
+ performOnTrainingExample(params, resultCallback);
+ } else if (operationCode == Constants.OP_WEB_TRIGGER) {
+ performOnWebTrigger(params, resultCallback);
+ } else {
+ throw new IllegalArgumentException("Invalid op code: " + operationCode);
+ }
+ }
+
+ private void performOnWebTrigger(
+ @NonNull Bundle params, @NonNull IIsolatedServiceCallback resultCallback) {
+ try {
+ WebTriggerInputParcel inputParcel =
+ Objects.requireNonNull(
+ params.getParcelable(
+ Constants.EXTRA_INPUT, WebTriggerInputParcel.class),
+ () ->
+ String.format(
+ "Missing '%s' from input params!",
+ Constants.EXTRA_INPUT));
+ WebTriggerInput input = new WebTriggerInput(inputParcel);
+ IDataAccessService binder = getDataAccessService(params);
+ UserData userData = params.getParcelable(Constants.EXTRA_USER_DATA, UserData.class);
+ RequestToken requestToken = new RequestToken(binder, null, null, userData);
+ IsolatedWorker isolatedWorker = IsolatedService.this.onRequest(requestToken);
+ isolatedWorker.onWebTrigger(
+ input,
+ new WrappedCallback<WebTriggerOutput, WebTriggerOutputParcel>(
+ 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.");
+ }
+ }
+ }
+
+ private void performOnTrainingExample(
+ @NonNull Bundle params, @NonNull IIsolatedServiceCallback resultCallback) {
+ try {
+ TrainingExamplesInputParcel inputParcel =
+ Objects.requireNonNull(
+ params.getParcelable(
+ Constants.EXTRA_INPUT, TrainingExamplesInputParcel.class),
+ () ->
+ String.format(
+ "Missing '%s' from input params!",
+ Constants.EXTRA_INPUT));
+ TrainingExamplesInput input = new TrainingExamplesInput(inputParcel);
+ IDataAccessService binder = getDataAccessService(params);
+ UserData userData = params.getParcelable(Constants.EXTRA_USER_DATA, UserData.class);
+ RequestToken requestToken = new RequestToken(binder, null, null, userData);
+ IsolatedWorker isolatedWorker = IsolatedService.this.onRequest(requestToken);
+ isolatedWorker.onTrainingExamples(
+ input,
+ new WrappedCallback<TrainingExamplesOutput, TrainingExamplesOutputParcel>(
+ resultCallback,
+ requestToken,
+ v ->
+ new TrainingExamplesOutputParcel.Builder()
+ .setTrainingExampleRecords(
+ new OdpParceledListSlice<
+ TrainingExampleRecord>(
+ v.getTrainingExampleRecords()))
+ .build()));
+ } 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.");
+ }
+ }
+ }
+
+ private void performOnWebViewEvent(
+ @NonNull Bundle params, @NonNull IIsolatedServiceCallback resultCallback) {
+ try {
+ EventInputParcel inputParcel =
+ Objects.requireNonNull(
+ params.getParcelable(Constants.EXTRA_INPUT, EventInputParcel.class),
+ () ->
+ String.format(
+ "Missing '%s' from input params!",
+ Constants.EXTRA_INPUT));
+ EventInput input = new EventInput(inputParcel);
+ IDataAccessService binder = getDataAccessService(params);
+ UserData userData = params.getParcelable(Constants.EXTRA_USER_DATA, UserData.class);
+ RequestToken requestToken = new RequestToken(binder, null, null, userData);
+ IsolatedWorker isolatedWorker = IsolatedService.this.onRequest(requestToken);
+ isolatedWorker.onEvent(
+ input,
+ new WrappedCallback<EventOutput, EventOutputParcel>(
+ 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.");
+ }
+ }
+ }
+
+ private void performRender(
+ @NonNull Bundle params, @NonNull IIsolatedServiceCallback resultCallback) {
+ try {
+ RenderInputParcel inputParcel =
+ Objects.requireNonNull(
+ params.getParcelable(
+ Constants.EXTRA_INPUT, RenderInputParcel.class),
+ () ->
+ String.format(
+ "Missing '%s' from input params!",
+ Constants.EXTRA_INPUT));
+ RenderInput input = new RenderInput(inputParcel);
+ Objects.requireNonNull(input.getRenderingConfig());
+ IDataAccessService binder = getDataAccessService(params);
+ RequestToken requestToken = new RequestToken(binder, null, null, null);
+ IsolatedWorker isolatedWorker = IsolatedService.this.onRequest(requestToken);
+ isolatedWorker.onRender(
+ input,
+ new WrappedCallback<RenderOutput, RenderOutputParcel>(
+ 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.");
+ }
+ }
+ }
+
+ private void performDownload(
+ @NonNull Bundle params, @NonNull IIsolatedServiceCallback resultCallback) {
+ try {
+ DownloadInputParcel inputParcel =
+ Objects.requireNonNull(
+ params.getParcelable(
+ Constants.EXTRA_INPUT, DownloadInputParcel.class),
+ () ->
+ String.format(
+ "Missing '%s' from input params!",
+ Constants.EXTRA_INPUT));
+ KeyValueStore downloadedContents =
+ new RemoteDataImpl(
+ IDataAccessService.Stub.asInterface(
+ Objects.requireNonNull(
+ inputParcel.getDataAccessServiceBinder(),
+ "Failed to get IDataAccessService binder from the input params!")));
+
+ DownloadCompletedInput input =
+ new DownloadCompletedInput.Builder()
+ .setDownloadedContents(downloadedContents)
+ .build();
+
+ IDataAccessService binder = getDataAccessService(params);
+
+ IFederatedComputeService fcBinder = getFederatedComputeService(params);
+ UserData userData = params.getParcelable(Constants.EXTRA_USER_DATA, UserData.class);
+ RequestToken requestToken = new RequestToken(binder, fcBinder, null, userData);
+ IsolatedWorker isolatedWorker = IsolatedService.this.onRequest(requestToken);
+ isolatedWorker.onDownloadCompleted(
+ input,
+ new WrappedCallback<DownloadCompletedOutput, DownloadCompletedOutputParcel>(
+ resultCallback,
+ requestToken,
+ 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.");
+ }
+ }
+ }
+
+ private static IIsolatedModelService getIsolatedModelService(@NonNull Bundle params) {
+ IIsolatedModelService modelServiceBinder =
+ IIsolatedModelService.Stub.asInterface(
+ Objects.requireNonNull(
+ params.getBinder(Constants.EXTRA_MODEL_SERVICE_BINDER),
+ () ->
+ String.format(
+ "Missing '%s' from input params!",
+ Constants.EXTRA_MODEL_SERVICE_BINDER)));
+ Objects.requireNonNull(
+ modelServiceBinder,
+ "Failed to get IIsolatedModelService binder from the input params!");
+ return modelServiceBinder;
+ }
+
+ private static IFederatedComputeService getFederatedComputeService(@NonNull Bundle params) {
+ IFederatedComputeService fcBinder =
+ IFederatedComputeService.Stub.asInterface(
+ Objects.requireNonNull(
+ params.getBinder(
+ Constants.EXTRA_FEDERATED_COMPUTE_SERVICE_BINDER),
+ () ->
+ String.format(
+ "Missing '%s' from input params!",
+ Constants
+ .EXTRA_FEDERATED_COMPUTE_SERVICE_BINDER)));
+ Objects.requireNonNull(
+ fcBinder,
+ "Failed to get IFederatedComputeService binder from the input params!");
+ return fcBinder;
+ }
+
+ private static IDataAccessService getDataAccessService(@NonNull Bundle params) {
+ IDataAccessService binder =
+ IDataAccessService.Stub.asInterface(
+ Objects.requireNonNull(
+ params.getBinder(Constants.EXTRA_DATA_ACCESS_SERVICE_BINDER),
+ () ->
+ String.format(
+ "Missing '%s' from input params!",
+ Constants.EXTRA_DATA_ACCESS_SERVICE_BINDER)));
+ Objects.requireNonNull(
+ binder, "Failed to get IDataAccessService binder from the input params!");
+ return binder;
+ }
+
+ private void performExecute(
+ @NonNull Bundle params, @NonNull IIsolatedServiceCallback resultCallback) {
+ try {
+ ExecuteInputParcel inputParcel =
+ Objects.requireNonNull(
+ params.getParcelable(
+ Constants.EXTRA_INPUT, ExecuteInputParcel.class),
+ () ->
+ String.format(
+ "Missing '%s' from input params!",
+ Constants.EXTRA_INPUT));
+ ExecuteInput input = new ExecuteInput(inputParcel);
+ Objects.requireNonNull(
+ input.getAppPackageName(),
+ "Failed to get AppPackageName from the input params!");
+ IDataAccessService binder = getDataAccessService(params);
+ IFederatedComputeService fcBinder = getFederatedComputeService(params);
+ IIsolatedModelService modelServiceBinder = getIsolatedModelService(params);
+ UserData userData = params.getParcelable(Constants.EXTRA_USER_DATA, UserData.class);
+ RequestToken requestToken =
+ new RequestToken(binder, fcBinder, modelServiceBinder, userData);
+ IsolatedWorker isolatedWorker = IsolatedService.this.onRequest(requestToken);
+ isolatedWorker.onExecute(
+ input,
+ new WrappedCallback<ExecuteOutput, ExecuteOutputParcel>(
+ 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.");
+ }
+ }
+ }
+ }
+
+ private static class WrappedCallback<T, U extends Parcelable>
+ implements OutcomeReceiver<T, IsolatedServiceException> {
+ @NonNull private final IIsolatedServiceCallback mCallback;
+ @NonNull private final RequestToken mRequestToken;
+ @NonNull private final Function<T, U> mConverter;
+
+ WrappedCallback(
+ IIsolatedServiceCallback callback,
+ RequestToken requestToken,
+ Function<T, U> converter) {
+ mCallback = Objects.requireNonNull(callback);
+ mRequestToken = Objects.requireNonNull(requestToken);
+ mConverter = Objects.requireNonNull(converter);
+ }
+
+ @Override
+ public void onResult(T result) {
+ 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);
+ }
+ } else {
+ Bundle bundle = new Bundle();
+ U wrappedResult = mConverter.apply(result);
+ bundle.putParcelable(Constants.EXTRA_RESULT, wrappedResult);
+ bundle.putParcelable(Constants.EXTRA_CALLEE_METADATA,
+ new CalleeMetadata.Builder()
+ .setElapsedTimeMillis(elapsedTimeMillis)
+ .build());
+ try {
+ mCallback.onSuccess(bundle);
+ } catch (RemoteException e) {
+ sLogger.w(TAG + ": Callback failed.", e);
+ }
+ }
+ }
+
+ @Override
+ public void onError(IsolatedServiceException e) {
+ try {
+ // TODO(b/324478256): Log and report the error code from e.
+ mCallback.onError(Constants.STATUS_SERVICE_FAILED, e.getErrorCode());
+ } catch (RemoteException re) {
+ sLogger.w(TAG + ": Callback failed.", re);
+ }
+ }
+ }
+}
diff --git a/android-35/android/adservices/ondevicepersonalization/IsolatedServiceException.java b/android-35/android/adservices/ondevicepersonalization/IsolatedServiceException.java
new file mode 100644
index 0000000..b280b10
--- /dev/null
+++ b/android-35/android/adservices/ondevicepersonalization/IsolatedServiceException.java
@@ -0,0 +1,53 @@
+/*
+ * 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.IntRange;
+
+import com.android.adservices.ondevicepersonalization.flags.Flags;
+
+/**
+ * A class that an {@link IsolatedService} can use to signal a failure in handling a request and
+ * return an error to be logged and aggregated. The error is not reported to the app that invoked
+ * the {@link IsolatedService} in order to prevent data leakage from the {@link IsolatedService} to
+ * an app. The platform does not interpret the error code, it only logs and aggregates it.
+ */
+@FlaggedApi(Flags.FLAG_ON_DEVICE_PERSONALIZATION_APIS_ENABLED)
+public final class IsolatedServiceException extends Exception {
+ @IntRange(from = 1, to = 127) private final int mErrorCode;
+
+ /**
+ * 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}.
+ */
+ public IsolatedServiceException(@IntRange(from = 1, to = 127) int errorCode) {
+ super("IsolatedServiceException: Error " + errorCode);
+ mErrorCode = errorCode;
+ }
+
+ /**
+ * Returns the error code for this exception.
+ * @hide
+ */
+ public @IntRange(from = 1, to = 127) int getErrorCode() {
+ return mErrorCode;
+ }
+}
diff --git a/android-35/android/adservices/ondevicepersonalization/IsolatedWorker.java b/android-35/android/adservices/ondevicepersonalization/IsolatedWorker.java
new file mode 100644
index 0000000..1b37b69
--- /dev/null
+++ b/android-35/android/adservices/ondevicepersonalization/IsolatedWorker.java
@@ -0,0 +1,157 @@
+/*
+ * 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;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.os.OutcomeReceiver;
+
+import com.android.adservices.ondevicepersonalization.flags.Flags;
+
+/**
+ * Interface with methods that need to be implemented to handle requests from the
+ * OnDevicePersonalization service to an {@link IsolatedService}. The {@link IsolatedService}
+ * creates an instance of {@link IsolatedWorker} on each request and calls one of the methods
+ * below, depending the type of the request. The {@link IsolatedService} calls the method on a
+ * Binder thread and the {@link IsolatedWorker} should offload long running operations to a
+ * worker thread. The {@link IsolatedWorker} should use the {@code receiver} parameter of each
+ * method to return results. If any of these methods throws a {@link RuntimeException}, the
+ * platform treats it as an unrecoverable error in the {@link IsolatedService} and ends processing
+ * the request.
+ */
+@FlaggedApi(Flags.FLAG_ON_DEVICE_PERSONALIZATION_APIS_ENABLED)
+public interface IsolatedWorker {
+
+ /**
+ * Handles a request from an app. This method is called when an app calls {@code
+ * OnDevicePersonalizationManager#execute(ComponentName, PersistableBundle,
+ * java.util.concurrent.Executor, OutcomeReceiver)} that refers to a named
+ * {@link IsolatedService}.
+ *
+ * @param input Request Parameters from the calling app.
+ * @param receiver Callback that receives the result {@link ExecuteOutput} or an
+ * {@link IsolatedServiceException}. If this method throws a {@link RuntimeException} or
+ * returns either {@code null} or {@link IsolatedServiceException}, the error is indicated
+ * to the calling app as an {@link OnDevicePersonalizationException} with error code
+ * {@link OnDevicePersonalizationException#ERROR_ISOLATED_SERVICE_FAILED}. To avoid leaking
+ * private data to the calling app, more detailed errors are not reported to the caller.
+ * If the {@link IsolatedService} needs to report additional data beyond the error code to
+ * its backend servers, it should populate the logging fields in {@link ExecuteOutput} with
+ * the additional error data for logging, and rely on Federated Analytics for the stats.
+ */
+ default void onExecute(
+ @NonNull ExecuteInput input,
+ @NonNull OutcomeReceiver<ExecuteOutput, IsolatedServiceException> receiver) {
+ receiver.onResult(new ExecuteOutput.Builder().build());
+ }
+
+ /**
+ * Handles a completed download. The platform downloads content using the parameters defined in
+ * the package manifest of the {@link IsolatedService}, calls this function after the download
+ * is complete, and updates the REMOTE_DATA table from
+ * {@link IsolatedService#getRemoteData(RequestToken)} with the result of this method.
+ *
+ * @param input Download handler parameters.
+ * @param receiver Callback that receives the result {@link DownloadCompletedOutput} or an
+ * {@link IsolatedServiceException}.
+ * <p>If this method returns a {@code null} result or exception via the callback, or
+ * throws a {@link RuntimeException}, no updates are made to the REMOTE_DATA table.
+ */
+ default void onDownloadCompleted(
+ @NonNull DownloadCompletedInput input,
+ @NonNull OutcomeReceiver<DownloadCompletedOutput, IsolatedServiceException> receiver) {
+ receiver.onResult(new DownloadCompletedOutput.Builder().build());
+ }
+
+ /**
+ * Generates HTML for the results that were returned as a result of
+ * {@link #onExecute(ExecuteInput, android.os.OutcomeReceiver)}. Called when a client app calls
+ * {@link OnDevicePersonalizationManager#requestSurfacePackage(SurfacePackageToken, IBinder, int, int, int, java.util.concurrent.Executor, OutcomeReceiver)}.
+ * The platform will render this HTML in an {@link android.webkit.WebView} inside a fenced
+ * frame.
+ *
+ * @param input Parameters for the render request.
+ * @param receiver Callback that receives the result {@link RenderOutput} or an
+ * {@link IsolatedServiceException}.
+ * <p>If this method returns a {@code null} result or exception via the callback, or
+ * throws a {@link RuntimeException}, the error is also reported to calling
+ * apps as an {@link OnDevicePersonalizationException} with error code {@link
+ * OnDevicePersonalizationException#ERROR_ISOLATED_SERVICE_FAILED}.
+ */
+ default void onRender(
+ @NonNull RenderInput input,
+ @NonNull OutcomeReceiver<RenderOutput, IsolatedServiceException> receiver) {
+ receiver.onResult(new RenderOutput.Builder().build());
+ }
+
+ /**
+ * Handles an event triggered by a request to a platform-provided tracking URL {@link
+ * EventUrlProvider} that was embedded in the HTML output returned by
+ * {@link #onRender(RenderInput, android.os.OutcomeReceiver)}. The platform updates the EVENTS table with
+ * {@link EventOutput#getEventLogRecord()}.
+ *
+ * @param input The parameters needed to compute event data.
+ * @param receiver Callback that receives the result {@link EventOutput} or an
+ * {@link IsolatedServiceException}.
+ * <p>If this method returns a {@code null} result or exception via the callback, or
+ * throws a {@link RuntimeException}, no data is written to the EVENTS table.
+ */
+ default void onEvent(
+ @NonNull EventInput input,
+ @NonNull OutcomeReceiver<EventOutput, IsolatedServiceException> receiver) {
+ receiver.onResult(new EventOutput.Builder().build());
+ }
+
+ /**
+ * Generate a list of training examples used for federated compute job. The platform will call
+ * this function when a federated compute job starts. The federated compute job is scheduled by
+ * an app through {@link FederatedComputeScheduler#schedule}.
+ *
+ * @param input The parameters needed to generate the training example.
+ * @param receiver Callback that receives the result {@link TrainingExamplesOutput} or an
+ * {@link IsolatedServiceException}.
+ * <p>If this method returns a {@code null} result or exception via the callback, or
+ * throws a {@link RuntimeException}, no training examples is produced for this
+ * training session.
+ */
+ default void onTrainingExamples(
+ @NonNull TrainingExamplesInput input,
+ @NonNull OutcomeReceiver<TrainingExamplesOutput, IsolatedServiceException> receiver) {
+ receiver.onResult(new TrainingExamplesOutput.Builder().build());
+ }
+
+ /**
+ * Handles a Web Trigger event from a browser. A Web Trigger event occurs when a browser
+ * registers a web trigger event with the OS using the <a href="https://github.com/WICG/attribution-reporting-api">
+ * Attribution and Reporting API</a>. If the data in the web trigger payload indicates that the
+ * event should be forwarded to an {@link IsolatedService}, the platform will call this function
+ * with the web trigger data.
+ *
+ * @param input The parameters needed to process Web Trigger event.
+ * @param receiver Callback that receives the result {@link WebTriggerOutput} or an
+ * {@link IsolatedServiceException}. Should be called with a
+ * {@link WebTriggerOutput} object populated with a set of records to be written to the
+ * REQUESTS or EVENTS tables.
+ * <p>If this method returns a {@code null} result or exception via the callback, or
+ * throws a {@link RuntimeException}, no data is written to the REQUESTS orEVENTS tables.
+ */
+ default void onWebTrigger(
+ @NonNull WebTriggerInput input,
+ @NonNull OutcomeReceiver<WebTriggerOutput, IsolatedServiceException> receiver) {
+ receiver.onResult(new WebTriggerOutput.Builder().build());
+ }
+}
diff --git a/android-35/android/adservices/ondevicepersonalization/JoinedLogRecord.java b/android-35/android/adservices/ondevicepersonalization/JoinedLogRecord.java
new file mode 100644
index 0000000..451c35a
--- /dev/null
+++ b/android-35/android/adservices/ondevicepersonalization/JoinedLogRecord.java
@@ -0,0 +1,365 @@
+/*
+ * 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.IntRange;
+import android.annotation.Nullable;
+import android.content.ContentValues;
+import android.os.Parcelable;
+
+import com.android.ondevicepersonalization.internal.util.AnnotationValidations;
+import com.android.ondevicepersonalization.internal.util.DataClass;
+
+/**
+ * Input data to create example from. Represents a single joined log record.
+ *
+ * @hide
+ */
+@DataClass(genBuilder = true, genEqualsHashCode = true)
+public class JoinedLogRecord implements Parcelable {
+ /** Time of the request in milliseconds */
+ private final long mRequestTimeMillis;
+
+ /** Time of the event in milliseconds */
+ private final long mEventTimeMillis;
+
+ /**
+ * The service-assigned type that identifies the event data. Must be >0 and <128. If type is 0,
+ * it is an Request-only row with no associated event.
+ */
+ @IntRange(from = 0, to = 127)
+ private final int mType;
+
+ /** Request data logged in a {@link RequestLogRecord} */
+ @Nullable private ContentValues mRequestData = null;
+
+ /** Event data logged in an {@link EventLogRecord} */
+ @Nullable private ContentValues mEventData = null;
+
+
+
+ // 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/JoinedLogRecord.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 */ JoinedLogRecord(
+ long requestTimeMillis,
+ long eventTimeMillis,
+ @IntRange(from = 0, to = 127) int type,
+ @Nullable ContentValues requestData,
+ @Nullable ContentValues eventData) {
+ this.mRequestTimeMillis = requestTimeMillis;
+ this.mEventTimeMillis = eventTimeMillis;
+ this.mType = type;
+ AnnotationValidations.validate(
+ IntRange.class, null, mType,
+ "from", 0,
+ "to", 127);
+ this.mRequestData = requestData;
+ this.mEventData = eventData;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * Time of the request in milliseconds
+ */
+ @DataClass.Generated.Member
+ public long getRequestTimeMillis() {
+ return mRequestTimeMillis;
+ }
+
+ /**
+ * Time of the event in milliseconds
+ */
+ @DataClass.Generated.Member
+ public long getEventTimeMillis() {
+ return mEventTimeMillis;
+ }
+
+ /**
+ * The service-assigned type that identifies the event data. Must be >0 and <128. If type is 0,
+ * it is an Request-only row with no associated event.
+ */
+ @DataClass.Generated.Member
+ public @IntRange(from = 0, to = 127) int getType() {
+ return mType;
+ }
+
+ /**
+ * Request data logged in a {@link RequestLogRecord}
+ */
+ @DataClass.Generated.Member
+ public @Nullable ContentValues getRequestData() {
+ return mRequestData;
+ }
+
+ /**
+ * Event data logged in an {@link EventLogRecord}
+ */
+ @DataClass.Generated.Member
+ public @Nullable ContentValues getEventData() {
+ return mEventData;
+ }
+
+ @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(JoinedLogRecord other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ JoinedLogRecord that = (JoinedLogRecord) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && mRequestTimeMillis == that.mRequestTimeMillis
+ && mEventTimeMillis == that.mEventTimeMillis
+ && mType == that.mType
+ && java.util.Objects.equals(mRequestData, that.mRequestData)
+ && java.util.Objects.equals(mEventData, that.mEventData);
+ }
+
+ @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 + Long.hashCode(mRequestTimeMillis);
+ _hash = 31 * _hash + Long.hashCode(mEventTimeMillis);
+ _hash = 31 * _hash + mType;
+ _hash = 31 * _hash + java.util.Objects.hashCode(mRequestData);
+ _hash = 31 * _hash + java.util.Objects.hashCode(mEventData);
+ return _hash;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@android.annotation.NonNull android.os.Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ byte flg = 0;
+ if (mRequestData != null) flg |= 0x8;
+ if (mEventData != null) flg |= 0x10;
+ dest.writeByte(flg);
+ dest.writeLong(mRequestTimeMillis);
+ dest.writeLong(mEventTimeMillis);
+ dest.writeInt(mType);
+ if (mRequestData != null) dest.writeTypedObject(mRequestData, flags);
+ if (mEventData != null) dest.writeTypedObject(mEventData, flags);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ protected JoinedLogRecord(@android.annotation.NonNull android.os.Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ byte flg = in.readByte();
+ long requestTimeMillis = in.readLong();
+ long eventTimeMillis = in.readLong();
+ int type = in.readInt();
+ ContentValues requestData = (flg & 0x8) == 0 ? null : (ContentValues) in.readTypedObject(ContentValues.CREATOR);
+ ContentValues eventData = (flg & 0x10) == 0 ? null : (ContentValues) in.readTypedObject(ContentValues.CREATOR);
+
+ this.mRequestTimeMillis = requestTimeMillis;
+ this.mEventTimeMillis = eventTimeMillis;
+ this.mType = type;
+ AnnotationValidations.validate(
+ IntRange.class, null, mType,
+ "from", 0,
+ "to", 127);
+ this.mRequestData = requestData;
+ this.mEventData = eventData;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @android.annotation.NonNull Parcelable.Creator<JoinedLogRecord> CREATOR
+ = new Parcelable.Creator<JoinedLogRecord>() {
+ @Override
+ public JoinedLogRecord[] newArray(int size) {
+ return new JoinedLogRecord[size];
+ }
+
+ @Override
+ public JoinedLogRecord createFromParcel(@android.annotation.NonNull android.os.Parcel in) {
+ return new JoinedLogRecord(in);
+ }
+ };
+
+ /**
+ * A builder for {@link JoinedLogRecord}
+ * @hide
+ */
+ @SuppressWarnings("WeakerAccess")
+ @DataClass.Generated.Member
+ public static class Builder {
+
+ private long mRequestTimeMillis;
+ private long mEventTimeMillis;
+ private @IntRange(from = 0, to = 127) int mType;
+ private @Nullable ContentValues mRequestData;
+ private @Nullable ContentValues mEventData;
+
+ private long mBuilderFieldsSet = 0L;
+
+ public Builder() {
+ }
+
+ /**
+ * Creates a new Builder.
+ *
+ * @param requestTimeMillis
+ * Time of the request in milliseconds
+ * @param eventTimeMillis
+ * Time of the event in milliseconds
+ * @param type
+ * The service-assigned type that identifies the event data. Must be >0 and <128. If type is 0,
+ * it is an Request-only row with no associated event.
+ */
+ public Builder(
+ long requestTimeMillis,
+ long eventTimeMillis,
+ @IntRange(from = 0, to = 127) int type) {
+ mRequestTimeMillis = requestTimeMillis;
+ mEventTimeMillis = eventTimeMillis;
+ mType = type;
+ AnnotationValidations.validate(
+ IntRange.class, null, mType,
+ "from", 0,
+ "to", 127);
+ }
+
+ /**
+ * Time of the request in milliseconds
+ */
+ @DataClass.Generated.Member
+ public @android.annotation.NonNull Builder setRequestTimeMillis(long value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x1;
+ mRequestTimeMillis = value;
+ return this;
+ }
+
+ /**
+ * Time of the event in milliseconds
+ */
+ @DataClass.Generated.Member
+ public @android.annotation.NonNull Builder setEventTimeMillis(long value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x2;
+ mEventTimeMillis = value;
+ return this;
+ }
+
+ /**
+ * The service-assigned type that identifies the event data. Must be >0 and <128. If type is 0,
+ * it is an Request-only row with no associated event.
+ */
+ @DataClass.Generated.Member
+ public @android.annotation.NonNull Builder setType(@IntRange(from = 0, to = 127) int value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x4;
+ mType = value;
+ return this;
+ }
+
+ /**
+ * Request data logged in a {@link RequestLogRecord}
+ */
+ @DataClass.Generated.Member
+ public @android.annotation.NonNull Builder setRequestData(@android.annotation.NonNull ContentValues value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x8;
+ mRequestData = value;
+ return this;
+ }
+
+ /**
+ * Event data logged in an {@link EventLogRecord}
+ */
+ @DataClass.Generated.Member
+ public @android.annotation.NonNull Builder setEventData(@android.annotation.NonNull ContentValues value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x10;
+ mEventData = value;
+ return this;
+ }
+
+ /** Builds the instance. This builder should not be touched after calling this! */
+ public @android.annotation.NonNull JoinedLogRecord build() {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x20; // Mark builder used
+
+ if ((mBuilderFieldsSet & 0x8) == 0) {
+ mRequestData = null;
+ }
+ if ((mBuilderFieldsSet & 0x10) == 0) {
+ mEventData = null;
+ }
+ JoinedLogRecord o = new JoinedLogRecord(
+ mRequestTimeMillis,
+ mEventTimeMillis,
+ mType,
+ mRequestData,
+ mEventData);
+ return o;
+ }
+
+ private void checkNotUsed() {
+ if ((mBuilderFieldsSet & 0x20) != 0) {
+ throw new IllegalStateException(
+ "This Builder should not be reused. Use a new Builder instance instead");
+ }
+ }
+ }
+
+ @DataClass.Generated(
+ time = 1695413878624L,
+ codegenVersion = "1.0.23",
+ sourceFile = "packages/modules/OnDevicePersonalization/framework/java/android/adservices/ondevicepersonalization/JoinedLogRecord.java",
+ inputSignatures = "private final long mRequestTimeMillis\nprivate final long mEventTimeMillis\nprivate final @android.annotation.IntRange int mType\nprivate @android.annotation.Nullable android.content.ContentValues mRequestData\nprivate @android.annotation.Nullable android.content.ContentValues mEventData\nclass JoinedLogRecord extends java.lang.Object implements [android.os.Parcelable]\[email protected](genBuilder=true, genEqualsHashCode=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/android-35/android/adservices/ondevicepersonalization/KeyValueStore.java b/android-35/android/adservices/ondevicepersonalization/KeyValueStore.java
new file mode 100644
index 0000000..198af7c
--- /dev/null
+++ b/android-35/android/adservices/ondevicepersonalization/KeyValueStore.java
@@ -0,0 +1,63 @@
+/*
+ * 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;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.WorkerThread;
+
+import com.android.adservices.ondevicepersonalization.flags.Flags;
+
+import java.util.Set;
+
+/**
+ * An interface to a read-only key-value store.
+ *
+ * Used as a Data Access Object for the REMOTE_DATA table.
+ *
+ * @see IsolatedService#getRemoteData(RequestToken)
+ *
+ */
+@FlaggedApi(Flags.FLAG_ON_DEVICE_PERSONALIZATION_APIS_ENABLED)
+public interface KeyValueStore {
+ /**
+ * Looks up a key in a read-only store.
+ *
+ * @param key The key to look up.
+ * @return the value to which the specified key is mapped,
+ * or null if there contains no mapping for the key.
+ *
+ */
+ @WorkerThread
+ @Nullable byte[] get(@NonNull String key);
+
+ /**
+ * Returns a Set view of the keys contained in the REMOTE_DATA table.
+ */
+ @WorkerThread
+ @NonNull Set<String> keySet();
+
+ /**
+ * Returns the table id {@link ModelId.TableId} of KeyValueStore implementation.
+ *
+ * @hide
+ */
+ default int getTableId(){
+ return 0;
+ }
+}
diff --git a/android-35/android/adservices/ondevicepersonalization/LocalDataImpl.java b/android-35/android/adservices/ondevicepersonalization/LocalDataImpl.java
new file mode 100644
index 0000000..e09c4c4
--- /dev/null
+++ b/android-35/android/adservices/ondevicepersonalization/LocalDataImpl.java
@@ -0,0 +1,168 @@
+/*
+ * 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;
+
+import android.adservices.ondevicepersonalization.aidl.IDataAccessService;
+import android.adservices.ondevicepersonalization.aidl.IDataAccessServiceCallback;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Bundle;
+import android.os.RemoteException;
+
+import com.android.ondevicepersonalization.internal.util.ByteArrayParceledSlice;
+import com.android.ondevicepersonalization.internal.util.LoggerFactory;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+
+/** @hide */
+public class LocalDataImpl implements MutableKeyValueStore {
+ private static final String TAG = "LocalDataImpl";
+ private static final LoggerFactory.Logger sLogger = LoggerFactory.getLogger();
+ @NonNull
+ IDataAccessService mDataAccessService;
+
+ /** @hide */
+ public LocalDataImpl(@NonNull IDataAccessService binder) {
+ mDataAccessService = Objects.requireNonNull(binder);
+ }
+
+ @Override @Nullable
+ public byte[] get(@NonNull String key) {
+ final long startTimeMillis = System.currentTimeMillis();
+ Objects.requireNonNull(key);
+ Bundle params = new Bundle();
+ params.putString(Constants.EXTRA_LOOKUP_KEYS, key);
+ return handleLookupRequest(
+ Constants.DATA_ACCESS_OP_LOCAL_DATA_LOOKUP, params,
+ Constants.API_NAME_LOCAL_DATA_GET, startTimeMillis);
+ }
+
+ @Override @Nullable
+ public byte[] put(@NonNull String key, byte[] value) {
+ final long startTimeMillis = System.currentTimeMillis();
+ Objects.requireNonNull(key);
+ Bundle params = new Bundle();
+ params.putString(Constants.EXTRA_LOOKUP_KEYS, key);
+ params.putParcelable(Constants.EXTRA_VALUE, new ByteArrayParceledSlice(value));
+ return handleLookupRequest(
+ Constants.DATA_ACCESS_OP_LOCAL_DATA_PUT, params,
+ Constants.API_NAME_LOCAL_DATA_PUT, startTimeMillis);
+ }
+
+ @Override @Nullable
+ public byte[] remove(@NonNull String key) {
+ final long startTimeMillis = System.currentTimeMillis();
+ Objects.requireNonNull(key);
+ Bundle params = new Bundle();
+ params.putString(Constants.EXTRA_LOOKUP_KEYS, key);
+ return handleLookupRequest(
+ Constants.DATA_ACCESS_OP_LOCAL_DATA_REMOVE, params,
+ Constants.API_NAME_LOCAL_DATA_REMOVE, startTimeMillis);
+ }
+
+ private byte[] handleLookupRequest(
+ int op, Bundle params, int apiName, long startTimeMillis) {
+ int responseCode = Constants.STATUS_SUCCESS;
+ try {
+ Bundle result = handleAsyncRequest(op, params);
+ ByteArrayParceledSlice data = result.getParcelable(
+ Constants.EXTRA_RESULT, ByteArrayParceledSlice.class);
+ if (null == data) {
+ return null;
+ }
+ return data.getByteArray();
+ } catch (RuntimeException e) {
+ responseCode = Constants.STATUS_INTERNAL_ERROR;
+ throw e;
+ } finally {
+ try {
+ mDataAccessService.logApiCallStats(
+ apiName,
+ System.currentTimeMillis() - startTimeMillis,
+ responseCode);
+ } catch (Exception e) {
+ sLogger.d(e, TAG + ": failed to log metrics");
+ }
+ }
+ }
+
+ @Override @NonNull
+ public Set<String> keySet() {
+ final long startTimeMillis = System.currentTimeMillis();
+ int responseCode = Constants.STATUS_SUCCESS;
+ try {
+ Bundle result = handleAsyncRequest(Constants.DATA_ACCESS_OP_LOCAL_DATA_KEYSET,
+ Bundle.EMPTY);
+ HashSet<String> resultSet =
+ result.getSerializable(Constants.EXTRA_RESULT, HashSet.class);
+ if (null == resultSet) {
+ return Collections.emptySet();
+ }
+ return resultSet;
+ } catch (RuntimeException e) {
+ responseCode = Constants.STATUS_INTERNAL_ERROR;
+ throw e;
+ } finally {
+ try {
+ mDataAccessService.logApiCallStats(
+ Constants.API_NAME_LOCAL_DATA_KEYSET,
+ System.currentTimeMillis() - startTimeMillis,
+ responseCode);
+ } catch (Exception e) {
+ sLogger.d(e, TAG + ": failed to log metrics");
+ }
+ }
+ }
+
+ @Override
+ public int getTableId() {
+ return ModelId.TABLE_ID_LOCAL_DATA;
+ }
+
+ private Bundle handleAsyncRequest(int op, Bundle params) {
+ try {
+ BlockingQueue<Bundle> asyncResult = new ArrayBlockingQueue<>(1);
+ mDataAccessService.onRequest(
+ op,
+ params,
+ new IDataAccessServiceCallback.Stub() {
+ @Override
+ public void onSuccess(@NonNull Bundle result) {
+ if (result != null) {
+ asyncResult.add(result);
+ } else {
+ asyncResult.add(Bundle.EMPTY);
+ }
+ }
+
+ @Override
+ public void onError(int errorCode) {
+ asyncResult.add(Bundle.EMPTY);
+ }
+ });
+ return asyncResult.take();
+ } catch (InterruptedException | RemoteException e) {
+ sLogger.e(TAG + ": Failed to retrieve result from localData", e);
+ throw new IllegalStateException(e);
+ }
+ }
+}
diff --git a/android-35/android/adservices/ondevicepersonalization/LogReader.java b/android-35/android/adservices/ondevicepersonalization/LogReader.java
new file mode 100644
index 0000000..04b603e
--- /dev/null
+++ b/android-35/android/adservices/ondevicepersonalization/LogReader.java
@@ -0,0 +1,185 @@
+/*
+ * 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;
+
+import android.adservices.ondevicepersonalization.aidl.IDataAccessService;
+import android.adservices.ondevicepersonalization.aidl.IDataAccessServiceCallback;
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.WorkerThread;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.os.RemoteException;
+
+import com.android.adservices.ondevicepersonalization.flags.Flags;
+import com.android.ondevicepersonalization.internal.util.LoggerFactory;
+import com.android.ondevicepersonalization.internal.util.OdpParceledListSlice;
+
+import java.time.Instant;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+
+/**
+ * An interface to a read logs from REQUESTS and EVENTS
+ *
+ * Used as a Data Access Object for the REQUESTS and EVENTS table.
+ *
+ * @see IsolatedService#getLogReader(RequestToken)
+ *
+ */
+@FlaggedApi(Flags.FLAG_ON_DEVICE_PERSONALIZATION_APIS_ENABLED)
+public class LogReader {
+ private static final String TAG = "LogReader";
+ private static final LoggerFactory.Logger sLogger = LoggerFactory.getLogger();
+
+ @NonNull
+ private final IDataAccessService mDataAccessService;
+
+ /** @hide */
+ public LogReader(@NonNull IDataAccessService binder) {
+ mDataAccessService = Objects.requireNonNull(binder);
+ }
+
+
+ /**
+ * Retrieves a List of RequestLogRecords written by this IsolatedService within
+ * the specified time range.
+ */
+ @WorkerThread
+ @NonNull
+ public List<RequestLogRecord> getRequests(
+ @NonNull Instant startTime, @NonNull Instant endTime) {
+ final long apiStartTimeMillis = System.currentTimeMillis();
+ int responseCode = Constants.STATUS_SUCCESS;
+ long startTimeMillis = startTime.toEpochMilli();
+ long endTimeMillis = endTime.toEpochMilli();
+ if (endTimeMillis <= startTimeMillis) {
+ throw new IllegalArgumentException(
+ "endTimeMillis must be greater than startTimeMillis");
+ }
+ if (startTimeMillis < 0) {
+ throw new IllegalArgumentException("startTimeMillis must be greater than 0");
+ }
+ try {
+ Bundle params = new Bundle();
+ params.putLongArray(Constants.EXTRA_LOOKUP_KEYS,
+ new long[]{startTimeMillis, endTimeMillis});
+ OdpParceledListSlice<RequestLogRecord> result =
+ handleListLookupRequest(Constants.DATA_ACCESS_OP_GET_REQUESTS, params);
+ return result.getList();
+ } catch (RuntimeException e) {
+ responseCode = Constants.STATUS_INTERNAL_ERROR;
+ throw e;
+ } finally {
+ logApiCallStats(
+ Constants.API_NAME_LOG_READER_GET_REQUESTS,
+ System.currentTimeMillis() - apiStartTimeMillis,
+ responseCode);
+ }
+ }
+
+ /**
+ * Retrieves a List of EventLogRecord with its corresponding RequestLogRecord written by this
+ * IsolatedService within the specified time range.
+ */
+ @WorkerThread
+ @NonNull
+ public List<EventLogRecord> getJoinedEvents(
+ @NonNull Instant startTime, @NonNull Instant endTime) {
+ final long apiStartTimeMillis = System.currentTimeMillis();
+ int responseCode = Constants.STATUS_SUCCESS;
+ long startTimeMillis = startTime.toEpochMilli();
+ long endTimeMillis = endTime.toEpochMilli();
+ if (endTimeMillis <= startTimeMillis) {
+ throw new IllegalArgumentException(
+ "endTimeMillis must be greater than startTimeMillis");
+ }
+ if (startTimeMillis < 0) {
+ throw new IllegalArgumentException("startTimeMillis must be greater than 0");
+ }
+ try {
+ Bundle params = new Bundle();
+ params.putLongArray(Constants.EXTRA_LOOKUP_KEYS,
+ new long[]{startTimeMillis, endTimeMillis});
+ OdpParceledListSlice<EventLogRecord> result =
+ handleListLookupRequest(Constants.DATA_ACCESS_OP_GET_JOINED_EVENTS, params);
+ return result.getList();
+ } catch (RuntimeException e) {
+ responseCode = Constants.STATUS_INTERNAL_ERROR;
+ throw e;
+ } finally {
+ logApiCallStats(
+ Constants.API_NAME_LOG_READER_GET_JOINED_EVENTS,
+ System.currentTimeMillis() - apiStartTimeMillis,
+ responseCode);
+ }
+ }
+
+ private Bundle handleAsyncRequest(int op, Bundle params) {
+ try {
+ BlockingQueue<Bundle> asyncResult = new ArrayBlockingQueue<>(1);
+ mDataAccessService.onRequest(
+ op,
+ params,
+ new IDataAccessServiceCallback.Stub() {
+ @Override
+ public void onSuccess(@NonNull Bundle result) {
+ if (result != null) {
+ asyncResult.add(result);
+ } else {
+ asyncResult.add(Bundle.EMPTY);
+ }
+ }
+
+ @Override
+ public void onError(int errorCode) {
+ asyncResult.add(Bundle.EMPTY);
+ }
+ });
+ return asyncResult.take();
+ } catch (InterruptedException | RemoteException e) {
+ sLogger.e(TAG + ": Failed to retrieve result", e);
+ throw new IllegalStateException(e);
+ }
+ }
+
+ private <T extends Parcelable> OdpParceledListSlice<T> handleListLookupRequest(int op,
+ Bundle params) {
+ Bundle result = handleAsyncRequest(op, params);
+ try {
+ OdpParceledListSlice<T> data = result.getParcelable(
+ Constants.EXTRA_RESULT, OdpParceledListSlice.class);
+ if (null == data) {
+ sLogger.e(TAG + ": No EXTRA_RESULT was present in bundle");
+ throw new IllegalStateException("Bundle missing EXTRA_RESULT.");
+ }
+ return data;
+ } catch (ClassCastException e) {
+ throw new IllegalStateException("Failed to retrieve parceled list");
+ }
+ }
+
+ private void logApiCallStats(int apiName, long duration, int responseCode) {
+ try {
+ mDataAccessService.logApiCallStats(apiName, duration, responseCode);
+ } catch (Exception e) {
+ sLogger.d(e, TAG + ": failed to log metrics");
+ }
+ }
+}
diff --git a/android-35/android/adservices/ondevicepersonalization/MeasurementWebTriggerEventParams.java b/android-35/android/adservices/ondevicepersonalization/MeasurementWebTriggerEventParams.java
new file mode 100644
index 0000000..118c422
--- /dev/null
+++ b/android-35/android/adservices/ondevicepersonalization/MeasurementWebTriggerEventParams.java
@@ -0,0 +1,340 @@
+/*
+ * 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 android.annotation.SystemApi;
+import android.content.ComponentName;
+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;
+
+// TODO(b/301732670): Add link to documentation describing the format of the ODP-specific
+// attribution data that the server is expected to return.
+/**
+ * A class that contains Web Trigger Event data sent from the
+ * <a href="https://developer.android.com/design-for-safety/privacy-sandbox/guides/attribution">
+ * Measurement API</a> to the OnDevicePersonalization service when the browser registers a web
+ * trigger URL with the native OS attribution API as described in
+ * <a href="https://github.com/WICG/attribution-reporting-api/blob/main/app_to_web.md">
+ * Cross App and Web Attribution Measurement</a>. The Measurement API fetches and processes the
+ * attribution response from the browser-provided URL. If the URL response contains additional
+ * data that needs to be processed by an {@link IsolatedService}, the Measurement API passes this
+ * to the OnDevicePersonalization service and the OnDevicePersonalization service will invoke
+ * the {@link IsolatedService} with the provided data.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_ON_DEVICE_PERSONALIZATION_APIS_ENABLED)
+@DataClass(genBuilder = true, genEqualsHashCode = true)
+public final class MeasurementWebTriggerEventParams {
+ /**
+ * The URL of the web page where the web trigger event occurred.
+ */
+ @NonNull private Uri mDestinationUrl;
+
+ /**
+ * The package name of the browser app where the web trigger event occurred.
+ */
+ @NonNull private String mAppPackageName;
+
+ /**
+ * The package and class name of the {@link IsolatedService} that should process
+ * the web trigger event.
+ */
+ @NonNull private ComponentName mIsolatedService;
+
+ /**
+ * An optional SHA-256 hash of the signing key of the package that contains
+ * the {@link IsolatedService}, to guard against package name spoofing via sideloading.
+ * If this field is present and does not match the signing key of the installed receiver
+ * service package, the web trigger event is discarded.
+ */
+ @DataClass.MaySetToNull
+ @Nullable private String mCertDigest = null;
+
+ /**
+ * Additional data that the server may provide to the {@link IsolatedService}. This can be
+ * {@code null} if the server does not need to provide any data other than the required fields.
+ */
+ @DataClass.MaySetToNull
+ @Nullable private byte[] mEventData = null;
+
+
+
+ // 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/MeasurementWebTriggerEventParams.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 */ MeasurementWebTriggerEventParams(
+ @NonNull Uri destinationUrl,
+ @NonNull String appPackageName,
+ @NonNull ComponentName isolatedService,
+ @Nullable String certDigest,
+ @Nullable byte[] eventData) {
+ this.mDestinationUrl = destinationUrl;
+ AnnotationValidations.validate(
+ NonNull.class, null, mDestinationUrl);
+ this.mAppPackageName = appPackageName;
+ AnnotationValidations.validate(
+ NonNull.class, null, mAppPackageName);
+ this.mIsolatedService = isolatedService;
+ AnnotationValidations.validate(
+ NonNull.class, null, mIsolatedService);
+ this.mCertDigest = certDigest;
+ this.mEventData = eventData;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * The URL of the web page where the web trigger event occurred.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Uri getDestinationUrl() {
+ return mDestinationUrl;
+ }
+
+ /**
+ * The package name of the browser app where the web trigger event occurred.
+ */
+ @DataClass.Generated.Member
+ public @NonNull String getAppPackageName() {
+ return mAppPackageName;
+ }
+
+ /**
+ * The package and class name of the {@link IsolatedService} that should process
+ * the web trigger event.
+ */
+ @DataClass.Generated.Member
+ public @NonNull ComponentName getIsolatedService() {
+ return mIsolatedService;
+ }
+
+ /**
+ * An optional SHA-256 hash of the signing key of the package that contains
+ * the {@link IsolatedService}, to guard against package name spoofing via sideloading.
+ * If this field is present and does not match the signing key of the installed receiver
+ * service package, the web trigger event is discarded.
+ */
+ @DataClass.Generated.Member
+ public @Nullable String getCertDigest() {
+ return mCertDigest;
+ }
+
+ /**
+ * Additional data that the server may provide to the {@link IsolatedService}. This can be
+ * {@code null} if the server does not need to provide any data other than the required fields.
+ */
+ @DataClass.Generated.Member
+ public @Nullable byte[] getEventData() {
+ return mEventData;
+ }
+
+ @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(MeasurementWebTriggerEventParams other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ MeasurementWebTriggerEventParams that = (MeasurementWebTriggerEventParams) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && java.util.Objects.equals(mDestinationUrl, that.mDestinationUrl)
+ && java.util.Objects.equals(mAppPackageName, that.mAppPackageName)
+ && java.util.Objects.equals(mIsolatedService, that.mIsolatedService)
+ && java.util.Objects.equals(mCertDigest, that.mCertDigest)
+ && java.util.Arrays.equals(mEventData, that.mEventData);
+ }
+
+ @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.Objects.hashCode(mIsolatedService);
+ _hash = 31 * _hash + java.util.Objects.hashCode(mCertDigest);
+ _hash = 31 * _hash + java.util.Arrays.hashCode(mEventData);
+ return _hash;
+ }
+
+ /**
+ * A builder for {@link MeasurementWebTriggerEventParams}
+ */
+ @SuppressWarnings("WeakerAccess")
+ @DataClass.Generated.Member
+ public static final class Builder {
+
+ private @NonNull Uri mDestinationUrl;
+ private @NonNull String mAppPackageName;
+ private @NonNull ComponentName mIsolatedService;
+ private @Nullable String mCertDigest;
+ private @Nullable byte[] mEventData;
+
+ private long mBuilderFieldsSet = 0L;
+
+ /**
+ * Creates a new Builder.
+ *
+ * @param destinationUrl
+ * The URL of the web page where the web trigger event occurred.
+ * @param appPackageName
+ * The package name of the browser app where the web trigger event occurred.
+ * @param isolatedService
+ * The package and class name of the {@link IsolatedService} that should process
+ * the web trigger event.
+ */
+ public Builder(
+ @NonNull Uri destinationUrl,
+ @NonNull String appPackageName,
+ @NonNull ComponentName isolatedService) {
+ mDestinationUrl = destinationUrl;
+ AnnotationValidations.validate(
+ NonNull.class, null, mDestinationUrl);
+ mAppPackageName = appPackageName;
+ AnnotationValidations.validate(
+ NonNull.class, null, mAppPackageName);
+ mIsolatedService = isolatedService;
+ AnnotationValidations.validate(
+ NonNull.class, null, mIsolatedService);
+ }
+
+ /**
+ * The URL of the web page where the web trigger event occurred.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setDestinationUrl(@NonNull Uri value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x1;
+ mDestinationUrl = value;
+ return this;
+ }
+
+ /**
+ * The package name of the browser app where the web trigger event occurred.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setAppPackageName(@NonNull String value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x2;
+ mAppPackageName = value;
+ return this;
+ }
+
+ /**
+ * The package and class name of the {@link IsolatedService} that should process
+ * the web trigger event.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setIsolatedService(@NonNull ComponentName value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x4;
+ mIsolatedService = value;
+ return this;
+ }
+
+ /**
+ * An optional SHA-256 hash of the signing key of the package that contains
+ * the {@link IsolatedService}, to guard against package name spoofing via sideloading.
+ * If this field is present and does not match the signing key of the installed receiver
+ * service package, the web trigger event is discarded.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setCertDigest(@Nullable String value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x8;
+ mCertDigest = value;
+ return this;
+ }
+
+ /**
+ * Additional data that the server may provide to the {@link IsolatedService}. This can be
+ * {@code null} if the server does not need to provide any data other than the required fields.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setEventData(@Nullable byte... value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x10;
+ mEventData = value;
+ return this;
+ }
+
+ /** Builds the instance. This builder should not be touched after calling this! */
+ public @NonNull MeasurementWebTriggerEventParams build() {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x20; // Mark builder used
+
+ if ((mBuilderFieldsSet & 0x8) == 0) {
+ mCertDigest = null;
+ }
+ if ((mBuilderFieldsSet & 0x10) == 0) {
+ mEventData = null;
+ }
+ MeasurementWebTriggerEventParams o = new MeasurementWebTriggerEventParams(
+ mDestinationUrl,
+ mAppPackageName,
+ mIsolatedService,
+ mCertDigest,
+ mEventData);
+ return o;
+ }
+
+ private void checkNotUsed() {
+ if ((mBuilderFieldsSet & 0x20) != 0) {
+ throw new IllegalStateException(
+ "This Builder should not be reused. Use a new Builder instance instead");
+ }
+ }
+ }
+
+ @DataClass.Generated(
+ time = 1707510203588L,
+ codegenVersion = "1.0.23",
+ sourceFile = "packages/modules/OnDevicePersonalization/framework/java/android/adservices/ondevicepersonalization/MeasurementWebTriggerEventParams.java",
+ inputSignatures = "private @android.annotation.NonNull android.net.Uri mDestinationUrl\nprivate @android.annotation.NonNull java.lang.String mAppPackageName\nprivate @android.annotation.NonNull android.content.ComponentName mIsolatedService\nprivate @com.android.ondevicepersonalization.internal.util.DataClass.MaySetToNull @android.annotation.Nullable java.lang.String mCertDigest\nprivate @com.android.ondevicepersonalization.internal.util.DataClass.MaySetToNull @android.annotation.Nullable byte[] mEventData\nclass MeasurementWebTriggerEventParams extends java.lang.Object implements []\[email protected](genBuilder=true, genEqualsHashCode=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/android-35/android/adservices/ondevicepersonalization/MeasurementWebTriggerEventParamsParcel.java b/android-35/android/adservices/ondevicepersonalization/MeasurementWebTriggerEventParamsParcel.java
new file mode 100644
index 0000000..03f5eae
--- /dev/null
+++ b/android-35/android/adservices/ondevicepersonalization/MeasurementWebTriggerEventParamsParcel.java
@@ -0,0 +1,250 @@
+/*
+ * 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.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.net.Uri;
+import android.os.Parcelable;
+
+import com.android.ondevicepersonalization.internal.util.AnnotationValidations;
+import com.android.ondevicepersonalization.internal.util.DataClass;
+
+/**
+ * A class that contains Web Trigger Event data sent from the Measurement API to the
+ * OnDevicePersonalization service when the browser registers a web trigger
+ * with the <a href="https://developer.android.com/design-for-safety/privacy-sandbox/guides/attribution">
+ * Measurement API</a> and the web trigger data is intended to be processed by an
+ * {@link IsolatedService}.
+ * @hide
+ */
+@DataClass(genAidl = false, genBuilder = false)
+public final class MeasurementWebTriggerEventParamsParcel implements Parcelable {
+ /**
+ * The URL of the web page where the web trigger event occurred.
+ */
+ @NonNull private Uri mDestinationUrl;
+
+ /**
+ * The package name of the browser app where the web trigger event occurred.
+ */
+ @NonNull private String mAppPackageName;
+
+ /**
+ * The package and class name of the {@link IsolatedService} that should process
+ * the web trigger event.
+ */
+ @NonNull private ComponentName mIsolatedService;
+
+ /**
+ * An optional SHA-256 hash of the signing key of the package that contains
+ * the {@link IsolatedService}, to guard against package name spoofing via sideloading.
+ * If this field is present and does not match the signing key of the installed receiver
+ * service package, the web trigger event is discarded.
+ */
+ @Nullable private String mCertDigest = null;
+
+ /**
+ * Additional data that the server may provide to the {@link IsolatedService}. This can be
+ * {@code null} if the server does not need to provide any data other than the required fields.
+ */
+ @Nullable private byte[] mEventData = null;
+
+ public MeasurementWebTriggerEventParamsParcel(
+ @NonNull MeasurementWebTriggerEventParams params) {
+ this(params.getDestinationUrl(), params.getAppPackageName(), params.getIsolatedService(),
+ params.getCertDigest(), params.getEventData());
+ }
+
+
+
+ // 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/MeasurementWebTriggerEventParamsParcel.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /**
+ * Creates a new MeasurementWebTriggerEventParamsParcel.
+ *
+ * @param destinationUrl
+ * The URL of the web page where the web trigger event occurred.
+ * @param appPackageName
+ * The package name of the browser app where the web trigger event occurred.
+ * @param isolatedService
+ * The package and class name of the {@link IsolatedService} that should process
+ * the web trigger event.
+ * @param certDigest
+ * An optional SHA-256 hash of the signing key of the package that contains
+ * the {@link IsolatedService}, to guard against package name spoofing via sideloading.
+ * If this field is present and does not match the signing key of the installed receiver
+ * service package, the web trigger event is discarded.
+ * @param eventData
+ * Additional data that the server may provide to the {@link IsolatedService}. This can be
+ * {@code null} if the server does not need to provide any data other than the required fields.
+ */
+ @DataClass.Generated.Member
+ public MeasurementWebTriggerEventParamsParcel(
+ @NonNull Uri destinationUrl,
+ @NonNull String appPackageName,
+ @NonNull ComponentName isolatedService,
+ @Nullable String certDigest,
+ @Nullable byte[] eventData) {
+ this.mDestinationUrl = destinationUrl;
+ AnnotationValidations.validate(
+ NonNull.class, null, mDestinationUrl);
+ this.mAppPackageName = appPackageName;
+ AnnotationValidations.validate(
+ NonNull.class, null, mAppPackageName);
+ this.mIsolatedService = isolatedService;
+ AnnotationValidations.validate(
+ NonNull.class, null, mIsolatedService);
+ this.mCertDigest = certDigest;
+ this.mEventData = eventData;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * The URL of the web page where the web trigger event occurred.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Uri getDestinationUrl() {
+ return mDestinationUrl;
+ }
+
+ /**
+ * The package name of the browser app where the web trigger event occurred.
+ */
+ @DataClass.Generated.Member
+ public @NonNull String getAppPackageName() {
+ return mAppPackageName;
+ }
+
+ /**
+ * The package and class name of the {@link IsolatedService} that should process
+ * the web trigger event.
+ */
+ @DataClass.Generated.Member
+ public @NonNull ComponentName getIsolatedService() {
+ return mIsolatedService;
+ }
+
+ /**
+ * An optional SHA-256 hash of the signing key of the package that contains
+ * the {@link IsolatedService}, to guard against package name spoofing via sideloading.
+ * If this field is present and does not match the signing key of the installed receiver
+ * service package, the web trigger event is discarded.
+ */
+ @DataClass.Generated.Member
+ public @Nullable String getCertDigest() {
+ return mCertDigest;
+ }
+
+ /**
+ * Additional data that the server may provide to the {@link IsolatedService}. This can be
+ * {@code null} if the server does not need to provide any data other than the required fields.
+ */
+ @DataClass.Generated.Member
+ public @Nullable byte[] getEventData() {
+ return mEventData;
+ }
+
+ @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) { ... }
+
+ byte flg = 0;
+ if (mCertDigest != null) flg |= 0x8;
+ dest.writeByte(flg);
+ dest.writeTypedObject(mDestinationUrl, flags);
+ dest.writeString(mAppPackageName);
+ dest.writeTypedObject(mIsolatedService, flags);
+ if (mCertDigest != null) dest.writeString(mCertDigest);
+ dest.writeByteArray(mEventData);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ MeasurementWebTriggerEventParamsParcel(@NonNull android.os.Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ byte flg = in.readByte();
+ Uri destinationUrl = (Uri) in.readTypedObject(Uri.CREATOR);
+ String appPackageName = in.readString();
+ ComponentName isolatedService = (ComponentName) in.readTypedObject(ComponentName.CREATOR);
+ String certDigest = (flg & 0x8) == 0 ? null : in.readString();
+ byte[] eventData = in.createByteArray();
+
+ this.mDestinationUrl = destinationUrl;
+ AnnotationValidations.validate(
+ NonNull.class, null, mDestinationUrl);
+ this.mAppPackageName = appPackageName;
+ AnnotationValidations.validate(
+ NonNull.class, null, mAppPackageName);
+ this.mIsolatedService = isolatedService;
+ AnnotationValidations.validate(
+ NonNull.class, null, mIsolatedService);
+ this.mCertDigest = certDigest;
+ this.mEventData = eventData;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<MeasurementWebTriggerEventParamsParcel> CREATOR
+ = new Parcelable.Creator<MeasurementWebTriggerEventParamsParcel>() {
+ @Override
+ public MeasurementWebTriggerEventParamsParcel[] newArray(int size) {
+ return new MeasurementWebTriggerEventParamsParcel[size];
+ }
+
+ @Override
+ public MeasurementWebTriggerEventParamsParcel createFromParcel(@NonNull android.os.Parcel in) {
+ return new MeasurementWebTriggerEventParamsParcel(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1707510209072L,
+ codegenVersion = "1.0.23",
+ sourceFile = "packages/modules/OnDevicePersonalization/framework/java/android/adservices/ondevicepersonalization/MeasurementWebTriggerEventParamsParcel.java",
+ inputSignatures = "private @android.annotation.NonNull android.net.Uri mDestinationUrl\nprivate @android.annotation.NonNull java.lang.String mAppPackageName\nprivate @android.annotation.NonNull android.content.ComponentName mIsolatedService\nprivate @android.annotation.Nullable java.lang.String mCertDigest\nprivate @android.annotation.Nullable byte[] mEventData\nclass MeasurementWebTriggerEventParamsParcel 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/android-35/android/adservices/ondevicepersonalization/ModelId.java b/android-35/android/adservices/ondevicepersonalization/ModelId.java
new file mode 100644
index 0000000..0d092a3
--- /dev/null
+++ b/android-35/android/adservices/ondevicepersonalization/ModelId.java
@@ -0,0 +1,225 @@
+/*
+ * 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.IntDef;
+import android.annotation.NonNull;
+import android.os.Parcelable;
+
+import com.android.ondevicepersonalization.internal.util.AnnotationValidations;
+import com.android.ondevicepersonalization.internal.util.DataClass;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/** @hide */
+@DataClass(genAidl = false, genBuilder = true, genEqualsHashCode = true)
+public final class ModelId implements Parcelable {
+
+ public static final int TABLE_ID_REMOTE_DATA = 1;
+ public static final int TABLE_ID_LOCAL_DATA = 2;
+
+ @IntDef(
+ prefix = "TABLE_ID_",
+ value = {TABLE_ID_REMOTE_DATA, TABLE_ID_LOCAL_DATA})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface TABLE {}
+
+ // The table name of the table where pre-trained model is stored. Only supports TFLite model
+ // now.
+ private @TABLE int mTableId;
+
+ // The key of the table where the corresponding value stores a pre-trained model. Only supports
+ // TFLite model now.
+ @NonNull private String mKey;
+
+ // 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/ModelId.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ // @formatter:off
+
+ @IntDef(
+ prefix = "TABLE_ID_",
+ value = {TABLE_ID_REMOTE_DATA, TABLE_ID_LOCAL_DATA})
+ @Retention(RetentionPolicy.SOURCE)
+ @DataClass.Generated.Member
+ public @interface TableId {}
+
+ @DataClass.Generated.Member
+ public static String tableIdToString(@TableId int value) {
+ switch (value) {
+ case TABLE_ID_REMOTE_DATA:
+ return "TABLE_ID_REMOTE_DATA";
+ case TABLE_ID_LOCAL_DATA:
+ return "TABLE_ID_LOCAL_DATA";
+ default:
+ return Integer.toHexString(value);
+ }
+ }
+
+ @DataClass.Generated.Member
+ /* package-private */ ModelId(@TABLE int tableId, @NonNull String key) {
+ this.mTableId = tableId;
+ AnnotationValidations.validate(TABLE.class, null, mTableId);
+ this.mKey = key;
+ AnnotationValidations.validate(NonNull.class, null, mKey);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public @TABLE int getTableId() {
+ return mTableId;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull String getKey() {
+ return mKey;
+ }
+
+ @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(ModelId other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ ModelId that = (ModelId) o;
+ //noinspection PointlessBooleanExpression
+ return true && mTableId == that.mTableId && java.util.Objects.equals(mKey, that.mKey);
+ }
+
+ @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 + mTableId;
+ _hash = 31 * _hash + java.util.Objects.hashCode(mKey);
+ return _hash;
+ }
+
+ @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(mTableId);
+ dest.writeString(mKey);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() {
+ return 0;
+ }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ ModelId(@NonNull android.os.Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ int tableId = in.readInt();
+ String key = in.readString();
+
+ this.mTableId = tableId;
+ AnnotationValidations.validate(TABLE.class, null, mTableId);
+ this.mKey = key;
+ AnnotationValidations.validate(NonNull.class, null, mKey);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<ModelId> CREATOR =
+ new Parcelable.Creator<ModelId>() {
+ @Override
+ public ModelId[] newArray(int size) {
+ return new ModelId[size];
+ }
+
+ @Override
+ public ModelId createFromParcel(@NonNull android.os.Parcel in) {
+ return new ModelId(in);
+ }
+ };
+
+ /** A builder for {@link ModelId} */
+ @SuppressWarnings("WeakerAccess")
+ @DataClass.Generated.Member
+ public static final class Builder {
+
+ private @TABLE int mTableId;
+ private @NonNull String mKey;
+
+ private long mBuilderFieldsSet = 0L;
+
+ public Builder() {}
+
+ @DataClass.Generated.Member
+ public @NonNull Builder setTableId(@TABLE int value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x1;
+ mTableId = value;
+ return this;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull Builder setKey(@NonNull String value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x2;
+ mKey = value;
+ return this;
+ }
+
+ /** Builds the instance. This builder should not be touched after calling this! */
+ public @NonNull ModelId build() {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x4; // Mark builder used
+
+ ModelId o = new ModelId(mTableId, mKey);
+ return o;
+ }
+
+ private void checkNotUsed() {
+ if ((mBuilderFieldsSet & 0x4) != 0) {
+ throw new IllegalStateException(
+ "This Builder should not be reused. Use a new Builder instance instead");
+ }
+ }
+ }
+
+ // @formatter:on
+ // End of generated code
+
+}
diff --git a/android-35/android/adservices/ondevicepersonalization/ModelManager.java b/android-35/android/adservices/ondevicepersonalization/ModelManager.java
new file mode 100644
index 0000000..29f6e01
--- /dev/null
+++ b/android-35/android/adservices/ondevicepersonalization/ModelManager.java
@@ -0,0 +1,132 @@
+/*
+ * 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.adservices.ondevicepersonalization.aidl.IDataAccessService;
+import android.adservices.ondevicepersonalization.aidl.IIsolatedModelService;
+import android.adservices.ondevicepersonalization.aidl.IIsolatedModelServiceCallback;
+import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.WorkerThread;
+import android.os.Bundle;
+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.Objects;
+import java.util.concurrent.Executor;
+
+/**
+ * Handles model inference and only support TFLite model inference now. See {@link
+ * IsolatedService#getModelManager}.
+ */
+@FlaggedApi(Flags.FLAG_ON_DEVICE_PERSONALIZATION_APIS_ENABLED)
+public class ModelManager {
+ private static final LoggerFactory.Logger sLogger = LoggerFactory.getLogger();
+ private static final String TAG = ModelManager.class.getSimpleName();
+ @NonNull private final IDataAccessService mDataService;
+
+ @NonNull private final IIsolatedModelService mModelService;
+
+ /** @hide */
+ public ModelManager(
+ @NonNull IDataAccessService dataService, @NonNull IIsolatedModelService modelService) {
+ mDataService = dataService;
+ mModelService = modelService;
+ }
+
+ /**
+ * Run a single model inference. Only supports TFLite model inference now.
+ *
+ * @param input contains all the information needed for a run of model inference.
+ * @param executor the {@link Executor} on which to invoke the callback.
+ * @param receiver this returns a {@link InferenceOutput} which contains model inference result
+ * or {@link Exception} on failure.
+ */
+ @WorkerThread
+ public void run(
+ @NonNull InferenceInput input,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OutcomeReceiver<InferenceOutput, Exception> receiver) {
+ final long startTimeMillis = System.currentTimeMillis();
+ Objects.requireNonNull(input);
+ Bundle bundle = new Bundle();
+ bundle.putBinder(Constants.EXTRA_DATA_ACCESS_SERVICE_BINDER, mDataService.asBinder());
+ bundle.putParcelable(Constants.EXTRA_INFERENCE_INPUT, new InferenceInputParcel(input));
+ try {
+ mModelService.runInference(
+ bundle,
+ new IIsolatedModelServiceCallback.Stub() {
+ @Override
+ public void onSuccess(Bundle result) {
+ executor.execute(
+ () -> {
+ int responseCode = Constants.STATUS_SUCCESS;
+ long endTimeMillis = System.currentTimeMillis();
+ try {
+ InferenceOutputParcel outputParcel =
+ Objects.requireNonNull(
+ result.getParcelable(
+ Constants.EXTRA_RESULT,
+ InferenceOutputParcel.class));
+ InferenceOutput output =
+ new InferenceOutput(outputParcel.getData());
+ endTimeMillis = System.currentTimeMillis();
+ receiver.onResult(output);
+ } catch (Exception e) {
+ endTimeMillis = System.currentTimeMillis();
+ responseCode = Constants.STATUS_INTERNAL_ERROR;
+ receiver.onError(e);
+ } finally {
+ logApiCallStats(
+ Constants.API_NAME_MODEL_MANAGER_RUN,
+ endTimeMillis - startTimeMillis,
+ responseCode);
+ }
+ });
+ }
+
+ @Override
+ public void onError(int errorCode) {
+ executor.execute(
+ () -> {
+ long endTimeMillis = System.currentTimeMillis();
+ receiver.onError(
+ new IllegalStateException("Error: " + errorCode));
+ logApiCallStats(
+ Constants.API_NAME_MODEL_MANAGER_RUN,
+ endTimeMillis - startTimeMillis,
+ Constants.STATUS_INTERNAL_ERROR);
+ });
+ }
+ });
+ } catch (RemoteException e) {
+ receiver.onError(new IllegalStateException(e));
+ }
+ }
+
+ private void logApiCallStats(int apiName, long duration, int responseCode) {
+ try {
+ mDataService.logApiCallStats(apiName, duration, responseCode);
+ } catch (Exception e) {
+ sLogger.d(e, TAG + ": failed to log metrics");
+ }
+ }
+}
diff --git a/android-35/android/adservices/ondevicepersonalization/MutableKeyValueStore.java b/android-35/android/adservices/ondevicepersonalization/MutableKeyValueStore.java
new file mode 100644
index 0000000..d20fc31
--- /dev/null
+++ b/android-35/android/adservices/ondevicepersonalization/MutableKeyValueStore.java
@@ -0,0 +1,57 @@
+/*
+ * 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;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.WorkerThread;
+
+import com.android.adservices.ondevicepersonalization.flags.Flags;
+
+/**
+ * An interface to a read-write key-value store.
+ *
+ * Used as a Data Access Object for the LOCAL_DATA table.
+ *
+ * @see IsolatedService#getLocalData(RequestToken)
+ *
+ */
+@FlaggedApi(Flags.FLAG_ON_DEVICE_PERSONALIZATION_APIS_ENABLED)
+public interface MutableKeyValueStore extends KeyValueStore {
+ /**
+ * Associates the specified value with the specified key.
+ * If a value already exists for that key, the old value is replaced.
+ *
+ * @param key key with which the specified value is to be associated
+ * @param value value to be associated with the specified key
+ *
+ * @return the previous value associated with key, or null if there was no mapping for key.
+ */
+ @WorkerThread
+ @Nullable byte[] put(@NonNull String key, @NonNull byte[] value);
+
+ /**
+ * Removes the mapping for the specified key.
+ *
+ * @param key key whose mapping is to be removed
+ *
+ * @return the previous value associated with key, or null if there was no mapping for key.
+ */
+ @WorkerThread
+ @Nullable byte[] remove(@NonNull String key);
+}
diff --git a/android-35/android/adservices/ondevicepersonalization/OSVersion.java b/android-35/android/adservices/ondevicepersonalization/OSVersion.java
new file mode 100644
index 0000000..946da82
--- /dev/null
+++ b/android-35/android/adservices/ondevicepersonalization/OSVersion.java
@@ -0,0 +1,288 @@
+/*
+ * 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.NonNull;
+import android.os.Parcelable;
+
+import com.android.ondevicepersonalization.internal.util.AnnotationValidations;
+import com.android.ondevicepersonalization.internal.util.DataClass;
+
+/**
+ * Values for OS versions.
+*
+* @hide
+*/
+@DataClass(genBuilder = true, genEqualsHashCode = true)
+public final class OSVersion implements Parcelable {
+ /** Major OS version. */
+ @NonNull int mMajor;
+
+ /** Minor OS version. */
+ @NonNull int mMinor;
+
+ /** Micro OS version. */
+ @NonNull int mMicro;
+
+
+
+ // 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/OSVersion.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 */ OSVersion(
+ @NonNull int major,
+ @NonNull int minor,
+ @NonNull int micro) {
+ this.mMajor = major;
+ AnnotationValidations.validate(
+ NonNull.class, null, mMajor);
+ this.mMinor = minor;
+ AnnotationValidations.validate(
+ NonNull.class, null, mMinor);
+ this.mMicro = micro;
+ AnnotationValidations.validate(
+ NonNull.class, null, mMicro);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * Major OS version.
+ */
+ @DataClass.Generated.Member
+ public @NonNull int getMajor() {
+ return mMajor;
+ }
+
+ /**
+ * Minor OS version.
+ */
+ @DataClass.Generated.Member
+ public @NonNull int getMinor() {
+ return mMinor;
+ }
+
+ /**
+ * Micro OS version.
+ */
+ @DataClass.Generated.Member
+ public @NonNull int getMicro() {
+ return mMicro;
+ }
+
+ @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(OSVersion other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ OSVersion that = (OSVersion) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && mMajor == that.mMajor
+ && mMinor == that.mMinor
+ && mMicro == that.mMicro;
+ }
+
+ @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 + mMajor;
+ _hash = 31 * _hash + mMinor;
+ _hash = 31 * _hash + mMicro;
+ return _hash;
+ }
+
+ @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(mMajor);
+ dest.writeInt(mMinor);
+ dest.writeInt(mMicro);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ OSVersion(@NonNull android.os.Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ int major = in.readInt();
+ int minor = in.readInt();
+ int micro = in.readInt();
+
+ this.mMajor = major;
+ AnnotationValidations.validate(
+ NonNull.class, null, mMajor);
+ this.mMinor = minor;
+ AnnotationValidations.validate(
+ NonNull.class, null, mMinor);
+ this.mMicro = micro;
+ AnnotationValidations.validate(
+ NonNull.class, null, mMicro);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<OSVersion> CREATOR
+ = new Parcelable.Creator<OSVersion>() {
+ @Override
+ public OSVersion[] newArray(int size) {
+ return new OSVersion[size];
+ }
+
+ @Override
+ public OSVersion createFromParcel(@NonNull android.os.Parcel in) {
+ return new OSVersion(in);
+ }
+ };
+
+ /**
+ * A builder for {@link OSVersion}
+ */
+ @SuppressWarnings("WeakerAccess")
+ @DataClass.Generated.Member
+ public static final class Builder {
+
+ private @NonNull int mMajor;
+ private @NonNull int mMinor;
+ private @NonNull int mMicro;
+
+ private long mBuilderFieldsSet = 0L;
+
+ /**
+ * Creates a new Builder.
+ *
+ * @param major
+ * Major OS version.
+ * @param minor
+ * Minor OS version.
+ * @param micro
+ * Micro OS version.
+ */
+ public Builder(
+ @NonNull int major,
+ @NonNull int minor,
+ @NonNull int micro) {
+ mMajor = major;
+ AnnotationValidations.validate(
+ NonNull.class, null, mMajor);
+ mMinor = minor;
+ AnnotationValidations.validate(
+ NonNull.class, null, mMinor);
+ mMicro = micro;
+ AnnotationValidations.validate(
+ NonNull.class, null, mMicro);
+ }
+
+ public Builder() {
+ }
+
+ /**
+ * Major OS version.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setMajor(@NonNull int value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x1;
+ mMajor = value;
+ return this;
+ }
+
+ /**
+ * Minor OS version.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setMinor(@NonNull int value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x2;
+ mMinor = value;
+ return this;
+ }
+
+ /**
+ * Micro OS version.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setMicro(@NonNull int value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x4;
+ mMicro = value;
+ return this;
+ }
+
+ /** Builds the instance. This builder should not be touched after calling this! */
+ public @NonNull OSVersion build() {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x8; // Mark builder used
+
+ OSVersion o = new OSVersion(
+ mMajor,
+ mMinor,
+ mMicro);
+ return o;
+ }
+
+ private void checkNotUsed() {
+ if ((mBuilderFieldsSet & 0x8) != 0) {
+ throw new IllegalStateException(
+ "This Builder should not be reused. Use a new Builder instance instead");
+ }
+ }
+ }
+
+ @DataClass.Generated(
+ time = 1692118390970L,
+ codegenVersion = "1.0.23",
+ sourceFile = "packages/modules/OnDevicePersonalization/framework/java/android/adservices/ondevicepersonalization/OSVersion.java",
+ inputSignatures = " @android.annotation.NonNull int mMajor\n @android.annotation.NonNull int mMinor\n @android.annotation.NonNull int mMicro\nclass OSVersion extends java.lang.Object implements [android.os.Parcelable]\[email protected](genBuilder=true, genEqualsHashCode=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/android-35/android/adservices/ondevicepersonalization/OnDevicePersonalizationConfigManager.java b/android-35/android/adservices/ondevicepersonalization/OnDevicePersonalizationConfigManager.java
new file mode 100644
index 0000000..df3fc3b
--- /dev/null
+++ b/android-35/android/adservices/ondevicepersonalization/OnDevicePersonalizationConfigManager.java
@@ -0,0 +1,166 @@
+/*
+ * 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 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;
+
+/**
+ * OnDevicePersonalizationConfigManager provides system APIs
+ * for privileged APKs to control OnDevicePersonalization's enablement status.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_ON_DEVICE_PERSONALIZATION_APIS_ENABLED)
+public class OnDevicePersonalizationConfigManager {
+ /** @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;
+ }
+
+ /**
+ * 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.
+ */
+ @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();
+ }
+}
diff --git a/android-35/android/adservices/ondevicepersonalization/OnDevicePersonalizationDebugManager.java b/android-35/android/adservices/ondevicepersonalization/OnDevicePersonalizationDebugManager.java
new file mode 100644
index 0000000..d055838
--- /dev/null
+++ b/android-35/android/adservices/ondevicepersonalization/OnDevicePersonalizationDebugManager.java
@@ -0,0 +1,68 @@
+/*
+ * 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.adservices.ondevicepersonalization.aidl.IOnDevicePersonalizationDebugService;
+import android.content.Context;
+import android.os.RemoteException;
+
+import com.android.federatedcompute.internal.util.AbstractServiceBinder;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Executors;
+
+/**
+ * Provides APIs to support testing.
+ * @hide
+ */
+public class OnDevicePersonalizationDebugManager {
+
+ private static final String INTENT_FILTER_ACTION =
+ "android.OnDevicePersonalizationDebugService";
+ private static final String ODP_MANAGING_SERVICE_PACKAGE_SUFFIX =
+ "com.android.ondevicepersonalization.services";
+
+ private static final String ALT_ODP_MANAGING_SERVICE_PACKAGE_SUFFIX =
+ "com.google.android.ondevicepersonalization.services";
+
+ private final AbstractServiceBinder<IOnDevicePersonalizationDebugService> mServiceBinder;
+
+ public OnDevicePersonalizationDebugManager(Context context) {
+ mServiceBinder =
+ AbstractServiceBinder.getServiceBinderByIntent(
+ context,
+ INTENT_FILTER_ACTION,
+ List.of(
+ ODP_MANAGING_SERVICE_PACKAGE_SUFFIX,
+ ALT_ODP_MANAGING_SERVICE_PACKAGE_SUFFIX),
+ IOnDevicePersonalizationDebugService.Stub::asInterface);
+ }
+
+ /** Returns whether the service is enabled. */
+ public Boolean isEnabled() {
+ try {
+ IOnDevicePersonalizationDebugService service = Objects.requireNonNull(
+ mServiceBinder.getService(Executors.newSingleThreadExecutor()));
+ boolean result = service.isEnabled();
+ mServiceBinder.unbindFromService();
+ return result;
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+}
diff --git a/android-35/android/adservices/ondevicepersonalization/OnDevicePersonalizationException.java b/android-35/android/adservices/ondevicepersonalization/OnDevicePersonalizationException.java
new file mode 100644
index 0000000..47b0128
--- /dev/null
+++ b/android-35/android/adservices/ondevicepersonalization/OnDevicePersonalizationException.java
@@ -0,0 +1,77 @@
+/*
+ * 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.IntDef;
+
+import com.android.adservices.ondevicepersonalization.flags.Flags;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Exception thrown by OnDevicePersonalization APIs.
+ *
+ */
+@FlaggedApi(Flags.FLAG_ON_DEVICE_PERSONALIZATION_APIS_ENABLED)
+public class OnDevicePersonalizationException extends Exception {
+ /**
+ * The {@link IsolatedService} that was invoked failed to run.
+ */
+ public static final int ERROR_ISOLATED_SERVICE_FAILED = 1;
+
+ /**
+ * The {@link IsolatedService} was not started because personalization is disabled by
+ * device configuration.
+ */
+ public static final int ERROR_PERSONALIZATION_DISABLED = 2;
+
+ /** @hide */
+ @IntDef(prefix = "ERROR_", value = {
+ ERROR_ISOLATED_SERVICE_FAILED,
+ ERROR_PERSONALIZATION_DISABLED
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ErrorCode {}
+
+ private final @ErrorCode int mErrorCode;
+
+ /** @hide */
+ public OnDevicePersonalizationException(@ErrorCode int errorCode) {
+ mErrorCode = errorCode;
+ }
+
+ /** @hide */
+ public OnDevicePersonalizationException(
+ @ErrorCode int errorCode, String message) {
+ super(message);
+ mErrorCode = errorCode;
+ }
+
+ /** @hide */
+ public OnDevicePersonalizationException(
+ @ErrorCode int errorCode, Throwable cause) {
+ super(cause);
+ mErrorCode = errorCode;
+ }
+
+ /** Returns the error code for this exception. */
+ public @ErrorCode int getErrorCode() {
+ return mErrorCode;
+ }
+}
diff --git a/android-35/android/adservices/ondevicepersonalization/OnDevicePersonalizationManager.java b/android-35/android/adservices/ondevicepersonalization/OnDevicePersonalizationManager.java
new file mode 100644
index 0000000..5b00b2a
--- /dev/null
+++ b/android-35/android/adservices/ondevicepersonalization/OnDevicePersonalizationManager.java
@@ -0,0 +1,434 @@
+/*
+ * 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.adservices.ondevicepersonalization.aidl.IExecuteCallback;
+import android.adservices.ondevicepersonalization.aidl.IOnDevicePersonalizationManagingService;
+import android.adservices.ondevicepersonalization.aidl.IRequestSurfacePackageCallback;
+import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.OutcomeReceiver;
+import android.os.PersistableBundle;
+import android.os.SystemClock;
+import android.view.SurfaceControlViewHost;
+
+import com.android.adservices.ondevicepersonalization.flags.Flags;
+import com.android.federatedcompute.internal.util.AbstractServiceBinder;
+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.LoggerFactory;
+import com.android.ondevicepersonalization.internal.util.PersistableBundleUtils;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+// TODO(b/289102463): Add a link to the public ODP developer documentation.
+/**
+ * OnDevicePersonalizationManager provides APIs for apps to load an
+ * {@link IsolatedService} in an isolated process and interact with it.
+ *
+ * An app can request an {@link IsolatedService} to generate content for display
+ * within an {@link android.view.SurfaceView} within the app's view hierarchy, and also write
+ * persistent results to on-device storage which can be consumed by Federated Analytics for
+ * cross-device statistical analysis or by Federated Learning for model training. The displayed
+ * content and the persistent output are both not directly accessible by the calling app.
+ */
+@FlaggedApi(Flags.FLAG_ON_DEVICE_PERSONALIZATION_APIS_ENABLED)
+public class OnDevicePersonalizationManager {
+ /** @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";
+
+ private static final String ALT_ODP_MANAGING_SERVICE_PACKAGE_SUFFIX =
+ "com.google.android.ondevicepersonalization.services";
+ 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;
+
+ /**
+ * The result of a call to {@link OnDevicePersonalizationManager#execute(ComponentName,
+ * PersistableBundle, Executor, OutcomeReceiver)}
+ */
+ public static class ExecuteResult {
+ @Nullable private final SurfacePackageToken mSurfacePackageToken;
+ @Nullable private final byte[] mOutputData;
+
+ /** @hide */
+ ExecuteResult(
+ @Nullable SurfacePackageToken surfacePackageToken,
+ @Nullable byte[] outputData) {
+ mSurfacePackageToken = surfacePackageToken;
+ mOutputData = outputData;
+ }
+
+ /**
+ * Returns a {@link SurfacePackageToken}, which is an opaque reference to content that
+ * can be displayed in a {@link android.view.SurfaceView}. This may be 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 output data that was returned by the {@link IsolatedService}. This will be
+ * non-null if the {@link IsolatedService} returns any results to the caller, and the
+ * egress of data from the {@link IsolatedService} to the specific calling app is allowed
+ * by policy as well as an allowlist.
+ */
+ @Nullable public byte[] getOutputData() {
+ return mOutputData;
+ }
+ }
+
+ /** @hide */
+ public OnDevicePersonalizationManager(Context context) {
+ this(
+ context,
+ AbstractServiceBinder.getServiceBinderByIntent(
+ context,
+ INTENT_FILTER_ACTION,
+ List.of(
+ ODP_MANAGING_SERVICE_PACKAGE_SUFFIX,
+ ALT_ODP_MANAGING_SERVICE_PACKAGE_SUFFIX),
+ SdkLevel.isAtLeastU() ? Context.BIND_ALLOW_ACTIVITY_STARTS : 0,
+ IOnDevicePersonalizationManagingService.Stub::asInterface));
+ }
+
+ /** @hide */
+ @VisibleForTesting
+ public OnDevicePersonalizationManager(
+ Context context,
+ AbstractServiceBinder<IOnDevicePersonalizationManagingService> serviceBinder) {
+ mContext = context;
+ mServiceBinder = serviceBinder;
+ }
+
+ /**
+ * 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 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.
+ */
+ public void execute(
+ @NonNull ComponentName service,
+ @NonNull PersistableBundle params,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OutcomeReceiver<ExecuteResult, Exception> receiver
+ ) {
+ Objects.requireNonNull(service);
+ Objects.requireNonNull(params);
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(receiver);
+ 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");
+ }
+ long startTimeMillis = SystemClock.elapsedRealtime();
+
+ try {
+ final IOnDevicePersonalizationManagingService odpService =
+ mServiceBinder.getService(executor);
+
+ try {
+ IExecuteCallback callbackWrapper = new IExecuteCallback.Stub() {
+ @Override
+ public void onSuccess(
+ Bundle callbackResult) {
+ 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);
+ }
+ }
+ byte[] data = callbackResult.getByteArray(
+ Constants.EXTRA_OUTPUT_DATA);
+ receiver.onResult(
+ new ExecuteResult(surfacePackageToken, data));
+ } catch (Exception e) {
+ receiver.onError(e);
+ }
+ });
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ logApiCallStats(
+ odpService,
+ Constants.API_NAME_EXECUTE,
+ SystemClock.elapsedRealtime() - startTimeMillis,
+ Constants.STATUS_SUCCESS);
+ }
+ }
+
+ @Override
+ public void onError(
+ int errorCode, int isolatedServiceErrorCode, String message) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ executor.execute(() -> receiver.onError(
+ createException(
+ errorCode, isolatedServiceErrorCode, message)));
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ logApiCallStats(
+ odpService,
+ Constants.API_NAME_EXECUTE,
+ SystemClock.elapsedRealtime() - startTimeMillis,
+ errorCode);
+ }
+
+ }
+ };
+
+ Bundle wrappedParams = new Bundle();
+ wrappedParams.putParcelable(
+ Constants.EXTRA_APP_PARAMS_SERIALIZED,
+ new ByteArrayParceledSlice(PersistableBundleUtils.toByteArray(params)));
+ odpService.execute(
+ mContext.getPackageName(),
+ service,
+ wrappedParams,
+ new CallerMetadata.Builder().setStartTimeMillis(startTimeMillis).build(),
+ callbackWrapper);
+
+ } catch (Exception e) {
+ logApiCallStats(
+ odpService,
+ Constants.API_NAME_EXECUTE,
+ SystemClock.elapsedRealtime() - startTimeMillis,
+ 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
+ * {@code #execute(ComponentName, PersistableBundle, Executor, OutcomeReceiver)} running in
+ * the OnDevicePersonalization sandbox.
+ *
+ * @param surfacePackageToken a reference to a {@link SurfacePackageToken} returned by a prior
+ * call to {@code #execute(ComponentName, PersistableBundle, Executor, OutcomeReceiver)}.
+ * @param surfaceViewHostToken the hostToken of the {@link android.view.SurfaceView}, which is
+ * returned by {@link android.view.SurfaceView#getHostToken()} after the
+ * {@link android.view.SurfaceView} has been added to the view hierarchy.
+ * @param displayId the integer ID of the logical display on which to display the
+ * {@link android.view.SurfaceControlViewHost.SurfacePackage}, returned by
+ * {@code Context.getDisplay().getDisplayId()}.
+ * @param width the width of the {@link android.view.SurfaceControlViewHost.SurfacePackage}
+ * in pixels.
+ * @param height the height of the {@link android.view.SurfaceControlViewHost.SurfacePackage}
+ * in pixels.
+ * @param executor the {@link Executor} on which to invoke the callback
+ * @param receiver This either returns a
+ * {@link android.view.SurfaceControlViewHost.SurfacePackage} on success, or
+ * {@link Exception} on failure. The exception type is
+ * {@link OnDevicePersonalizationException} if execution of the handler fails.
+ */
+ public void requestSurfacePackage(
+ @NonNull SurfacePackageToken surfacePackageToken,
+ @NonNull IBinder surfaceViewHostToken,
+ int displayId,
+ int width,
+ int height,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OutcomeReceiver<SurfaceControlViewHost.SurfacePackage, Exception> receiver
+ ) {
+ Objects.requireNonNull(surfacePackageToken);
+ Objects.requireNonNull(surfaceViewHostToken);
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(receiver);
+ if (width <= 0) {
+ throw new IllegalArgumentException("width must be > 0");
+ }
+
+ if (height <= 0) {
+ throw new IllegalArgumentException("height must be > 0");
+ }
+
+ if (displayId < 0) {
+ throw new IllegalArgumentException("displayId must be >= 0");
+ }
+ long startTimeMillis = SystemClock.elapsedRealtime();
+
+ try {
+ final IOnDevicePersonalizationManagingService service =
+ Objects.requireNonNull(mServiceBinder.getService(executor));
+
+ try {
+ IRequestSurfacePackageCallback callbackWrapper =
+ new IRequestSurfacePackageCallback.Stub() {
+ @Override
+ public void onSuccess(
+ SurfaceControlViewHost.SurfacePackage surfacePackage) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ executor.execute(() -> {
+ receiver.onResult(surfacePackage);
+ });
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ logApiCallStats(
+ service,
+ Constants.API_NAME_REQUEST_SURFACE_PACKAGE,
+ SystemClock.elapsedRealtime() - startTimeMillis,
+ Constants.STATUS_SUCCESS);
+ }
+ }
+
+ @Override
+ public void onError(
+ int errorCode, int isolatedServiceErrorCode, String message) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ executor.execute(
+ () -> receiver.onError(createException(
+ errorCode, isolatedServiceErrorCode,
+ message)));
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ logApiCallStats(
+ service,
+ Constants.API_NAME_REQUEST_SURFACE_PACKAGE,
+ SystemClock.elapsedRealtime() - startTimeMillis,
+ errorCode);
+ }
+ }
+ };
+
+ service.requestSurfacePackage(
+ surfacePackageToken.getTokenString(),
+ surfaceViewHostToken,
+ displayId,
+ width,
+ height,
+ new CallerMetadata.Builder().setStartTimeMillis(startTimeMillis).build(),
+ callbackWrapper);
+
+ } catch (Exception e) {
+ logApiCallStats(
+ service,
+ Constants.API_NAME_REQUEST_SURFACE_PACKAGE,
+ SystemClock.elapsedRealtime() - startTimeMillis,
+ Constants.STATUS_INTERNAL_ERROR);
+ receiver.onError(e);
+ }
+
+ } catch (Exception e) {
+ receiver.onError(e);
+ }
+ }
+
+ private Exception createException(
+ int errorCode, int isolatedServiceErrorCode, String message) {
+ if (message == null || message.isBlank()) {
+ message = "Error: " + errorCode;
+ }
+ 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) {
+ return new OnDevicePersonalizationException(
+ OnDevicePersonalizationException.ERROR_ISOLATED_SERVICE_FAILED,
+ new IsolatedServiceException(isolatedServiceErrorCode));
+ } else {
+ return new OnDevicePersonalizationException(
+ OnDevicePersonalizationException.ERROR_ISOLATED_SERVICE_FAILED,
+ message);
+ }
+ } else if (errorCode == Constants.STATUS_PERSONALIZATION_DISABLED) {
+ return new OnDevicePersonalizationException(
+ OnDevicePersonalizationException.ERROR_PERSONALIZATION_DISABLED,
+ message);
+ } else {
+ return new IllegalStateException(message);
+ }
+ }
+
+ private void logApiCallStats(
+ IOnDevicePersonalizationManagingService service,
+ int apiName,
+ long latencyMillis,
+ int responseCode) {
+ try {
+ if (service != null) {
+ service.logApiCallStats(apiName, latencyMillis, responseCode);
+ }
+ } catch (Exception e) {
+ sLogger.e(e, TAG + ": Error logging API call stats");
+ }
+ }
+}
diff --git a/android-35/android/adservices/ondevicepersonalization/OnDevicePersonalizationPermissions.java b/android-35/android/adservices/ondevicepersonalization/OnDevicePersonalizationPermissions.java
new file mode 100644
index 0000000..ecf3b9c
--- /dev/null
+++ b/android-35/android/adservices/ondevicepersonalization/OnDevicePersonalizationPermissions.java
@@ -0,0 +1,52 @@
+/*
+ * 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.SystemApi;
+
+import com.android.adservices.ondevicepersonalization.flags.Flags;
+
+/**
+ * OnDevicePersonalization permission settings.
+ *
+ * @hide
+*/
+@SystemApi
+@FlaggedApi(Flags.FLAG_ON_DEVICE_PERSONALIZATION_APIS_ENABLED)
+public class OnDevicePersonalizationPermissions {
+ private OnDevicePersonalizationPermissions() {}
+
+ /**
+ * The permission that lets it modify ODP's enablement state.
+ */
+ public static final String MODIFY_ONDEVICEPERSONALIZATION_STATE =
+ "android.permission.ondevicepersonalization.MODIFY_ONDEVICEPERSONALIZATION_STATE";
+
+ /**
+ * The permission required for callers to send measurement events to ODP.
+ */
+ public static final String NOTIFY_MEASUREMENT_EVENT =
+ "android.permission.ondevicepersonalization.NOTIFY_MEASUREMENT_EVENT";
+
+ /**
+ * The permission required to connect to the ODP system server component.
+ * @hide
+ */
+ public static final String ACCESS_SYSTEM_SERVER_SERVICE =
+ "android.permission.ondevicepersonalization.ACCESS_SYSTEM_SERVER_SERVICE";
+}
diff --git a/android-35/android/adservices/ondevicepersonalization/OnDevicePersonalizationSystemEventManager.java b/android-35/android/adservices/ondevicepersonalization/OnDevicePersonalizationSystemEventManager.java
new file mode 100644
index 0000000..a09397e
--- /dev/null
+++ b/android-35/android/adservices/ondevicepersonalization/OnDevicePersonalizationSystemEventManager.java
@@ -0,0 +1,148 @@
+/*
+ * 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 static android.adservices.ondevicepersonalization.OnDevicePersonalizationPermissions.NOTIFY_MEASUREMENT_EVENT;
+
+import android.adservices.ondevicepersonalization.aidl.IOnDevicePersonalizationManagingService;
+import android.adservices.ondevicepersonalization.aidl.IRegisterMeasurementEventCallback;
+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.Bundle;
+import android.os.OutcomeReceiver;
+import android.os.SystemClock;
+
+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.Objects;
+import java.util.concurrent.Executor;
+
+/**
+ * Provides APIs for the platform to signal events that are to be handled by the ODP service.
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_ON_DEVICE_PERSONALIZATION_APIS_ENABLED)
+public class OnDevicePersonalizationSystemEventManager {
+ /** @hide */
+ public static final String ON_DEVICE_PERSONALIZATION_SYSTEM_EVENT_SERVICE =
+ "on_device_personalization_system_event_service";
+ private static final String INTENT_FILTER_ACTION =
+ "android.OnDevicePersonalizationService";
+ private static final String ODP_MANAGING_SERVICE_PACKAGE_SUFFIX =
+ "com.android.ondevicepersonalization.services";
+ private static final String ALT_ODP_MANAGING_SERVICE_PACKAGE_SUFFIX =
+ "com.google.android.ondevicepersonalization.services";
+ private static final LoggerFactory.Logger sLogger = LoggerFactory.getLogger();
+
+ // TODO(b/301732670): Define a new service for this manager and bind to it.
+ private final AbstractServiceBinder<IOnDevicePersonalizationManagingService> mServiceBinder;
+ private final Context mContext;
+
+ /** @hide */
+ public OnDevicePersonalizationSystemEventManager(Context context) {
+ this(
+ context,
+ AbstractServiceBinder.getServiceBinderByIntent(
+ context,
+ INTENT_FILTER_ACTION,
+ List.of(
+ ODP_MANAGING_SERVICE_PACKAGE_SUFFIX,
+ ALT_ODP_MANAGING_SERVICE_PACKAGE_SUFFIX),
+ 0,
+ IOnDevicePersonalizationManagingService.Stub::asInterface));
+ }
+
+ /** @hide */
+ @VisibleForTesting
+ public OnDevicePersonalizationSystemEventManager(
+ Context context,
+ AbstractServiceBinder<IOnDevicePersonalizationManagingService> serviceBinder) {
+ mContext = context;
+ mServiceBinder = serviceBinder;
+ }
+
+ /**
+ * Receives a web trigger event from the Measurement API. This is intended to be called by the
+ * <a href="https://developer.android.com/design-for-safety/privacy-sandbox/guides/attribution">
+ * Measurement Service</a> when a browser registers an attribution event using the
+ * <a href="https://github.com/WICG/attribution-reporting-api">Attribution and Reporting API</a>
+ * with a payload that should be processed by an {@link IsolatedService}.
+ *
+ * @param measurementWebTriggerEvent the web trigger payload to be processed.
+ * @param executor the {@link Executor} on which to invoke the callback.
+ * @param receiver This either returns a {@code null} on success, or an exception on failure.
+ */
+ @RequiresPermission(NOTIFY_MEASUREMENT_EVENT)
+ public void notifyMeasurementEvent(
+ @NonNull MeasurementWebTriggerEventParams measurementWebTriggerEvent,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OutcomeReceiver<Void, Exception> receiver) {
+ Objects.requireNonNull(measurementWebTriggerEvent);
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(receiver);
+ long startTimeMillis = SystemClock.elapsedRealtime();
+
+ try {
+ final IOnDevicePersonalizationManagingService service =
+ mServiceBinder.getService(executor);
+ Bundle bundle = new Bundle();
+ bundle.putParcelable(Constants.EXTRA_MEASUREMENT_WEB_TRIGGER_PARAMS,
+ new MeasurementWebTriggerEventParamsParcel(measurementWebTriggerEvent));
+ // TODO(b/301732670): Update method name in service.
+ service.registerMeasurementEvent(
+ Constants.MEASUREMENT_EVENT_TYPE_WEB_TRIGGER,
+ bundle,
+ new CallerMetadata.Builder().setStartTimeMillis(startTimeMillis).build(),
+ new IRegisterMeasurementEventCallback.Stub() {
+ @Override
+ public void onSuccess() {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ executor.execute(() -> receiver.onResult(null));
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ @Override
+ public void onError(int errorCode) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ executor.execute(() -> receiver.onError(
+ new IllegalStateException("Error: " + errorCode)));
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ }
+ );
+ } catch (IllegalArgumentException | NullPointerException e) {
+ throw e;
+ } catch (Exception e) {
+ receiver.onError(e);
+ }
+ }
+}
diff --git a/android-35/android/adservices/ondevicepersonalization/RemoteDataImpl.java b/android-35/android/adservices/ondevicepersonalization/RemoteDataImpl.java
new file mode 100644
index 0000000..aaa32ef
--- /dev/null
+++ b/android-35/android/adservices/ondevicepersonalization/RemoteDataImpl.java
@@ -0,0 +1,145 @@
+/*
+ * 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.adservices.ondevicepersonalization.aidl.IDataAccessService;
+import android.adservices.ondevicepersonalization.aidl.IDataAccessServiceCallback;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Bundle;
+import android.os.RemoteException;
+
+import com.android.ondevicepersonalization.internal.util.ByteArrayParceledSlice;
+import com.android.ondevicepersonalization.internal.util.LoggerFactory;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+
+/** @hide */
+public class RemoteDataImpl implements KeyValueStore {
+ private static final LoggerFactory.Logger sLogger = LoggerFactory.getLogger();
+ private static final String TAG = "RemoteDataImpl";
+ @NonNull
+ IDataAccessService mDataAccessService;
+
+ /** @hide */
+ public RemoteDataImpl(@NonNull IDataAccessService binder) {
+ mDataAccessService = Objects.requireNonNull(binder);
+ }
+
+ @Override @Nullable
+ public byte[] get(@NonNull String key) {
+ Objects.requireNonNull(key);
+ final long startTimeMillis = System.currentTimeMillis();
+ int responseCode = Constants.STATUS_SUCCESS;
+ try {
+ BlockingQueue<Bundle> asyncResult = new ArrayBlockingQueue<>(1);
+ Bundle params = new Bundle();
+ params.putString(Constants.EXTRA_LOOKUP_KEYS, key);
+ mDataAccessService.onRequest(
+ Constants.DATA_ACCESS_OP_REMOTE_DATA_LOOKUP,
+ params,
+ new IDataAccessServiceCallback.Stub() {
+ @Override
+ public void onSuccess(@NonNull Bundle result) {
+ if (result != null) {
+ asyncResult.add(result);
+ } else {
+ asyncResult.add(Bundle.EMPTY);
+ }
+ }
+
+ @Override
+ public void onError(int errorCode) {
+ asyncResult.add(Bundle.EMPTY);
+ }
+ });
+ Bundle result = asyncResult.take();
+ ByteArrayParceledSlice data = result.getParcelable(
+ Constants.EXTRA_RESULT, ByteArrayParceledSlice.class);
+ return (data == null) ? null : data.getByteArray();
+ } catch (InterruptedException | RemoteException e) {
+ sLogger.e(TAG + ": Failed to retrieve key from remoteData", e);
+ responseCode = Constants.STATUS_INTERNAL_ERROR;
+ throw new IllegalStateException(e);
+ } finally {
+ try {
+ mDataAccessService.logApiCallStats(
+ Constants.API_NAME_REMOTE_DATA_GET,
+ System.currentTimeMillis() - startTimeMillis,
+ responseCode);
+ } catch (Exception e) {
+ sLogger.d(e, TAG + ": failed to log metrics");
+ }
+ }
+ }
+
+ @Override @NonNull
+ public Set<String> keySet() {
+ final long startTimeMillis = System.currentTimeMillis();
+ int responseCode = Constants.STATUS_SUCCESS;
+ try {
+ BlockingQueue<Bundle> 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);
+ }
+ }
+
+ @Override
+ public void onError(int errorCode) {
+ asyncResult.add(Bundle.EMPTY);
+ }
+ });
+ Bundle result = asyncResult.take();
+ HashSet<String> resultSet =
+ result.getSerializable(Constants.EXTRA_RESULT, HashSet.class);
+ if (null == resultSet) {
+ return Collections.emptySet();
+ }
+ return resultSet;
+ } catch (InterruptedException | RemoteException e) {
+ sLogger.e(TAG + ": Failed to retrieve keySet from remoteData", e);
+ throw new IllegalStateException(e);
+ } finally {
+ try {
+ mDataAccessService.logApiCallStats(
+ Constants.API_NAME_REMOTE_DATA_KEYSET,
+ System.currentTimeMillis() - startTimeMillis,
+ responseCode);
+ } catch (Exception e) {
+ sLogger.d(e, TAG + ": failed to log metrics");
+ }
+ }
+ }
+
+ @Override
+ public int getTableId() {
+ return ModelId.TABLE_ID_REMOTE_DATA;
+ }
+}
diff --git a/android-35/android/adservices/ondevicepersonalization/RenderInput.java b/android-35/android/adservices/ondevicepersonalization/RenderInput.java
new file mode 100644
index 0000000..9549c73
--- /dev/null
+++ b/android-35/android/adservices/ondevicepersonalization/RenderInput.java
@@ -0,0 +1,158 @@
+/*
+ * 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.DataClass;
+
+/**
+ * The input data for
+ * {@link IsolatedWorker#onRender(RenderInput, android.os.OutcomeReceiver)}.
+ *
+ */
+@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;
+
+ /** The height of the slot. */
+ private int mHeight = 0;
+
+ /**
+ * A {@link RenderingConfig} within an {@link ExecuteOutput} that was returned by
+ * {@link IsolatedWorker#onExecute(ExecuteInput, android.os.OutcomeReceiver)}.
+ */
+ @Nullable RenderingConfig mRenderingConfig = null;
+
+ /** @hide */
+ public RenderInput(@NonNull RenderInputParcel parcel) {
+ 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.
+ *
+ * @param width
+ * The width of the slot.
+ * @param height
+ * The height of the slot.
+ * @param renderingConfig
+ * A {@link RenderingConfig} within an {@link ExecuteOutput} that was returned by
+ * {@link IsolatedWorker#onExecute(ExecuteInput, android.os.OutcomeReceiver)}.
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public RenderInput(
+ int width,
+ int height,
+ @Nullable RenderingConfig renderingConfig) {
+ this.mWidth = width;
+ this.mHeight = height;
+ this.mRenderingConfig = renderingConfig;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * The width of the slot.
+ */
+ @DataClass.Generated.Member
+ public int getWidth() {
+ return mWidth;
+ }
+
+ /**
+ * The height of the slot.
+ */
+ @DataClass.Generated.Member
+ public int getHeight() {
+ return mHeight;
+ }
+
+ /**
+ * 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) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ RenderInput that = (RenderInput) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && mWidth == that.mWidth
+ && mHeight == that.mHeight
+ && java.util.Objects.equals(mRenderingConfig, that.mRenderingConfig);
+ }
+
+ @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 + mWidth;
+ _hash = 31 * _hash + mHeight;
+ _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/android-35/android/adservices/ondevicepersonalization/RenderInputParcel.java b/android-35/android/adservices/ondevicepersonalization/RenderInputParcel.java
new file mode 100644
index 0000000..ad069bb
--- /dev/null
+++ b/android-35/android/adservices/ondevicepersonalization/RenderInputParcel.java
@@ -0,0 +1,237 @@
+/*
+ * 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.Nullable;
+import android.os.Parcelable;
+
+import com.android.ondevicepersonalization.internal.util.DataClass;
+
+/**
+ * Parcelable version of {@link RenderInput}.
+ * @hide
+ */
+@DataClass(genAidl = false, genHiddenBuilder = true)
+public final class RenderInputParcel implements Parcelable {
+ /** The width of the slot. */
+ private int mWidth = 0;
+
+ /** The height of the slot. */
+ private int mHeight = 0;
+
+ /**
+ * A {@link RenderingConfig} within an {@link ExecuteOutput} that was returned by
+ * {@link IsolatedWorker#onExecute(ExecuteInput, android.os.OutcomeReceiver)}.
+ */
+ @Nullable RenderingConfig mRenderingConfig = null;
+
+
+
+ // 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/RenderInputParcel.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 */ RenderInputParcel(
+ int width,
+ int height,
+ @Nullable RenderingConfig renderingConfig) {
+ this.mWidth = width;
+ this.mHeight = height;
+ this.mRenderingConfig = renderingConfig;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * The width of the slot.
+ */
+ @DataClass.Generated.Member
+ public int getWidth() {
+ return mWidth;
+ }
+
+ /**
+ * The height of the slot.
+ */
+ @DataClass.Generated.Member
+ public int getHeight() {
+ return mHeight;
+ }
+
+ /**
+ * 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 void writeToParcel(@android.annotation.NonNull android.os.Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ byte flg = 0;
+ if (mRenderingConfig != null) flg |= 0x4;
+ dest.writeByte(flg);
+ dest.writeInt(mWidth);
+ dest.writeInt(mHeight);
+ if (mRenderingConfig != null) dest.writeTypedObject(mRenderingConfig, flags);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ RenderInputParcel(@android.annotation.NonNull android.os.Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ byte flg = in.readByte();
+ int width = in.readInt();
+ int height = in.readInt();
+ RenderingConfig renderingConfig = (flg & 0x4) == 0 ? null : (RenderingConfig) in.readTypedObject(RenderingConfig.CREATOR);
+
+ this.mWidth = width;
+ this.mHeight = height;
+ this.mRenderingConfig = renderingConfig;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @android.annotation.NonNull Parcelable.Creator<RenderInputParcel> CREATOR
+ = new Parcelable.Creator<RenderInputParcel>() {
+ @Override
+ public RenderInputParcel[] newArray(int size) {
+ return new RenderInputParcel[size];
+ }
+
+ @Override
+ public RenderInputParcel createFromParcel(@android.annotation.NonNull android.os.Parcel in) {
+ return new RenderInputParcel(in);
+ }
+ };
+
+ /**
+ * A builder for {@link RenderInputParcel}
+ * @hide
+ */
+ @SuppressWarnings("WeakerAccess")
+ @DataClass.Generated.Member
+ public static final class Builder {
+
+ private int mWidth;
+ private int mHeight;
+ private @Nullable RenderingConfig mRenderingConfig;
+
+ private long mBuilderFieldsSet = 0L;
+
+ public Builder() {
+ }
+
+ /**
+ * The width of the slot.
+ */
+ @DataClass.Generated.Member
+ public @android.annotation.NonNull Builder setWidth(int value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x1;
+ mWidth = value;
+ return this;
+ }
+
+ /**
+ * The height of the slot.
+ */
+ @DataClass.Generated.Member
+ public @android.annotation.NonNull Builder setHeight(int value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x2;
+ mHeight = value;
+ return this;
+ }
+
+ /**
+ * A {@link RenderingConfig} within an {@link ExecuteOutput} that was returned by
+ * {@link IsolatedWorker#onExecute(ExecuteInput, android.os.OutcomeReceiver)}.
+ */
+ @DataClass.Generated.Member
+ public @android.annotation.NonNull Builder setRenderingConfig(@android.annotation.NonNull RenderingConfig value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x4;
+ mRenderingConfig = value;
+ return this;
+ }
+
+ /** Builds the instance. This builder should not be touched after calling this! */
+ public @android.annotation.NonNull RenderInputParcel build() {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x8; // Mark builder used
+
+ if ((mBuilderFieldsSet & 0x1) == 0) {
+ mWidth = 0;
+ }
+ if ((mBuilderFieldsSet & 0x2) == 0) {
+ mHeight = 0;
+ }
+ if ((mBuilderFieldsSet & 0x4) == 0) {
+ mRenderingConfig = null;
+ }
+ RenderInputParcel o = new RenderInputParcel(
+ mWidth,
+ mHeight,
+ mRenderingConfig);
+ return o;
+ }
+
+ private void checkNotUsed() {
+ if ((mBuilderFieldsSet & 0x8) != 0) {
+ throw new IllegalStateException(
+ "This Builder should not be reused. Use a new Builder instance instead");
+ }
+ }
+ }
+
+ @DataClass.Generated(
+ time = 1704831939599L,
+ codegenVersion = "1.0.23",
+ sourceFile = "packages/modules/OnDevicePersonalization/framework/java/android/adservices/ondevicepersonalization/RenderInputParcel.java",
+ inputSignatures = "private int mWidth\nprivate int mHeight\n @android.annotation.Nullable android.adservices.ondevicepersonalization.RenderingConfig mRenderingConfig\nclass RenderInputParcel extends java.lang.Object implements [android.os.Parcelable]\[email protected](genAidl=false, genHiddenBuilder=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/android-35/android/adservices/ondevicepersonalization/RenderOutput.java b/android-35/android/adservices/ondevicepersonalization/RenderOutput.java
new file mode 100644
index 0000000..213084a
--- /dev/null
+++ b/android-35/android/adservices/ondevicepersonalization/RenderOutput.java
@@ -0,0 +1,244 @@
+/*
+ * 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 android.os.PersistableBundle;
+
+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 IsolatedWorker#onRender(RenderInput, android.os.OutcomeReceiver)}.
+ *
+ */
+@FlaggedApi(Flags.FLAG_ON_DEVICE_PERSONALIZATION_APIS_ENABLED)
+@DataClass(genBuilder = true, genEqualsHashCode = true)
+public final class RenderOutput {
+ /**
+ * The HTML content to be rendered in a webview. If this is null, the ODP service
+ * generates HTML from the data in {@link #getTemplateId()} and {@link #getTemplateParams()}
+ * as described below.
+ */
+ @DataClass.MaySetToNull
+ @Nullable private String mContent = null;
+
+ /**
+ * A key in the REMOTE_DATA {@link IsolatedService#getRemoteData(RequestToken)} table that
+ * points to an <a href="velocity.apache.org">Apache Velocity</a> template. This is ignored if
+ * {@link #getContent()} is not null.
+ */
+ @DataClass.MaySetToNull
+ @Nullable private String mTemplateId = null;
+
+ /**
+ * The parameters to be populated in the template from {@link #getTemplateId()}. This is
+ * ignored if {@link #getContent()} is not null.
+ */
+ @NonNull private PersistableBundle mTemplateParams = PersistableBundle.EMPTY;
+
+
+
+
+
+ // 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/RenderOutput.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 */ RenderOutput(
+ @Nullable String content,
+ @Nullable String templateId,
+ @NonNull PersistableBundle templateParams) {
+ this.mContent = content;
+ this.mTemplateId = templateId;
+ this.mTemplateParams = templateParams;
+ AnnotationValidations.validate(
+ NonNull.class, null, mTemplateParams);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * The HTML content to be rendered in a webview. If this is null, the ODP service
+ * generates HTML from the data in {@link #getTemplateId()} and {@link #getTemplateParams()}
+ * as described below.
+ */
+ @DataClass.Generated.Member
+ public @Nullable String getContent() {
+ return mContent;
+ }
+
+ /**
+ * A key in the REMOTE_DATA {@link IsolatedService#getRemoteData(RequestToken)} table that
+ * points to an <a href="velocity.apache.org">Apache Velocity</a> template. This is ignored if
+ * {@link #getContent()} is not null.
+ */
+ @DataClass.Generated.Member
+ public @Nullable String getTemplateId() {
+ return mTemplateId;
+ }
+
+ /**
+ * The parameters to be populated in the template from {@link #getTemplateId()}. This is
+ * ignored if {@link #getContent()} is not null.
+ */
+ @DataClass.Generated.Member
+ public @NonNull PersistableBundle getTemplateParams() {
+ return mTemplateParams;
+ }
+
+ @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(RenderOutput other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ RenderOutput that = (RenderOutput) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && java.util.Objects.equals(mContent, that.mContent)
+ && java.util.Objects.equals(mTemplateId, that.mTemplateId)
+ && java.util.Objects.equals(mTemplateParams, that.mTemplateParams);
+ }
+
+ @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(mContent);
+ _hash = 31 * _hash + java.util.Objects.hashCode(mTemplateId);
+ _hash = 31 * _hash + java.util.Objects.hashCode(mTemplateParams);
+ return _hash;
+ }
+
+ /**
+ * A builder for {@link RenderOutput}
+ */
+ @SuppressWarnings("WeakerAccess")
+ @DataClass.Generated.Member
+ public static final class Builder {
+
+ private @Nullable String mContent;
+ private @Nullable String mTemplateId;
+ private @NonNull PersistableBundle mTemplateParams;
+
+ private long mBuilderFieldsSet = 0L;
+
+ public Builder() {
+ }
+
+ /**
+ * The HTML content to be rendered in a webview. If this is null, the ODP service
+ * generates HTML from the data in {@link #getTemplateId()} and {@link #getTemplateParams()}
+ * as described below.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setContent(@Nullable String value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x1;
+ mContent = value;
+ return this;
+ }
+
+ /**
+ * A key in the REMOTE_DATA {@link IsolatedService#getRemoteData(RequestToken)} table that
+ * points to an <a href="velocity.apache.org">Apache Velocity</a> template. This is ignored if
+ * {@link #getContent()} is not null.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setTemplateId(@Nullable String value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x2;
+ mTemplateId = value;
+ return this;
+ }
+
+ /**
+ * The parameters to be populated in the template from {@link #getTemplateId()}. This is
+ * ignored if {@link #getContent()} is not null.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setTemplateParams(@NonNull PersistableBundle value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x4;
+ mTemplateParams = value;
+ return this;
+ }
+
+ /** Builds the instance. This builder should not be touched after calling this! */
+ public @NonNull RenderOutput build() {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x8; // Mark builder used
+
+ if ((mBuilderFieldsSet & 0x1) == 0) {
+ mContent = null;
+ }
+ if ((mBuilderFieldsSet & 0x2) == 0) {
+ mTemplateId = null;
+ }
+ if ((mBuilderFieldsSet & 0x4) == 0) {
+ mTemplateParams = PersistableBundle.EMPTY;
+ }
+ RenderOutput o = new RenderOutput(
+ mContent,
+ mTemplateId,
+ mTemplateParams);
+ return o;
+ }
+
+ private void checkNotUsed() {
+ if ((mBuilderFieldsSet & 0x8) != 0) {
+ throw new IllegalStateException(
+ "This Builder should not be reused. Use a new Builder instance instead");
+ }
+ }
+ }
+
+ @DataClass.Generated(
+ time = 1707253768205L,
+ codegenVersion = "1.0.23",
+ sourceFile = "packages/modules/OnDevicePersonalization/framework/java/android/adservices/ondevicepersonalization/RenderOutput.java",
+ inputSignatures = "private @com.android.ondevicepersonalization.internal.util.DataClass.MaySetToNull @android.annotation.Nullable java.lang.String mContent\nprivate @com.android.ondevicepersonalization.internal.util.DataClass.MaySetToNull @android.annotation.Nullable java.lang.String mTemplateId\nprivate @android.annotation.NonNull android.os.PersistableBundle mTemplateParams\nclass RenderOutput extends java.lang.Object implements []\[email protected](genBuilder=true, genEqualsHashCode=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/android-35/android/adservices/ondevicepersonalization/RenderOutputParcel.java b/android-35/android/adservices/ondevicepersonalization/RenderOutputParcel.java
new file mode 100644
index 0000000..afd4cfa
--- /dev/null
+++ b/android-35/android/adservices/ondevicepersonalization/RenderOutputParcel.java
@@ -0,0 +1,197 @@
+/*
+ * 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.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcelable;
+import android.os.PersistableBundle;
+
+import com.android.ondevicepersonalization.internal.util.AnnotationValidations;
+import com.android.ondevicepersonalization.internal.util.DataClass;
+
+/**
+ * Parcelable version of {@link RenderOutput}.
+ * @hide
+ */
+@DataClass(genAidl = false, genBuilder = false)
+public final class RenderOutputParcel implements Parcelable {
+ /**
+ * The HTML content to be rendered in a webview. If this is null, the ODP service
+ * generates HTML from the data in {@link #getTemplateId()} and {@link #getTemplateParams()}
+ * as described below.
+ */
+ @Nullable private String mContent = null;
+
+ /**
+ * A key in the REMOTE_DATA {@link IsolatedService#getRemoteData(RequestToken)} table that
+ * points to an <a href="velocity.apache.org">Apache Velocity</a> template. This is ignored if
+ * {@link #getContent()} is not null.
+ */
+ @Nullable private String mTemplateId = null;
+
+ /**
+ * The parameters to be populated in the template from {@link #getTemplateId()}. This is
+ * ignored if {@link #getContent()} is not null.
+ */
+ @NonNull private PersistableBundle mTemplateParams = PersistableBundle.EMPTY;
+
+ /** @hide */
+ public RenderOutputParcel(@NonNull RenderOutput value) {
+ this(value.getContent(), value.getTemplateId(), value.getTemplateParams());
+ }
+
+
+
+ // 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/RenderOutputParcel.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /**
+ * Creates a new RenderOutputParcel.
+ *
+ * @param content
+ * The HTML content to be rendered in a webview. If this is null, the ODP service
+ * generates HTML from the data in {@link #getTemplateId()} and {@link #getTemplateParams()}
+ * as described below.
+ * @param templateId
+ * A key in the REMOTE_DATA {@link IsolatedService#getRemoteData(RequestToken)} table that
+ * points to an <a href="velocity.apache.org">Apache Velocity</a> template. This is ignored if
+ * {@link #getContent()} is not null.
+ * @param templateParams
+ * The parameters to be populated in the template from {@link #getTemplateId()}. This is
+ * ignored if {@link #getContent()} is not null.
+ */
+ @DataClass.Generated.Member
+ public RenderOutputParcel(
+ @Nullable String content,
+ @Nullable String templateId,
+ @NonNull PersistableBundle templateParams) {
+ this.mContent = content;
+ this.mTemplateId = templateId;
+ this.mTemplateParams = templateParams;
+ AnnotationValidations.validate(
+ NonNull.class, null, mTemplateParams);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * The HTML content to be rendered in a webview. If this is null, the ODP service
+ * generates HTML from the data in {@link #getTemplateId()} and {@link #getTemplateParams()}
+ * as described below.
+ */
+ @DataClass.Generated.Member
+ public @Nullable String getContent() {
+ return mContent;
+ }
+
+ /**
+ * A key in the REMOTE_DATA {@link IsolatedService#getRemoteData(RequestToken)} table that
+ * points to an <a href="velocity.apache.org">Apache Velocity</a> template. This is ignored if
+ * {@link #getContent()} is not null.
+ */
+ @DataClass.Generated.Member
+ public @Nullable String getTemplateId() {
+ return mTemplateId;
+ }
+
+ /**
+ * The parameters to be populated in the template from {@link #getTemplateId()}. This is
+ * ignored if {@link #getContent()} is not null.
+ */
+ @DataClass.Generated.Member
+ public @NonNull PersistableBundle getTemplateParams() {
+ return mTemplateParams;
+ }
+
+ @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) { ... }
+
+ byte flg = 0;
+ if (mContent != null) flg |= 0x1;
+ if (mTemplateId != null) flg |= 0x2;
+ dest.writeByte(flg);
+ if (mContent != null) dest.writeString(mContent);
+ if (mTemplateId != null) dest.writeString(mTemplateId);
+ dest.writeTypedObject(mTemplateParams, flags);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ RenderOutputParcel(@NonNull android.os.Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ byte flg = in.readByte();
+ String content = (flg & 0x1) == 0 ? null : in.readString();
+ String templateId = (flg & 0x2) == 0 ? null : in.readString();
+ PersistableBundle templateParams = (PersistableBundle) in.readTypedObject(PersistableBundle.CREATOR);
+
+ this.mContent = content;
+ this.mTemplateId = templateId;
+ this.mTemplateParams = templateParams;
+ AnnotationValidations.validate(
+ NonNull.class, null, mTemplateParams);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<RenderOutputParcel> CREATOR
+ = new Parcelable.Creator<RenderOutputParcel>() {
+ @Override
+ public RenderOutputParcel[] newArray(int size) {
+ return new RenderOutputParcel[size];
+ }
+
+ @Override
+ public RenderOutputParcel createFromParcel(@NonNull android.os.Parcel in) {
+ return new RenderOutputParcel(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1698864341247L,
+ codegenVersion = "1.0.23",
+ sourceFile = "packages/modules/OnDevicePersonalization/framework/java/android/adservices/ondevicepersonalization/RenderOutputParcel.java",
+ inputSignatures = "private @android.annotation.Nullable java.lang.String mContent\nprivate @android.annotation.Nullable java.lang.String mTemplateId\nprivate @android.annotation.NonNull android.os.PersistableBundle mTemplateParams\nclass RenderOutputParcel 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/android-35/android/adservices/ondevicepersonalization/RenderingConfig.java b/android-35/android/adservices/ondevicepersonalization/RenderingConfig.java
new file mode 100644
index 0000000..5e26adf
--- /dev/null
+++ b/android-35/android/adservices/ondevicepersonalization/RenderingConfig.java
@@ -0,0 +1,223 @@
+/*
+ * 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.os.Parcelable;
+
+import com.android.adservices.ondevicepersonalization.flags.Flags;
+import com.android.ondevicepersonalization.internal.util.AnnotationValidations;
+import com.android.ondevicepersonalization.internal.util.DataClass;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Information returned by
+ * {@link IsolatedWorker#onExecute(ExecuteInput, android.os.OutcomeReceiver)}
+ * that is used in a subesequent call to
+ * {@link IsolatedWorker#onRender(RenderInput, android.os.OutcomeReceiver)} to identify the
+ * content to be displayed in a single {@link android.view.View}.
+ *
+ */
+@FlaggedApi(Flags.FLAG_ON_DEVICE_PERSONALIZATION_APIS_ENABLED)
+@DataClass(genBuilder = true, genEqualsHashCode = true)
+public final class RenderingConfig implements Parcelable {
+ /**
+ * A List of keys in the REMOTE_DATA
+ * {@link IsolatedService#getRemoteData(RequestToken)}
+ * table that identify the content to be rendered.
+ **/
+ @DataClass.PluralOf("key")
+ @NonNull private List<String> mKeys = Collections.emptyList();
+
+
+
+ // 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/RenderingConfig.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 */ RenderingConfig(
+ @NonNull List<String> keys) {
+ this.mKeys = keys;
+ AnnotationValidations.validate(
+ NonNull.class, null, mKeys);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * A List of keys in the REMOTE_DATA
+ * {@link IsolatedService#getRemoteData(RequestToken)}
+ * table that identify the content to be rendered.
+ */
+ @DataClass.Generated.Member
+ public @NonNull List<String> getKeys() {
+ return mKeys;
+ }
+
+ @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(RenderingConfig other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ RenderingConfig that = (RenderingConfig) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && java.util.Objects.equals(mKeys, that.mKeys);
+ }
+
+ @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(mKeys);
+ return _hash;
+ }
+
+ @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.writeStringList(mKeys);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ RenderingConfig(@NonNull android.os.Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ List<String> keys = new java.util.ArrayList<>();
+ in.readStringList(keys);
+
+ this.mKeys = keys;
+ AnnotationValidations.validate(
+ NonNull.class, null, mKeys);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<RenderingConfig> CREATOR
+ = new Parcelable.Creator<RenderingConfig>() {
+ @Override
+ public RenderingConfig[] newArray(int size) {
+ return new RenderingConfig[size];
+ }
+
+ @Override
+ public RenderingConfig createFromParcel(@NonNull android.os.Parcel in) {
+ return new RenderingConfig(in);
+ }
+ };
+
+ /**
+ * A builder for {@link RenderingConfig}
+ */
+ @SuppressWarnings("WeakerAccess")
+ @DataClass.Generated.Member
+ public static final class Builder {
+
+ private @NonNull List<String> mKeys;
+
+ private long mBuilderFieldsSet = 0L;
+
+ public Builder() {
+ }
+
+ /**
+ * A List of keys in the REMOTE_DATA
+ * {@link IsolatedService#getRemoteData(RequestToken)}
+ * table that identify the content to be rendered.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setKeys(@NonNull List<String> value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x1;
+ mKeys = value;
+ return this;
+ }
+
+ /** @see #setKeys */
+ @DataClass.Generated.Member
+ public @NonNull Builder addKey(@NonNull String value) {
+ if (mKeys == null) setKeys(new java.util.ArrayList<>());
+ mKeys.add(value);
+ return this;
+ }
+
+ /** Builds the instance. This builder should not be touched after calling this! */
+ public @NonNull RenderingConfig build() {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x2; // Mark builder used
+
+ if ((mBuilderFieldsSet & 0x1) == 0) {
+ mKeys = Collections.emptyList();
+ }
+ RenderingConfig o = new RenderingConfig(
+ mKeys);
+ 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 = 1697132616124L,
+ codegenVersion = "1.0.23",
+ sourceFile = "packages/modules/OnDevicePersonalization/framework/java/android/adservices/ondevicepersonalization/RenderingConfig.java",
+ inputSignatures = "private @com.android.ondevicepersonalization.internal.util.DataClass.PluralOf(\"key\") @android.annotation.NonNull java.util.List<java.lang.String> mKeys\nclass RenderingConfig extends java.lang.Object implements [android.os.Parcelable]\[email protected](genBuilder=true, genEqualsHashCode=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/android-35/android/adservices/ondevicepersonalization/RequestLogRecord.java b/android-35/android/adservices/ondevicepersonalization/RequestLogRecord.java
new file mode 100644
index 0000000..8ec3409
--- /dev/null
+++ b/android-35/android/adservices/ondevicepersonalization/RequestLogRecord.java
@@ -0,0 +1,317 @@
+/*
+ * 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.content.ContentValues;
+import android.os.Parcelable;
+
+import com.android.adservices.ondevicepersonalization.flags.Flags;
+import com.android.ondevicepersonalization.internal.util.AnnotationValidations;
+import com.android.ondevicepersonalization.internal.util.DataClass;
+
+import java.time.Instant;
+import java.util.Collections;
+import java.util.List;
+
+// TODO(b/289102463): Add a link to the public doc for the REQUESTS table when available.
+/**
+ * Contains data that will be written to the REQUESTS table at the end of a call to
+ * {@link IsolatedWorker#onExecute(ExecuteInput, android.os.OutcomeReceiver)}.
+ * A single {@link RequestLogRecord} is appended to the
+ * REQUESTS table if it is present in the output of one of the methods in {@link IsolatedWorker}.
+ * The contents of the REQUESTS table can be consumed by Federated Learning facilitated model
+ * training, or Federated Analytics facilitated cross-device statistical analysis.
+ */
+@FlaggedApi(Flags.FLAG_ON_DEVICE_PERSONALIZATION_APIS_ENABLED)
+@DataClass(genBuilder = true, genEqualsHashCode = true)
+public final class RequestLogRecord implements Parcelable {
+ /**
+ * A List of rows, each containing a {@link ContentValues}.
+ **/
+ @DataClass.PluralOf("row")
+ @NonNull List<ContentValues> mRows = Collections.emptyList();
+
+ /**
+ * Internal id for the RequestLogRecord.
+ * @hide
+ */
+ private long mRequestId = 0;
+
+ /**
+ * Time of the request in milliseconds
+ * @hide
+ */
+ private long mTimeMillis = 0;
+
+ /**
+ * Returns the timestamp of this record.
+ */
+ @NonNull public Instant getTime() {
+ return Instant.ofEpochMilli(getTimeMillis());
+ }
+
+ abstract static class BaseBuilder {
+ /**
+ * @hide
+ */
+ public abstract Builder setTimeMillis(long value);
+ }
+
+
+
+ // 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/RequestLogRecord.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 */ RequestLogRecord(
+ @NonNull List<ContentValues> rows,
+ long requestId,
+ long timeMillis) {
+ this.mRows = rows;
+ AnnotationValidations.validate(
+ NonNull.class, null, mRows);
+ this.mRequestId = requestId;
+ this.mTimeMillis = timeMillis;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * A List of rows, each containing a {@link ContentValues}.
+ */
+ @DataClass.Generated.Member
+ public @NonNull List<ContentValues> getRows() {
+ return mRows;
+ }
+
+ /**
+ * Internal id for the RequestLogRecord.
+ *
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public long getRequestId() {
+ return mRequestId;
+ }
+
+ /**
+ * Time of the request in milliseconds
+ *
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public long getTimeMillis() {
+ return mTimeMillis;
+ }
+
+ @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(RequestLogRecord other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ RequestLogRecord that = (RequestLogRecord) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && java.util.Objects.equals(mRows, that.mRows)
+ && mRequestId == that.mRequestId
+ && mTimeMillis == that.mTimeMillis;
+ }
+
+ @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(mRows);
+ _hash = 31 * _hash + Long.hashCode(mRequestId);
+ _hash = 31 * _hash + Long.hashCode(mTimeMillis);
+ return _hash;
+ }
+
+ @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.writeParcelableList(mRows, flags);
+ dest.writeLong(mRequestId);
+ dest.writeLong(mTimeMillis);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ RequestLogRecord(@NonNull android.os.Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ List<ContentValues> rows = new java.util.ArrayList<>();
+ in.readParcelableList(rows, ContentValues.class.getClassLoader());
+ long requestId = in.readLong();
+ long timeMillis = in.readLong();
+
+ this.mRows = rows;
+ AnnotationValidations.validate(
+ NonNull.class, null, mRows);
+ this.mRequestId = requestId;
+ this.mTimeMillis = timeMillis;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<RequestLogRecord> CREATOR
+ = new Parcelable.Creator<RequestLogRecord>() {
+ @Override
+ public RequestLogRecord[] newArray(int size) {
+ return new RequestLogRecord[size];
+ }
+
+ @Override
+ public RequestLogRecord createFromParcel(@NonNull android.os.Parcel in) {
+ return new RequestLogRecord(in);
+ }
+ };
+
+ /**
+ * A builder for {@link RequestLogRecord}
+ */
+ @SuppressWarnings("WeakerAccess")
+ @DataClass.Generated.Member
+ public static final class Builder extends BaseBuilder {
+
+ private @NonNull List<ContentValues> mRows;
+ private long mRequestId;
+ private long mTimeMillis;
+
+ private long mBuilderFieldsSet = 0L;
+
+ public Builder() {
+ }
+
+ /**
+ * A List of rows, each containing a {@link ContentValues}.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setRows(@NonNull List<ContentValues> value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x1;
+ mRows = value;
+ return this;
+ }
+
+ /** @see #setRows */
+ @DataClass.Generated.Member
+ public @NonNull Builder addRow(@NonNull ContentValues value) {
+ if (mRows == null) setRows(new java.util.ArrayList<>());
+ mRows.add(value);
+ return this;
+ }
+
+ /**
+ * Internal id for the RequestLogRecord.
+ *
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setRequestId(long value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x2;
+ mRequestId = value;
+ return this;
+ }
+
+ /**
+ * Time of the request in milliseconds
+ *
+ * @hide
+ */
+ @DataClass.Generated.Member
+ @Override
+ public @NonNull Builder setTimeMillis(long value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x4;
+ mTimeMillis = value;
+ return this;
+ }
+
+ /** Builds the instance. This builder should not be touched after calling this! */
+ public @NonNull RequestLogRecord build() {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x8; // Mark builder used
+
+ if ((mBuilderFieldsSet & 0x1) == 0) {
+ mRows = Collections.emptyList();
+ }
+ if ((mBuilderFieldsSet & 0x2) == 0) {
+ mRequestId = 0;
+ }
+ if ((mBuilderFieldsSet & 0x4) == 0) {
+ mTimeMillis = 0;
+ }
+ RequestLogRecord o = new RequestLogRecord(
+ mRows,
+ mRequestId,
+ mTimeMillis);
+ return o;
+ }
+
+ private void checkNotUsed() {
+ if ((mBuilderFieldsSet & 0x8) != 0) {
+ throw new IllegalStateException(
+ "This Builder should not be reused. Use a new Builder instance instead");
+ }
+ }
+ }
+
+ @DataClass.Generated(
+ time = 1698962042612L,
+ codegenVersion = "1.0.23",
+ sourceFile = "packages/modules/OnDevicePersonalization/framework/java/android/adservices/ondevicepersonalization/RequestLogRecord.java",
+ inputSignatures = " @com.android.ondevicepersonalization.internal.util.DataClass.PluralOf(\"row\") @android.annotation.NonNull java.util.List<android.content.ContentValues> mRows\nprivate long mRequestId\nprivate long mTimeMillis\npublic @android.annotation.NonNull java.time.Instant getTime()\nclass RequestLogRecord extends java.lang.Object implements [android.os.Parcelable]\npublic abstract android.adservices.ondevicepersonalization.RequestLogRecord.Builder setTimeMillis(long)\nclass BaseBuilder extends java.lang.Object implements []\[email protected](genBuilder=true, genEqualsHashCode=true)\npublic abstract android.adservices.ondevicepersonalization.RequestLogRecord.Builder setTimeMillis(long)\nclass BaseBuilder extends java.lang.Object implements []")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/android-35/android/adservices/ondevicepersonalization/RequestToken.java b/android-35/android/adservices/ondevicepersonalization/RequestToken.java
new file mode 100644
index 0000000..89f4512
--- /dev/null
+++ b/android-35/android/adservices/ondevicepersonalization/RequestToken.java
@@ -0,0 +1,91 @@
+/*
+ * 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.adservices.ondevicepersonalization.aidl.IDataAccessService;
+import android.adservices.ondevicepersonalization.aidl.IFederatedComputeService;
+import android.adservices.ondevicepersonalization.aidl.IIsolatedModelService;
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.SystemClock;
+
+import com.android.adservices.ondevicepersonalization.flags.Flags;
+
+import java.util.Objects;
+
+/**
+ * An opaque token that identifies the current request to an {@link IsolatedService}. This token
+ * must be passed as a parameter to all service methods that depend on per-request state.
+ */
+@FlaggedApi(Flags.FLAG_ON_DEVICE_PERSONALIZATION_APIS_ENABLED)
+public class RequestToken {
+ @NonNull
+ private final IDataAccessService mDataAccessService;
+
+ @Nullable
+ private final IFederatedComputeService mFcService;
+
+ @Nullable private final IIsolatedModelService mModelService;
+
+ @Nullable
+ private final UserData mUserData;
+
+ private final long mStartTimeMillis;
+
+ /** @hide */
+ RequestToken(
+ @NonNull IDataAccessService binder,
+ @Nullable IFederatedComputeService fcServiceBinder,
+ @Nullable IIsolatedModelService modelServiceBinder,
+ @Nullable UserData userData) {
+ mDataAccessService = Objects.requireNonNull(binder);
+ mFcService = fcServiceBinder;
+ mModelService = modelServiceBinder;
+ mUserData = userData;
+ mStartTimeMillis = SystemClock.elapsedRealtime();
+ }
+
+ /** @hide */
+ @NonNull
+ IDataAccessService getDataAccessService() {
+ return mDataAccessService;
+ }
+
+ /** @hide */
+ @Nullable
+ IFederatedComputeService getFederatedComputeService() {
+ return mFcService;
+ }
+
+ /** @hide */
+ @Nullable
+ IIsolatedModelService getModelService() {
+ return mModelService;
+ }
+
+ /** @hide */
+ @Nullable
+ UserData getUserData() {
+ return mUserData;
+ }
+
+ /** @hide */
+ long getStartTimeMillis() {
+ return mStartTimeMillis;
+ }
+}
diff --git a/android-35/android/adservices/ondevicepersonalization/SurfacePackageToken.java b/android-35/android/adservices/ondevicepersonalization/SurfacePackageToken.java
new file mode 100644
index 0000000..d412de9
--- /dev/null
+++ b/android-35/android/adservices/ondevicepersonalization/SurfacePackageToken.java
@@ -0,0 +1,43 @@
+/*
+ * 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;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+
+import com.android.adservices.ondevicepersonalization.flags.Flags;
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * 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) {
+ mTokenString = tokenString;
+ }
+
+ /** @hide */
+ @VisibleForTesting
+ @NonNull public String getTokenString() {
+ return mTokenString;
+ }
+}
diff --git a/android-35/android/adservices/ondevicepersonalization/TrainingExampleRecord.java b/android-35/android/adservices/ondevicepersonalization/TrainingExampleRecord.java
new file mode 100644
index 0000000..f9bfa4a
--- /dev/null
+++ b/android-35/android/adservices/ondevicepersonalization/TrainingExampleRecord.java
@@ -0,0 +1,215 @@
+/*
+ * 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.Nullable;
+import android.os.Parcelable;
+
+import com.android.adservices.ondevicepersonalization.flags.Flags;
+import com.android.ondevicepersonalization.internal.util.DataClass;
+
+/**
+ * One record of {@link TrainingExamplesOutput}.
+ */
+@FlaggedApi(Flags.FLAG_ON_DEVICE_PERSONALIZATION_APIS_ENABLED)
+@DataClass(genBuilder = true, genAidl = false)
+public final class TrainingExampleRecord implements Parcelable {
+ /**
+ * Training example byte arrays. The format is a binary serialized <a
+ * href="https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/example/example.proto">
+ * tensorflow.Example</a> proto. The maximum allowed example size is 50KB.
+ */
+ @DataClass.MaySetToNull
+ @Nullable private byte[] mTrainingExample = null;
+
+ /**
+ * The resumption token byte arrays corresponding to training examples. The last processed
+ * example's corresponding resumption token will be passed to {@link
+ * IsolatedWorker#onTrainingExamples} to support resumption.
+ */
+ @DataClass.MaySetToNull
+ @Nullable private byte[] mResumptionToken = null;
+
+
+
+ // 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/TrainingExampleRecord.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 */ TrainingExampleRecord(
+ @Nullable byte[] trainingExample,
+ @Nullable byte[] resumptionToken) {
+ this.mTrainingExample = trainingExample;
+ this.mResumptionToken = resumptionToken;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * Training example byte arrays. The format is a binary serialized <a
+ * href="https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/example/example.proto">
+ * tensorflow.Example</a> proto. The maximum allowed example size is 50KB.
+ */
+ @DataClass.Generated.Member
+ public @Nullable byte[] getTrainingExample() {
+ return mTrainingExample;
+ }
+
+ /**
+ * The resumption token byte arrays corresponding to training examples. The last processed
+ * example's corresponding resumption token will be passed to {@link
+ * IsolatedWorker#onTrainingExamples} to support resumption.
+ */
+ @DataClass.Generated.Member
+ public @Nullable byte[] getResumptionToken() {
+ return mResumptionToken;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@android.annotation.NonNull android.os.Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ dest.writeByteArray(mTrainingExample);
+ dest.writeByteArray(mResumptionToken);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ TrainingExampleRecord(@android.annotation.NonNull android.os.Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ byte[] trainingExample = in.createByteArray();
+ byte[] resumptionToken = in.createByteArray();
+
+ this.mTrainingExample = trainingExample;
+ this.mResumptionToken = resumptionToken;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @android.annotation.NonNull Parcelable.Creator<TrainingExampleRecord> CREATOR
+ = new Parcelable.Creator<TrainingExampleRecord>() {
+ @Override
+ public TrainingExampleRecord[] newArray(int size) {
+ return new TrainingExampleRecord[size];
+ }
+
+ @Override
+ public TrainingExampleRecord createFromParcel(@android.annotation.NonNull android.os.Parcel in) {
+ return new TrainingExampleRecord(in);
+ }
+ };
+
+ /**
+ * A builder for {@link TrainingExampleRecord}
+ */
+ @SuppressWarnings("WeakerAccess")
+ @DataClass.Generated.Member
+ public static final class Builder {
+
+ private @Nullable byte[] mTrainingExample;
+ private @Nullable byte[] mResumptionToken;
+
+ private long mBuilderFieldsSet = 0L;
+
+ public Builder() {
+ }
+
+ /**
+ * Training example byte arrays. The format is a binary serialized <a
+ * href="https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/example/example.proto">
+ * tensorflow.Example</a> proto. The maximum allowed example size is 50KB.
+ */
+ @DataClass.Generated.Member
+ public @android.annotation.NonNull Builder setTrainingExample(@Nullable byte... value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x1;
+ mTrainingExample = value;
+ return this;
+ }
+
+ /**
+ * The resumption token byte arrays corresponding to training examples. The last processed
+ * example's corresponding resumption token will be passed to {@link
+ * IsolatedWorker#onTrainingExamples} to support resumption.
+ */
+ @DataClass.Generated.Member
+ public @android.annotation.NonNull Builder setResumptionToken(@Nullable byte... value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x2;
+ mResumptionToken = value;
+ return this;
+ }
+
+ /** Builds the instance. This builder should not be touched after calling this! */
+ public @android.annotation.NonNull TrainingExampleRecord build() {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x4; // Mark builder used
+
+ if ((mBuilderFieldsSet & 0x1) == 0) {
+ mTrainingExample = null;
+ }
+ if ((mBuilderFieldsSet & 0x2) == 0) {
+ mResumptionToken = null;
+ }
+ TrainingExampleRecord o = new TrainingExampleRecord(
+ mTrainingExample,
+ mResumptionToken);
+ return o;
+ }
+
+ private void checkNotUsed() {
+ if ((mBuilderFieldsSet & 0x4) != 0) {
+ throw new IllegalStateException(
+ "This Builder should not be reused. Use a new Builder instance instead");
+ }
+ }
+ }
+
+ @DataClass.Generated(
+ time = 1707253849218L,
+ codegenVersion = "1.0.23",
+ sourceFile = "packages/modules/OnDevicePersonalization/framework/java/android/adservices/ondevicepersonalization/TrainingExampleRecord.java",
+ inputSignatures = "private @com.android.ondevicepersonalization.internal.util.DataClass.MaySetToNull @android.annotation.Nullable byte[] mTrainingExample\nprivate @com.android.ondevicepersonalization.internal.util.DataClass.MaySetToNull @android.annotation.Nullable byte[] mResumptionToken\nclass TrainingExampleRecord extends java.lang.Object implements [android.os.Parcelable]\[email protected](genBuilder=true, genAidl=false)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/android-35/android/adservices/ondevicepersonalization/TrainingExamplesInput.java b/android-35/android/adservices/ondevicepersonalization/TrainingExamplesInput.java
new file mode 100644
index 0000000..7fe868c
--- /dev/null
+++ b/android-35/android/adservices/ondevicepersonalization/TrainingExamplesInput.java
@@ -0,0 +1,199 @@
+/*
+ * 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 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
+ * FederatedComputeInput#getPopulationName}.
+ */
+ @NonNull private String mPopulationName = "";
+
+ /**
+ * The name of the task within the population. It should match task plan configured at remote
+ * federated compute server. One population may have multiple tasks. The task name can be used
+ * to uniquely identify the job.
+ */
+ @NonNull private String mTaskName = "";
+
+ /**
+ * Token used to support the resumption of training. If client app wants to use resumption token
+ * to track what examples are already used in previous federated compute jobs, it need set
+ * {@link TrainingExampleRecord.Builder#setResumptionToken}, OnDevicePersonalization will store
+ * it and pass it here for generating new training examples.
+ */
+ @Nullable private byte[] mResumptionToken = null;
+
+ /**
+ * The data collection name to use to create training examples.
+ *
+ * @hide
+ */
+ @Nullable private String mCollectionUri;
+
+ /** @hide */
+ public TrainingExamplesInput(@NonNull TrainingExamplesInputParcel parcel) {
+ this(
+ parcel.getPopulationName(),
+ parcel.getTaskName(),
+ parcel.getResumptionToken(),
+ parcel.getCollectionUri());
+ }
+
+ // 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.
+ *
+ * @param populationName The name of the federated compute population. It should match the
+ * population name in {@link FederatedComputeInput#getPopulationName}.
+ * @param taskName The name of the task within the population. It should match task plan
+ * configured at remote federated compute server. One population may have multiple tasks.
+ * The task name can be used to uniquely identify the job.
+ * @param resumptionToken Token used to support the resumption of training. If client app wants
+ * to use resumption token to track what examples are already used in previous federated
+ * compute jobs, it need set {@link TrainingExampleRecord.Builder#setResumptionToken},
+ * OnDevicePersonalization will store it and pass it here for generating new training
+ * examples.
+ * @param collectionUri The data collection name to use to create training examples.
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public TrainingExamplesInput(
+ @NonNull String populationName,
+ @NonNull String taskName,
+ @Nullable byte[] resumptionToken,
+ @Nullable String collectionUri) {
+ this.mPopulationName = populationName;
+ AnnotationValidations.validate(NonNull.class, null, mPopulationName);
+ this.mTaskName = taskName;
+ AnnotationValidations.validate(NonNull.class, null, mTaskName);
+ this.mResumptionToken = resumptionToken;
+ this.mCollectionUri = collectionUri;
+
+ // 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;
+ }
+
+ /**
+ * The name of the task within the population. It should match task plan configured at remote
+ * 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;
+ }
+
+ /**
+ * Token used to support the resumption of training. If client app wants to use resumption token
+ * to track what examples are already used in previous federated compute jobs, it need set
+ * {@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.
+ *
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public @Nullable String getCollectionUri() {
+ return mCollectionUri;
+ }
+
+ @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")
+ TrainingExamplesInput that = (TrainingExamplesInput) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && java.util.Objects.equals(mPopulationName, that.mPopulationName)
+ && java.util.Objects.equals(mTaskName, that.mTaskName)
+ && java.util.Arrays.equals(mResumptionToken, that.mResumptionToken)
+ && java.util.Objects.equals(mCollectionUri, that.mCollectionUri);
+ }
+
+ @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);
+ _hash = 31 * _hash + java.util.Arrays.hashCode(mResumptionToken);
+ _hash = 31 * _hash + java.util.Objects.hashCode(mCollectionUri);
+ return _hash;
+ }
+
+ @DataClass.Generated(
+ time = 1714844498373L,
+ 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 mCollectionUri\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/android-35/android/adservices/ondevicepersonalization/TrainingExamplesInputParcel.java b/android-35/android/adservices/ondevicepersonalization/TrainingExamplesInputParcel.java
new file mode 100644
index 0000000..a039a3b
--- /dev/null
+++ b/android-35/android/adservices/ondevicepersonalization/TrainingExamplesInputParcel.java
@@ -0,0 +1,266 @@
+/*
+ * 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.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcelable;
+
+import com.android.ondevicepersonalization.internal.util.AnnotationValidations;
+import com.android.ondevicepersonalization.internal.util.DataClass;
+
+/**
+ * Parcelable version of {@link TrainingExamplesInput}
+ *
+ * @hide
+ */
+@DataClass(genAidl = false, genHiddenBuilder = true)
+public final class TrainingExamplesInputParcel implements Parcelable {
+ /** The name of the federated compute population. */
+ @NonNull private String mPopulationName = "";
+
+ /**
+ * The name of the task within the population. One population may have multiple tasks. The task
+ * name can be used to uniquely identify the job.
+ */
+ @NonNull private String mTaskName = "";
+
+ /** Token used to support the resumption of training. */
+ @Nullable private byte[] mResumptionToken = null;
+
+ /** The data collection name to use to create training examples. */
+ @Nullable private String mCollectionUri;
+
+ // 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/TrainingExamplesInputParcel.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 */ TrainingExamplesInputParcel(
+ @NonNull String populationName,
+ @NonNull String taskName,
+ @Nullable byte[] resumptionToken,
+ @Nullable String collectionUri) {
+ this.mPopulationName = populationName;
+ AnnotationValidations.validate(NonNull.class, null, mPopulationName);
+ this.mTaskName = taskName;
+ AnnotationValidations.validate(NonNull.class, null, mTaskName);
+ this.mResumptionToken = resumptionToken;
+ this.mCollectionUri = collectionUri;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * The name of the federated compute population.
+ */
+ @DataClass.Generated.Member
+ public @NonNull String getPopulationName() {
+ return mPopulationName;
+ }
+
+ /**
+ * The name of the task within the population. 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;
+ }
+
+ /**
+ * Token used to support the resumption of training.
+ */
+ @DataClass.Generated.Member
+ public @Nullable byte[] getResumptionToken() {
+ return mResumptionToken;
+ }
+
+ /** The data collection name to use to create training examples. */
+ @DataClass.Generated.Member
+ public @Nullable String getCollectionUri() {
+ return mCollectionUri;
+ }
+
+ @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) { ... }
+
+ byte flg = 0;
+ if (mCollectionUri != null) flg |= 0x8;
+ dest.writeByte(flg);
+ dest.writeString(mPopulationName);
+ dest.writeString(mTaskName);
+ dest.writeByteArray(mResumptionToken);
+ if (mCollectionUri != null) dest.writeString(mCollectionUri);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ TrainingExamplesInputParcel(@NonNull android.os.Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ byte flg = in.readByte();
+ String populationName = in.readString();
+ String taskName = in.readString();
+ byte[] resumptionToken = in.createByteArray();
+ String collectionUri = (flg & 0x8) == 0 ? null : in.readString();
+
+ this.mPopulationName = populationName;
+ AnnotationValidations.validate(NonNull.class, null, mPopulationName);
+ this.mTaskName = taskName;
+ AnnotationValidations.validate(
+ NonNull.class, null, mTaskName);
+ this.mResumptionToken = resumptionToken;
+ this.mCollectionUri = collectionUri;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<TrainingExamplesInputParcel> CREATOR =
+ new Parcelable.Creator<TrainingExamplesInputParcel>() {
+ @Override
+ public TrainingExamplesInputParcel[] newArray(int size) {
+ return new TrainingExamplesInputParcel[size];
+ }
+
+ @Override
+ public TrainingExamplesInputParcel createFromParcel(@NonNull android.os.Parcel in) {
+ return new TrainingExamplesInputParcel(in);
+ }
+ };
+
+ /**
+ * A builder for {@link TrainingExamplesInputParcel}
+ * @hide
+ */
+ @SuppressWarnings("WeakerAccess")
+ @DataClass.Generated.Member
+ public static final class Builder {
+
+ private @NonNull String mPopulationName;
+ private @NonNull String mTaskName;
+ private @Nullable byte[] mResumptionToken;
+ private @Nullable String mCollectionUri;
+
+ private long mBuilderFieldsSet = 0L;
+
+ public Builder() {}
+
+ /**
+ * The name of the federated compute population.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setPopulationName(@NonNull String value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x1;
+ mPopulationName = value;
+ return this;
+ }
+
+ /**
+ * The name of the task within the population. One population may have multiple tasks. The
+ * task name can be used to uniquely identify the job.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setTaskName(@NonNull String value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x2;
+ mTaskName = value;
+ return this;
+ }
+
+ /**
+ * Token used to support the resumption of training.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setResumptionToken(@NonNull byte... value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x4;
+ mResumptionToken = value;
+ return this;
+ }
+
+ /** The data collection name to use to create training examples. */
+ @DataClass.Generated.Member
+ public @NonNull Builder setCollectionUri(@NonNull String value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x8;
+ mCollectionUri = value;
+ return this;
+ }
+
+ /** Builds the instance. This builder should not be touched after calling this! */
+ public @NonNull TrainingExamplesInputParcel build() {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x10; // Mark builder used
+
+ if ((mBuilderFieldsSet & 0x1) == 0) {
+ mPopulationName = "";
+ }
+ if ((mBuilderFieldsSet & 0x2) == 0) {
+ mTaskName = "";
+ }
+ if ((mBuilderFieldsSet & 0x4) == 0) {
+ mResumptionToken = null;
+ }
+ TrainingExamplesInputParcel o =
+ new TrainingExamplesInputParcel(
+ mPopulationName, mTaskName, mResumptionToken, mCollectionUri);
+ 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 = 1714844524307L,
+ codegenVersion = "1.0.23",
+ sourceFile =
+ "packages/modules/OnDevicePersonalization/framework/java/android/adservices/ondevicepersonalization/TrainingExamplesInputParcel.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 mCollectionUri\nclass TrainingExamplesInputParcel extends java.lang.Object implements [android.os.Parcelable]\[email protected](genAidl=false, genHiddenBuilder=true)")
+ @Deprecated
+ private void __metadata() {}
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/android-35/android/adservices/ondevicepersonalization/TrainingExamplesOutput.java b/android-35/android/adservices/ondevicepersonalization/TrainingExamplesOutput.java
new file mode 100644
index 0000000..662c011
--- /dev/null
+++ b/android-35/android/adservices/ondevicepersonalization/TrainingExamplesOutput.java
@@ -0,0 +1,177 @@
+/*
+ * 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.internal.util.Preconditions;
+import com.android.ondevicepersonalization.internal.util.AnnotationValidations;
+import com.android.ondevicepersonalization.internal.util.DataClass;
+
+import java.util.Collections;
+import java.util.List;
+
+/** The output data of {@link IsolatedWorker#onTrainingExamples} */
+@FlaggedApi(Flags.FLAG_ON_DEVICE_PERSONALIZATION_APIS_ENABLED)
+@DataClass(genBuilder = true, genEqualsHashCode = true)
+public final class TrainingExamplesOutput {
+ /**
+ * The list of training example byte arrays. The format is a binary serialized <a
+ * href="https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/example/example.proto">
+ * tensorflow.Example</a> proto. The maximum allowed example size is 50KB.
+ */
+ @NonNull
+ @DataClass.PluralOf("trainingExampleRecord")
+ private List<TrainingExampleRecord> mTrainingExampleRecords = Collections.emptyList();
+
+ // 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/TrainingExamplesOutput.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 */ TrainingExamplesOutput(
+ @NonNull List<TrainingExampleRecord> trainingExampleRecords) {
+ this.mTrainingExampleRecords = trainingExampleRecords;
+ AnnotationValidations.validate(NonNull.class, null, mTrainingExampleRecords);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * The list of training example byte arrays. The format is a binary serialized <a
+ * href="https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/example/example.proto">
+ * tensorflow.Example</a> proto. The maximum allowed example size is 50KB.
+ */
+ @DataClass.Generated.Member
+ public @NonNull List<TrainingExampleRecord> getTrainingExampleRecords() {
+ return mTrainingExampleRecords;
+ }
+
+ @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(TrainingExamplesOutput other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ TrainingExamplesOutput that = (TrainingExamplesOutput) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && java.util.Objects.equals(mTrainingExampleRecords, that.mTrainingExampleRecords);
+ }
+
+ @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(mTrainingExampleRecords);
+ return _hash;
+ }
+
+ /** A builder for {@link TrainingExamplesOutput} */
+ @SuppressWarnings("WeakerAccess")
+ @DataClass.Generated.Member
+ public static final class Builder {
+
+ private @NonNull List<TrainingExampleRecord> mTrainingExampleRecords;
+
+ private long mBuilderFieldsSet = 0L;
+
+ public Builder() {}
+
+ /**
+ * The list of training example byte arrays. The format is a binary serialized <a
+ * href="https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/example/example.proto">
+ * tensorflow.Example</a> proto. The maximum allowed example size is 50KB.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setTrainingExampleRecords(
+ @NonNull List<TrainingExampleRecord> value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x1;
+ mTrainingExampleRecords = value;
+ return this;
+ }
+
+ /**
+ * @see #setTrainingExampleRecords
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder addTrainingExampleRecord(@NonNull TrainingExampleRecord value) {
+ if (mTrainingExampleRecords == null)
+ setTrainingExampleRecords(new java.util.ArrayList<>());
+ mTrainingExampleRecords.add(value);
+ return this;
+ }
+
+ /** Builds the instance. This builder should not be touched after calling this! */
+ public @NonNull TrainingExamplesOutput build() {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x2; // Mark builder used
+
+ if ((mBuilderFieldsSet & 0x1) == 0) {
+ mTrainingExampleRecords = Collections.emptyList();
+ }
+ TrainingExamplesOutput o = new TrainingExamplesOutput(mTrainingExampleRecords);
+ 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 = 1704915709729L,
+ codegenVersion = "1.0.23",
+ sourceFile =
+ "packages/modules/OnDevicePersonalization/framework/java/android/adservices/ondevicepersonalization/TrainingExamplesOutput.java",
+ inputSignatures =
+ "private @android.annotation.NonNull"
+ + " @com.android.ondevicepersonalization.internal.util.DataClass.PluralOf(\"trainingExampleRecord\")"
+ + " java.util.List<android.adservices.ondevicepersonalization.TrainingExampleRecord>"
+ + " mTrainingExampleRecords\n"
+ + "class TrainingExamplesOutput 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/android-35/android/adservices/ondevicepersonalization/TrainingExamplesOutputParcel.java b/android-35/android/adservices/ondevicepersonalization/TrainingExamplesOutputParcel.java
new file mode 100644
index 0000000..f621565
--- /dev/null
+++ b/android-35/android/adservices/ondevicepersonalization/TrainingExamplesOutputParcel.java
@@ -0,0 +1,206 @@
+/*
+ * 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;
+
+import android.annotation.Nullable;
+import android.os.Parcelable;
+
+import com.android.ondevicepersonalization.internal.util.DataClass;
+import com.android.ondevicepersonalization.internal.util.OdpParceledListSlice;
+
+/**
+ * Parcelable version of {@link TrainingExamplesOutput}
+ *
+ * @hide
+ */
+@DataClass(genAidl = false, genHiddenBuilder = true, genEqualsHashCode = true)
+public class TrainingExamplesOutputParcel implements Parcelable {
+ /** List of training example records. */
+ @Nullable OdpParceledListSlice<TrainingExampleRecord> mTrainingExampleRecords = null;
+
+ // 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/TrainingExamplesOutputParcel.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 */ TrainingExamplesOutputParcel(
+ @Nullable OdpParceledListSlice<TrainingExampleRecord> trainingExampleRecords) {
+ this.mTrainingExampleRecords = trainingExampleRecords;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /** List of training example records. */
+ @DataClass.Generated.Member
+ public @Nullable OdpParceledListSlice<TrainingExampleRecord> getTrainingExampleRecords() {
+ return mTrainingExampleRecords;
+ }
+
+ @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(TrainingExamplesOutputParcel other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ TrainingExamplesOutputParcel that = (TrainingExamplesOutputParcel) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && java.util.Objects.equals(mTrainingExampleRecords, that.mTrainingExampleRecords);
+ }
+
+ @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(mTrainingExampleRecords);
+ return _hash;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@android.annotation.NonNull android.os.Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ byte flg = 0;
+ if (mTrainingExampleRecords != null) flg |= 0x1;
+ dest.writeByte(flg);
+ if (mTrainingExampleRecords != null) dest.writeTypedObject(mTrainingExampleRecords, flags);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() {
+ return 0;
+ }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ protected TrainingExamplesOutputParcel(@android.annotation.NonNull android.os.Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ byte flg = in.readByte();
+ OdpParceledListSlice<TrainingExampleRecord> trainingExampleRecords =
+ (flg & 0x1) == 0
+ ? null
+ : (OdpParceledListSlice) in.readTypedObject(OdpParceledListSlice.CREATOR);
+
+ this.mTrainingExampleRecords = trainingExampleRecords;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @android.annotation.NonNull Parcelable.Creator<TrainingExamplesOutputParcel>
+ CREATOR =
+ new Parcelable.Creator<TrainingExamplesOutputParcel>() {
+ @Override
+ public TrainingExamplesOutputParcel[] newArray(int size) {
+ return new TrainingExamplesOutputParcel[size];
+ }
+
+ @Override
+ public TrainingExamplesOutputParcel createFromParcel(
+ @android.annotation.NonNull android.os.Parcel in) {
+ return new TrainingExamplesOutputParcel(in);
+ }
+ };
+
+ /**
+ * A builder for {@link TrainingExamplesOutputParcel}
+ *
+ * @hide
+ */
+ @SuppressWarnings("WeakerAccess")
+ @DataClass.Generated.Member
+ public static class Builder {
+
+ private @Nullable OdpParceledListSlice<TrainingExampleRecord> mTrainingExampleRecords;
+
+ private long mBuilderFieldsSet = 0L;
+
+ public Builder() {}
+
+ /** List of training example records. */
+ @DataClass.Generated.Member
+ public @android.annotation.NonNull Builder setTrainingExampleRecords(
+ @android.annotation.NonNull OdpParceledListSlice<TrainingExampleRecord> value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x1;
+ mTrainingExampleRecords = value;
+ return this;
+ }
+
+ /** Builds the instance. This builder should not be touched after calling this! */
+ public @android.annotation.NonNull TrainingExamplesOutputParcel build() {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x2; // Mark builder used
+
+ if ((mBuilderFieldsSet & 0x1) == 0) {
+ mTrainingExampleRecords = null;
+ }
+ TrainingExamplesOutputParcel o =
+ new TrainingExamplesOutputParcel(mTrainingExampleRecords);
+ 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 = 1704916269933L,
+ codegenVersion = "1.0.23",
+ sourceFile =
+ "packages/modules/OnDevicePersonalization/framework/java/android/adservices/ondevicepersonalization/TrainingExamplesOutputParcel.java",
+ inputSignatures =
+ " @android.annotation.Nullable"
+ + " com.android.ondevicepersonalization.internal.util.OdpParceledListSlice<android.adservices.ondevicepersonalization.TrainingExampleRecord>"
+ + " mTrainingExampleRecords\n"
+ + "class TrainingExamplesOutputParcel extends java.lang.Object implements"
+ + " [android.os.Parcelable]\n"
+ + "@com.android.ondevicepersonalization.internal.util.DataClass(genAidl=false,"
+ + " genHiddenBuilder=true, genEqualsHashCode=true)")
+ @Deprecated
+ private void __metadata() {}
+
+ // @formatter:on
+ // End of generated code
+
+}
diff --git a/android-35/android/adservices/ondevicepersonalization/TrainingInterval.java b/android-35/android/adservices/ondevicepersonalization/TrainingInterval.java
new file mode 100644
index 0000000..89c5b6d
--- /dev/null
+++ b/android-35/android/adservices/ondevicepersonalization/TrainingInterval.java
@@ -0,0 +1,254 @@
+/*
+ * 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;
+
+import java.time.Duration;
+
+/** Training interval settings required for federated computation jobs. */
+@FlaggedApi(Flags.FLAG_ON_DEVICE_PERSONALIZATION_APIS_ENABLED)
+@DataClass(genBuilder = true, genHiddenConstDefs = true, genEqualsHashCode = true)
+public final class TrainingInterval {
+ /** The scheduling mode for a one-off task. */
+ public static final int SCHEDULING_MODE_ONE_TIME = 1;
+
+ /** The scheduling mode for a task that will be rescheduled after each run. */
+ public static final int SCHEDULING_MODE_RECURRENT = 2;
+
+ /**
+ * The scheduling mode for this task, either {@link #SCHEDULING_MODE_ONE_TIME} or {@link
+ * #SCHEDULING_MODE_RECURRENT}. The default scheduling mode is {@link #SCHEDULING_MODE_ONE_TIME}
+ * if unspecified.
+ */
+ @SchedulingMode private int mSchedulingMode = SCHEDULING_MODE_ONE_TIME;
+
+ /**
+ * Sets the minimum time interval between two training runs.
+ *
+ * <p>This field will only be used when the scheduling mode is {@link
+ * #SCHEDULING_MODE_RECURRENT}. The value has be greater than zero.
+ *
+ * <p>Please also note this value is advisory, which does not guarantee the job will be run
+ * immediately after the interval expired. Federated compute will still enforce a minimum
+ * required interval and training constraints to ensure system health. The current training
+ * constraints are device on unmetered network, idle and battery not low.
+ */
+ @NonNull private Duration mMinimumInterval = Duration.ZERO;
+
+ // 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/TrainingInterval.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ // @formatter:off
+
+ /** @hide */
+ @android.annotation.IntDef(
+ prefix = "SCHEDULING_MODE_",
+ value = {SCHEDULING_MODE_ONE_TIME, SCHEDULING_MODE_RECURRENT})
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE)
+ @DataClass.Generated.Member
+ public @interface SchedulingMode {}
+
+ /** @hide */
+ @DataClass.Generated.Member
+ public static String schedulingModeToString(@SchedulingMode int value) {
+ switch (value) {
+ case SCHEDULING_MODE_ONE_TIME:
+ return "SCHEDULING_MODE_ONE_TIME";
+ case SCHEDULING_MODE_RECURRENT:
+ return "SCHEDULING_MODE_RECURRENT";
+ default:
+ return Integer.toHexString(value);
+ }
+ }
+
+ @DataClass.Generated.Member
+ /* package-private */ TrainingInterval(
+ @SchedulingMode int schedulingMode, @NonNull Duration minimumInterval) {
+ this.mSchedulingMode = schedulingMode;
+
+ if (!(mSchedulingMode == SCHEDULING_MODE_ONE_TIME)
+ && !(mSchedulingMode == SCHEDULING_MODE_RECURRENT)) {
+ throw new java.lang.IllegalArgumentException(
+ "schedulingMode was "
+ + mSchedulingMode
+ + " but must be one of: "
+ + "SCHEDULING_MODE_ONE_TIME("
+ + SCHEDULING_MODE_ONE_TIME
+ + "), "
+ + "SCHEDULING_MODE_RECURRENT("
+ + SCHEDULING_MODE_RECURRENT
+ + ")");
+ }
+
+ this.mMinimumInterval = minimumInterval;
+ AnnotationValidations.validate(NonNull.class, null, mMinimumInterval);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * The scheduling mode for this task, either {@link #SCHEDULING_MODE_ONE_TIME} or {@link
+ * #SCHEDULING_MODE_RECURRENT}. The default scheduling mode is {@link #SCHEDULING_MODE_ONE_TIME}
+ * if unspecified.
+ */
+ @DataClass.Generated.Member
+ public @SchedulingMode int getSchedulingMode() {
+ return mSchedulingMode;
+ }
+
+ /**
+ * Sets the minimum time interval between two training runs.
+ *
+ * <p>This field will only be used when the scheduling mode is {@link
+ * #SCHEDULING_MODE_RECURRENT}. Only positive values are accepted, zero or negative values will
+ * result in IllegalArgumentException.
+ *
+ * <p>Please also note this value is advisory, which does not guarantee the job will be run
+ * immediately after the interval expired. Federated compute will still enforce a minimum
+ * required interval and training constraints to ensure system health. The current training
+ * constraints are device on unmetered network, idle and battery not low.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Duration getMinimumInterval() {
+ return mMinimumInterval;
+ }
+
+ @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(TrainingInterval other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ TrainingInterval that = (TrainingInterval) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && mSchedulingMode == that.mSchedulingMode
+ && java.util.Objects.equals(mMinimumInterval, that.mMinimumInterval);
+ }
+
+ @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 + mSchedulingMode;
+ _hash = 31 * _hash + java.util.Objects.hashCode(mMinimumInterval);
+ return _hash;
+ }
+
+ /** A builder for {@link TrainingInterval} */
+ @SuppressWarnings("WeakerAccess")
+ @DataClass.Generated.Member
+ public static final class Builder {
+
+ private @SchedulingMode int mSchedulingMode;
+ private @NonNull Duration mMinimumInterval;
+
+ private long mBuilderFieldsSet = 0L;
+
+ public Builder() {}
+
+ /**
+ * The scheduling mode for this task, either {@link #SCHEDULING_MODE_ONE_TIME} or {@link
+ * #SCHEDULING_MODE_RECURRENT}. The default scheduling mode is {@link
+ * #SCHEDULING_MODE_ONE_TIME} if unspecified.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setSchedulingMode(@SchedulingMode int value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x1;
+ mSchedulingMode = value;
+ return this;
+ }
+
+ /**
+ * Sets the minimum time interval between two training runs.
+ *
+ * <p>This field will only be used when the scheduling mode is {@link
+ * #SCHEDULING_MODE_RECURRENT}. Only positive values are accepted, zero or negative values
+ * will result in IllegalArgumentException.
+ *
+ * <p>Please also note this value is advisory, which does not guarantee the job will be run
+ * immediately after the interval expired. Federated compute will still enforce a minimum
+ * required interval and training constraints to ensure system health. The current training
+ * constraints are device on unmetered network, idle and battery not low.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setMinimumInterval(@NonNull Duration value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x2;
+ mMinimumInterval = value;
+ return this;
+ }
+
+ /** Builds the instance. This builder should not be touched after calling this! */
+ public @NonNull TrainingInterval build() {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x4; // Mark builder used
+
+ if ((mBuilderFieldsSet & 0x1) == 0) {
+ mSchedulingMode = SCHEDULING_MODE_ONE_TIME;
+ }
+ if ((mBuilderFieldsSet & 0x2) == 0) {
+ mMinimumInterval = Duration.ZERO;
+ }
+ TrainingInterval o = new TrainingInterval(mSchedulingMode, mMinimumInterval);
+ return o;
+ }
+
+ private void checkNotUsed() {
+ if ((mBuilderFieldsSet & 0x4) != 0) {
+ throw new IllegalStateException(
+ "This Builder should not be reused. Use a new Builder instance instead");
+ }
+ }
+ }
+
+ @DataClass.Generated(
+ time = 1697653739724L,
+ codegenVersion = "1.0.23",
+ sourceFile =
+ "packages/modules/OnDevicePersonalization/framework/java/android/adservices/ondevicepersonalization/TrainingInterval.java",
+ inputSignatures =
+ "public static final int SCHEDULING_MODE_ONE_TIME\npublic static final int SCHEDULING_MODE_RECURRENT\nprivate @android.adservices.ondevicepersonalization.TrainingInterval.SchedulingMode int mSchedulingMode\nprivate @android.annotation.NonNull java.time.Duration mMinimumInterval\nclass TrainingInterval extends java.lang.Object implements []\[email protected](genBuilder=true, genHiddenConstDefs=true, genEqualsHashCode=true)")
+ @Deprecated
+ private void __metadata() {}
+
+ // @formatter:on
+ // End of generated code
+
+}
diff --git a/android-35/android/adservices/ondevicepersonalization/UserData.java b/android-35/android/adservices/ondevicepersonalization/UserData.java
new file mode 100644
index 0000000..a3a84a0
--- /dev/null
+++ b/android-35/android/adservices/ondevicepersonalization/UserData.java
@@ -0,0 +1,586 @@
+/*
+ * 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 static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.NetworkCapabilities;
+import android.os.Parcelable;
+import android.telephony.TelephonyManager;
+
+import com.android.adservices.ondevicepersonalization.flags.Flags;
+import com.android.ondevicepersonalization.internal.util.AnnotationValidations;
+import com.android.ondevicepersonalization.internal.util.DataClass;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.time.Duration;
+import java.util.Collections;
+import java.util.Map;
+
+/**
+ * User data provided by the platform to an {@link IsolatedService}.
+ *
+ */
+// This class should be updated with the Kotlin mirror
+// {@link com.android.ondevicepersonalization.services.policyengine.data.UserData}.
+@FlaggedApi(Flags.FLAG_ON_DEVICE_PERSONALIZATION_APIS_ENABLED)
+@DataClass(genHiddenBuilder = true, genEqualsHashCode = true, genConstDefs = false)
+public final class UserData implements Parcelable {
+ /**
+ * The device timezone +/- offset from UTC.
+ *
+ * @hide
+ */
+ int mTimezoneUtcOffsetMins = 0;
+
+ /** @hide **/
+ @IntDef(prefix = {"ORIENTATION_"}, value = {
+ ORIENTATION_UNDEFINED,
+ ORIENTATION_PORTRAIT,
+ ORIENTATION_LANDSCAPE
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Orientation {
+ }
+
+ /**
+ * The device orientation. The value can be one of the constants ORIENTATION_UNDEFINED,
+ * ORIENTATION_PORTRAIT or ORIENTATION_LANDSCAPE defined in
+ * {@link android.content.res.Configuration}.
+ */
+ @Orientation int mOrientation = 0;
+
+ /** The available space on device in bytes. */
+ @IntRange(from = 0) long mAvailableStorageBytes = 0;
+
+ /** Battery percentage. */
+ @IntRange(from = 0, to = 100) int mBatteryPercentage = 0;
+
+ /** The Service Provider Name (SPN) returned by {@link TelephonyManager#getSimOperatorName()} */
+ @NonNull String mCarrier = "";
+
+ /** @hide **/
+ @IntDef({
+ TelephonyManager.NETWORK_TYPE_UNKNOWN,
+ TelephonyManager.NETWORK_TYPE_GPRS,
+ TelephonyManager.NETWORK_TYPE_EDGE,
+ TelephonyManager.NETWORK_TYPE_UMTS,
+ TelephonyManager.NETWORK_TYPE_CDMA,
+ TelephonyManager.NETWORK_TYPE_EVDO_0,
+ TelephonyManager.NETWORK_TYPE_EVDO_A,
+ TelephonyManager.NETWORK_TYPE_1xRTT,
+ TelephonyManager.NETWORK_TYPE_HSDPA,
+ TelephonyManager.NETWORK_TYPE_HSUPA,
+ TelephonyManager.NETWORK_TYPE_HSPA,
+ TelephonyManager.NETWORK_TYPE_EVDO_B,
+ TelephonyManager.NETWORK_TYPE_LTE,
+ TelephonyManager.NETWORK_TYPE_EHRPD,
+ TelephonyManager.NETWORK_TYPE_HSPAP,
+ TelephonyManager.NETWORK_TYPE_GSM,
+ TelephonyManager.NETWORK_TYPE_TD_SCDMA,
+ TelephonyManager.NETWORK_TYPE_IWLAN,
+
+ //TODO: In order for @SystemApi methods to use this class, there cannot be any
+ // public hidden members. This network type is marked as hidden because it is not a
+ // true network type and we are looking to remove it completely from the available list
+ // of network types.
+ //TelephonyManager.NETWORK_TYPE_LTE_CA,
+
+ TelephonyManager.NETWORK_TYPE_NR,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface NetworkType {
+ }
+
+ /**
+ * A filtered subset of the Network capabilities of the device that contains upstream
+ * and downstream speeds, and whether the network is metered.
+ * This is an instance of {@link NetworkCapabilities} that contains the capability
+ * {@link android.net.NetworkCapabilities#NET_CAPABILITY_NOT_METERED} if the network is not
+ * metered, and {@link NetworkCapabilities#getLinkDownstreamBandwidthKbps()} and
+ * {@link NetworkCapabilities#getLinkUpstreamBandwidthKbps()} return the downstream and
+ * upstream connection speeds. All other methods of this {@link NetworkCapabilities} object
+ * return empty or default values.
+ */
+ @Nullable NetworkCapabilities mNetworkCapabilities = null;
+
+ /**
+ * Data network type. This is the value of
+ * {@link android.telephony.TelephonyManager#getDataNetworkType()}.
+ */
+ @NetworkType int mDataNetworkType = 0;
+
+ /** A map from package name to app information for installed and uninstalled apps. */
+ @DataClass.PluralOf("appInfo")
+ @NonNull Map<String, AppInfo> mAppInfos = Collections.emptyMap();
+
+ /** The device timezone +/- offset from UTC. */
+ @NonNull public Duration getTimezoneUtcOffset() {
+ return Duration.ofMinutes(mTimezoneUtcOffsetMins);
+ }
+
+
+
+ // 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/UserData.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 */ UserData(
+ int timezoneUtcOffsetMins,
+ @Orientation int orientation,
+ @IntRange(from = 0) long availableStorageBytes,
+ @IntRange(from = 0, to = 100) int batteryPercentage,
+ @NonNull String carrier,
+ @Nullable NetworkCapabilities networkCapabilities,
+ @NetworkType int dataNetworkType,
+ @NonNull Map<String,AppInfo> appInfos) {
+ this.mTimezoneUtcOffsetMins = timezoneUtcOffsetMins;
+ this.mOrientation = orientation;
+ AnnotationValidations.validate(
+ Orientation.class, null, mOrientation);
+ this.mAvailableStorageBytes = availableStorageBytes;
+ AnnotationValidations.validate(
+ IntRange.class, null, mAvailableStorageBytes,
+ "from", 0);
+ this.mBatteryPercentage = batteryPercentage;
+ AnnotationValidations.validate(
+ IntRange.class, null, mBatteryPercentage,
+ "from", 0,
+ "to", 100);
+ this.mCarrier = carrier;
+ AnnotationValidations.validate(
+ NonNull.class, null, mCarrier);
+ this.mNetworkCapabilities = networkCapabilities;
+ this.mDataNetworkType = dataNetworkType;
+ AnnotationValidations.validate(
+ NetworkType.class, null, mDataNetworkType);
+ this.mAppInfos = appInfos;
+ AnnotationValidations.validate(
+ NonNull.class, null, mAppInfos);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * The device timezone +/- offset from UTC.
+ *
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public int getTimezoneUtcOffsetMins() {
+ return mTimezoneUtcOffsetMins;
+ }
+
+ /**
+ * The device orientation. The value can be one of the constants ORIENTATION_UNDEFINED,
+ * ORIENTATION_PORTRAIT or ORIENTATION_LANDSCAPE defined in
+ * {@link android.content.res.Configuration}.
+ */
+ @DataClass.Generated.Member
+ public @Orientation int getOrientation() {
+ return mOrientation;
+ }
+
+ /**
+ * The available space on device in bytes.
+ */
+ @DataClass.Generated.Member
+ public @IntRange(from = 0) long getAvailableStorageBytes() {
+ return mAvailableStorageBytes;
+ }
+
+ /**
+ * Battery percentage.
+ */
+ @DataClass.Generated.Member
+ public @IntRange(from = 0, to = 100) int getBatteryPercentage() {
+ return mBatteryPercentage;
+ }
+
+ /**
+ * The Service Provider Name (SPN) returned by {@link TelephonyManager#getSimOperatorName()}
+ */
+ @DataClass.Generated.Member
+ public @NonNull String getCarrier() {
+ return mCarrier;
+ }
+
+ /**
+ * A filtered subset of the Network capabilities of the device that contains upstream
+ * and downstream speeds, and whether the network is metered.
+ * This is an instance of {@link NetworkCapabilities} that contains the capability
+ * {@link android.net.NetworkCapabilities#NET_CAPABILITY_NOT_METERED} if the network is not
+ * metered, and {@link NetworkCapabilities#getLinkDownstreamBandwidthKbps()} and
+ * {@link NetworkCapabilities#getLinkUpstreamBandwidthKbps()} return the downstream and
+ * upstream connection speeds. All other methods of this {@link NetworkCapabilities} object
+ * return empty or default values.
+ */
+ @DataClass.Generated.Member
+ public @Nullable NetworkCapabilities getNetworkCapabilities() {
+ return mNetworkCapabilities;
+ }
+
+ /**
+ * Data network type. This is the value of
+ * {@link android.telephony.TelephonyManager#getDataNetworkType()}.
+ */
+ @DataClass.Generated.Member
+ public @NetworkType int getDataNetworkType() {
+ return mDataNetworkType;
+ }
+
+ /**
+ * A map from package name to app information for installed and uninstalled apps.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Map<String,AppInfo> getAppInfos() {
+ return mAppInfos;
+ }
+
+ @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(UserData other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ UserData that = (UserData) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && mTimezoneUtcOffsetMins == that.mTimezoneUtcOffsetMins
+ && mOrientation == that.mOrientation
+ && mAvailableStorageBytes == that.mAvailableStorageBytes
+ && mBatteryPercentage == that.mBatteryPercentage
+ && java.util.Objects.equals(mCarrier, that.mCarrier)
+ && java.util.Objects.equals(mNetworkCapabilities, that.mNetworkCapabilities)
+ && mDataNetworkType == that.mDataNetworkType
+ && java.util.Objects.equals(mAppInfos, that.mAppInfos);
+ }
+
+ @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 + mTimezoneUtcOffsetMins;
+ _hash = 31 * _hash + mOrientation;
+ _hash = 31 * _hash + Long.hashCode(mAvailableStorageBytes);
+ _hash = 31 * _hash + mBatteryPercentage;
+ _hash = 31 * _hash + java.util.Objects.hashCode(mCarrier);
+ _hash = 31 * _hash + java.util.Objects.hashCode(mNetworkCapabilities);
+ _hash = 31 * _hash + mDataNetworkType;
+ _hash = 31 * _hash + java.util.Objects.hashCode(mAppInfos);
+ return _hash;
+ }
+
+ @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) { ... }
+
+ int flg = 0;
+ if (mNetworkCapabilities != null) flg |= 0x20;
+ dest.writeInt(flg);
+ dest.writeInt(mTimezoneUtcOffsetMins);
+ dest.writeInt(mOrientation);
+ dest.writeLong(mAvailableStorageBytes);
+ dest.writeInt(mBatteryPercentage);
+ dest.writeString(mCarrier);
+ if (mNetworkCapabilities != null) dest.writeTypedObject(mNetworkCapabilities, flags);
+ dest.writeInt(mDataNetworkType);
+ dest.writeMap(mAppInfos);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ UserData(@NonNull android.os.Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ int flg = in.readInt();
+ int timezoneUtcOffsetMins = in.readInt();
+ int orientation = in.readInt();
+ long availableStorageBytes = in.readLong();
+ int batteryPercentage = in.readInt();
+ String carrier = in.readString();
+ NetworkCapabilities networkCapabilities = (flg & 0x20) == 0 ? null : (NetworkCapabilities) in.readTypedObject(NetworkCapabilities.CREATOR);
+ int dataNetworkType = in.readInt();
+ Map<String,AppInfo> appInfos = new java.util.LinkedHashMap<>();
+ in.readMap(appInfos, AppInfo.class.getClassLoader());
+
+ this.mTimezoneUtcOffsetMins = timezoneUtcOffsetMins;
+ this.mOrientation = orientation;
+ AnnotationValidations.validate(
+ Orientation.class, null, mOrientation);
+ this.mAvailableStorageBytes = availableStorageBytes;
+ AnnotationValidations.validate(
+ IntRange.class, null, mAvailableStorageBytes,
+ "from", 0);
+ this.mBatteryPercentage = batteryPercentage;
+ AnnotationValidations.validate(
+ IntRange.class, null, mBatteryPercentage,
+ "from", 0,
+ "to", 100);
+ this.mCarrier = carrier;
+ AnnotationValidations.validate(
+ NonNull.class, null, mCarrier);
+ this.mNetworkCapabilities = networkCapabilities;
+ this.mDataNetworkType = dataNetworkType;
+ AnnotationValidations.validate(
+ NetworkType.class, null, mDataNetworkType);
+ this.mAppInfos = appInfos;
+ AnnotationValidations.validate(
+ NonNull.class, null, mAppInfos);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<UserData> CREATOR
+ = new Parcelable.Creator<UserData>() {
+ @Override
+ public UserData[] newArray(int size) {
+ return new UserData[size];
+ }
+
+ @Override
+ public UserData createFromParcel(@NonNull android.os.Parcel in) {
+ return new UserData(in);
+ }
+ };
+
+ /**
+ * A builder for {@link UserData}
+ * @hide
+ */
+ @SuppressWarnings("WeakerAccess")
+ @DataClass.Generated.Member
+ public static final class Builder {
+
+ private int mTimezoneUtcOffsetMins;
+ private @Orientation int mOrientation;
+ private @IntRange(from = 0) long mAvailableStorageBytes;
+ private @IntRange(from = 0, to = 100) int mBatteryPercentage;
+ private @NonNull String mCarrier;
+ private @Nullable NetworkCapabilities mNetworkCapabilities;
+ private @NetworkType int mDataNetworkType;
+ private @NonNull Map<String,AppInfo> mAppInfos;
+
+ private long mBuilderFieldsSet = 0L;
+
+ public Builder() {
+ }
+
+ /**
+ * The device timezone +/- offset from UTC.
+ *
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setTimezoneUtcOffsetMins(int value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x1;
+ mTimezoneUtcOffsetMins = value;
+ return this;
+ }
+
+ /**
+ * The device orientation. The value can be one of the constants ORIENTATION_UNDEFINED,
+ * ORIENTATION_PORTRAIT or ORIENTATION_LANDSCAPE defined in
+ * {@link android.content.res.Configuration}.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setOrientation(@Orientation int value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x2;
+ mOrientation = value;
+ return this;
+ }
+
+ /**
+ * The available space on device in bytes.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setAvailableStorageBytes(@IntRange(from = 0) long value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x4;
+ mAvailableStorageBytes = value;
+ return this;
+ }
+
+ /**
+ * Battery percentage.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setBatteryPercentage(@IntRange(from = 0, to = 100) int value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x8;
+ mBatteryPercentage = value;
+ return this;
+ }
+
+ /**
+ * The Service Provider Name (SPN) returned by {@link TelephonyManager#getSimOperatorName()}
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setCarrier(@NonNull String value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x10;
+ mCarrier = value;
+ return this;
+ }
+
+ /**
+ * A filtered subset of the Network capabilities of the device that contains upstream
+ * and downstream speeds, and whether the network is metered.
+ * This is an instance of {@link NetworkCapabilities} that contains the capability
+ * {@link android.net.NetworkCapabilities#NET_CAPABILITY_NOT_METERED} if the network is not
+ * metered, and {@link NetworkCapabilities#getLinkDownstreamBandwidthKbps()} and
+ * {@link NetworkCapabilities#getLinkUpstreamBandwidthKbps()} return the downstream and
+ * upstream connection speeds. All other methods of this {@link NetworkCapabilities} object
+ * return empty or default values.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setNetworkCapabilities(@NonNull NetworkCapabilities value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x20;
+ mNetworkCapabilities = value;
+ return this;
+ }
+
+ /**
+ * Data network type. This is the value of
+ * {@link android.telephony.TelephonyManager#getDataNetworkType()}.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setDataNetworkType(@NetworkType int value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x40;
+ mDataNetworkType = value;
+ return this;
+ }
+
+ /**
+ * A map from package name to app information for installed and uninstalled apps.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setAppInfos(@NonNull Map<String,AppInfo> value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x80;
+ mAppInfos = value;
+ return this;
+ }
+
+ /** @see #setAppInfos */
+ @DataClass.Generated.Member
+ public @NonNull Builder addAppInfo(@NonNull String key, @NonNull AppInfo value) {
+ if (mAppInfos == null) setAppInfos(new java.util.LinkedHashMap());
+ mAppInfos.put(key, value);
+ return this;
+ }
+
+ /** Builds the instance. This builder should not be touched after calling this! */
+ public @NonNull UserData build() {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x100; // Mark builder used
+
+ if ((mBuilderFieldsSet & 0x1) == 0) {
+ mTimezoneUtcOffsetMins = 0;
+ }
+ if ((mBuilderFieldsSet & 0x2) == 0) {
+ mOrientation = 0;
+ }
+ if ((mBuilderFieldsSet & 0x4) == 0) {
+ mAvailableStorageBytes = 0;
+ }
+ if ((mBuilderFieldsSet & 0x8) == 0) {
+ mBatteryPercentage = 0;
+ }
+ if ((mBuilderFieldsSet & 0x10) == 0) {
+ mCarrier = "";
+ }
+ if ((mBuilderFieldsSet & 0x20) == 0) {
+ mNetworkCapabilities = null;
+ }
+ if ((mBuilderFieldsSet & 0x40) == 0) {
+ mDataNetworkType = 0;
+ }
+ if ((mBuilderFieldsSet & 0x80) == 0) {
+ mAppInfos = Collections.emptyMap();
+ }
+ UserData o = new UserData(
+ mTimezoneUtcOffsetMins,
+ mOrientation,
+ mAvailableStorageBytes,
+ mBatteryPercentage,
+ mCarrier,
+ mNetworkCapabilities,
+ mDataNetworkType,
+ mAppInfos);
+ return o;
+ }
+
+ private void checkNotUsed() {
+ if ((mBuilderFieldsSet & 0x100) != 0) {
+ throw new IllegalStateException(
+ "This Builder should not be reused. Use a new Builder instance instead");
+ }
+ }
+ }
+
+ @DataClass.Generated(
+ time = 1707172832988L,
+ codegenVersion = "1.0.23",
+ sourceFile = "packages/modules/OnDevicePersonalization/framework/java/android/adservices/ondevicepersonalization/UserData.java",
+ inputSignatures = " int mTimezoneUtcOffsetMins\n @android.adservices.ondevicepersonalization.UserData.Orientation int mOrientation\n @android.annotation.IntRange long mAvailableStorageBytes\n @android.annotation.IntRange int mBatteryPercentage\n @android.annotation.NonNull java.lang.String mCarrier\n @android.annotation.Nullable android.net.NetworkCapabilities mNetworkCapabilities\n @android.adservices.ondevicepersonalization.UserData.NetworkType int mDataNetworkType\n @com.android.ondevicepersonalization.internal.util.DataClass.PluralOf(\"appInfo\") @android.annotation.NonNull java.util.Map<java.lang.String,android.adservices.ondevicepersonalization.AppInfo> mAppInfos\npublic @android.annotation.NonNull java.time.Duration getTimezoneUtcOffset()\nclass UserData extends java.lang.Object implements [android.os.Parcelable]\[email protected](genHiddenBuilder=true, genEqualsHashCode=true, genConstDefs=false)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/android-35/android/adservices/ondevicepersonalization/WebTriggerInput.java b/android-35/android/adservices/ondevicepersonalization/WebTriggerInput.java
new file mode 100644
index 0000000..5c57cb6
--- /dev/null
+++ b/android-35/android/adservices/ondevicepersonalization/WebTriggerInput.java
@@ -0,0 +1,167 @@
+/*
+ * 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.net.Uri;
+
+import com.android.adservices.ondevicepersonalization.flags.Flags;
+import com.android.ondevicepersonalization.internal.util.AnnotationValidations;
+import com.android.ondevicepersonalization.internal.util.DataClass;
+
+/**
+ * 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 */
+ @NonNull private String mAppPackageName;
+
+ /**
+ * 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.
+ */
+ @NonNull private byte[] mData;
+
+ /** @hide */
+ public WebTriggerInput(@NonNull WebTriggerInputParcel parcel) {
+ 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
+ * @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
+ 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
+ }
+
+ /**
+ * 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
+ */
+ @DataClass.Generated.Member
+ public @NonNull String getAppPackageName() {
+ return mAppPackageName;
+ }
+
+ /**
+ * 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.
+ */
+ @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")
+ WebTriggerInput that = (WebTriggerInput) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && java.util.Objects.equals(mDestinationUrl, that.mDestinationUrl)
+ && java.util.Objects.equals(mAppPackageName, that.mAppPackageName)
+ && java.util.Arrays.equals(mData, that.mData);
+ }
+
+ @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/android-35/android/adservices/ondevicepersonalization/WebTriggerInputParcel.java b/android-35/android/adservices/ondevicepersonalization/WebTriggerInputParcel.java
new file mode 100644
index 0000000..71f44cc
--- /dev/null
+++ b/android-35/android/adservices/ondevicepersonalization/WebTriggerInputParcel.java
@@ -0,0 +1,255 @@
+/*
+ * 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.NonNull;
+import android.net.Uri;
+import android.os.Parcelable;
+
+import com.android.ondevicepersonalization.internal.util.AnnotationValidations;
+import com.android.ondevicepersonalization.internal.util.DataClass;
+
+/**
+ * Parcelable version of {@link WebTriggerInput}.
+ * @hide
+ */
+@DataClass(genAidl = false, genHiddenBuilder = true)
+public final class WebTriggerInputParcel implements Parcelable {
+ /** The destination URL (landing page) where the trigger registration occurred. */
+ @NonNull private Uri mDestinationUrl;
+
+ /** The app where the trigger registration occurred */
+ @NonNull private String mAppPackageName;
+
+ /** The data to be sent to the isolated service. */
+ @NonNull private byte[] mData;
+
+
+
+ // 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/WebTriggerInputParcel.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 */ WebTriggerInputParcel(
+ @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
+ }
+
+ /**
+ * The destination URL (landing page) where the trigger registration occurred.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Uri getDestinationUrl() {
+ return mDestinationUrl;
+ }
+
+ /**
+ * The app where the trigger registration occurred
+ */
+ @DataClass.Generated.Member
+ public @NonNull String getAppPackageName() {
+ return mAppPackageName;
+ }
+
+ /**
+ * The data to be sent to the isolated service.
+ */
+ @DataClass.Generated.Member
+ public @NonNull byte[] getData() {
+ return mData;
+ }
+
+ @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.writeTypedObject(mDestinationUrl, flags);
+ dest.writeString(mAppPackageName);
+ dest.writeByteArray(mData);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ WebTriggerInputParcel(@NonNull android.os.Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ Uri destinationUrl = (Uri) in.readTypedObject(Uri.CREATOR);
+ String appPackageName = in.readString();
+ byte[] data = in.createByteArray();
+
+ 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
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<WebTriggerInputParcel> CREATOR
+ = new Parcelable.Creator<WebTriggerInputParcel>() {
+ @Override
+ public WebTriggerInputParcel[] newArray(int size) {
+ return new WebTriggerInputParcel[size];
+ }
+
+ @Override
+ public WebTriggerInputParcel createFromParcel(@NonNull android.os.Parcel in) {
+ return new WebTriggerInputParcel(in);
+ }
+ };
+
+ /**
+ * A builder for {@link WebTriggerInputParcel}
+ * @hide
+ */
+ @SuppressWarnings("WeakerAccess")
+ @DataClass.Generated.Member
+ public static final class Builder {
+
+ private @NonNull Uri mDestinationUrl;
+ private @NonNull String mAppPackageName;
+ private @NonNull byte[] mData;
+
+ private long mBuilderFieldsSet = 0L;
+
+ /**
+ * Creates a new Builder.
+ *
+ * @param destinationUrl
+ * The destination URL (landing page) where the trigger registration occurred.
+ * @param appPackageName
+ * The app where the trigger registration occurred
+ * @param data
+ * The data to be sent to the isolated service.
+ */
+ public Builder(
+ @NonNull Uri destinationUrl,
+ @NonNull String appPackageName,
+ @NonNull byte[] data) {
+ mDestinationUrl = destinationUrl;
+ AnnotationValidations.validate(
+ NonNull.class, null, mDestinationUrl);
+ mAppPackageName = appPackageName;
+ AnnotationValidations.validate(
+ NonNull.class, null, mAppPackageName);
+ mData = data;
+ AnnotationValidations.validate(
+ NonNull.class, null, mData);
+ }
+
+ /**
+ * The destination URL (landing page) where the trigger registration occurred.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setDestinationUrl(@NonNull Uri value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x1;
+ mDestinationUrl = value;
+ return this;
+ }
+
+ /**
+ * The app where the trigger registration occurred
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setAppPackageName(@NonNull String value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x2;
+ mAppPackageName = value;
+ return this;
+ }
+
+ /**
+ * The data to be sent to the isolated service.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setData(@NonNull byte... value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x4;
+ mData = value;
+ return this;
+ }
+
+ /** Builds the instance. This builder should not be touched after calling this! */
+ public @NonNull WebTriggerInputParcel build() {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x8; // Mark builder used
+
+ WebTriggerInputParcel o = new WebTriggerInputParcel(
+ mDestinationUrl,
+ mAppPackageName,
+ mData);
+ return o;
+ }
+
+ private void checkNotUsed() {
+ if ((mBuilderFieldsSet & 0x8) != 0) {
+ throw new IllegalStateException(
+ "This Builder should not be reused. Use a new Builder instance instead");
+ }
+ }
+ }
+
+ @DataClass.Generated(
+ time = 1707510196470L,
+ codegenVersion = "1.0.23",
+ sourceFile = "packages/modules/OnDevicePersonalization/framework/java/android/adservices/ondevicepersonalization/WebTriggerInputParcel.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 WebTriggerInputParcel extends java.lang.Object implements [android.os.Parcelable]\[email protected](genAidl=false, genHiddenBuilder=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/android-35/android/adservices/ondevicepersonalization/WebTriggerOutput.java b/android-35/android/adservices/ondevicepersonalization/WebTriggerOutput.java
new file mode 100644
index 0000000..4dea0ab
--- /dev/null
+++ b/android-35/android/adservices/ondevicepersonalization/WebTriggerOutput.java
@@ -0,0 +1,230 @@
+/*
+ * 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;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * The result that should be returned by
+ * {@link IsolatedWorker#onWebTrigger(WebTriggerInput, android.os.OutcomeReceiver)}.
+ * This class contains data that should be written to the REQUESTS or EVENTS tables.
+ * The contents of these tables can be consumed by Federated Learning facilitated model training,
+ * or Federated Analytics facilitated cross-device statistical analysis.
+ */
+@FlaggedApi(Flags.FLAG_ON_DEVICE_PERSONALIZATION_APIS_ENABLED)
+@DataClass(genBuilder = true, genEqualsHashCode = true)
+public final class WebTriggerOutput {
+ /**
+ * Persistent data to be written to the REQUESTS table after
+ * {@link IsolatedWorker#onWebTrigger(WebTriggerInput, android.os.OutcomeReceiver)}
+ * completes. This can be {@code null} if no data needs to be written to
+ * the REQUESTS table.
+ */
+ @DataClass.MaySetToNull
+ @Nullable private RequestLogRecord mRequestLogRecord = null;
+
+ /**
+ * A list of {@link EventLogRecord} objects to be written to the EVENTS table. Each
+ * {@link EventLogRecord} must be associated with an existing {@link RequestLogRecord} in
+ * the REQUESTS table, specified using
+ * {@link EventLogRecord.Builder#setRequestLogRecord(RequestLogRecord)}.
+ * If the {@link RequestLogRecord} is not specified, the {@link EventLogRecord} will not be
+ * written. The list can be empty if no data needs to be written to the EVENTS table.
+ */
+ @DataClass.PluralOf("eventLogRecord")
+ @NonNull private List<EventLogRecord> mEventLogRecords = Collections.emptyList();
+
+
+
+ // 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/WebTriggerOutput.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 */ WebTriggerOutput(
+ @Nullable RequestLogRecord requestLogRecord,
+ @NonNull List<EventLogRecord> eventLogRecords) {
+ this.mRequestLogRecord = requestLogRecord;
+ this.mEventLogRecords = eventLogRecords;
+ AnnotationValidations.validate(
+ NonNull.class, null, mEventLogRecords);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * Persistent data to be written to the REQUESTS table after
+ * {@link IsolatedWorker#onWebTrigger(WebTriggerInput, android.os.OutcomeReceiver)}
+ * completes. This can be {@code null} if no data needs to be written to
+ * the REQUESTS table.
+ */
+ @DataClass.Generated.Member
+ public @Nullable RequestLogRecord getRequestLogRecord() {
+ return mRequestLogRecord;
+ }
+
+ /**
+ * A list of {@link EventLogRecord} objects to be written to the EVENTS table. Each
+ * {@link EventLogRecord} must be associated with an existing {@link RequestLogRecord} in
+ * the REQUESTS table, specified using
+ * {@link EventLogRecord.Builder#setRequestLogRecord(RequestLogRecord)}.
+ * If the {@link RequestLogRecord} is not specified, the {@link EventLogRecord} will not be
+ * written. The list can be empty if no data needs to be written to the EVENTS table.
+ */
+ @DataClass.Generated.Member
+ public @NonNull List<EventLogRecord> getEventLogRecords() {
+ return mEventLogRecords;
+ }
+
+ @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(WebTriggerOutput other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ WebTriggerOutput that = (WebTriggerOutput) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && java.util.Objects.equals(mRequestLogRecord, that.mRequestLogRecord)
+ && java.util.Objects.equals(mEventLogRecords, that.mEventLogRecords);
+ }
+
+ @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(mEventLogRecords);
+ return _hash;
+ }
+
+ /**
+ * A builder for {@link WebTriggerOutput}
+ */
+ @SuppressWarnings("WeakerAccess")
+ @DataClass.Generated.Member
+ public static final class Builder {
+
+ private @Nullable RequestLogRecord mRequestLogRecord;
+ private @NonNull List<EventLogRecord> mEventLogRecords;
+
+ private long mBuilderFieldsSet = 0L;
+
+ public Builder() {
+ }
+
+ /**
+ * Persistent data to be written to the REQUESTS table after
+ * {@link IsolatedWorker#onWebTrigger(WebTriggerInput, android.os.OutcomeReceiver)}
+ * completes. This can be {@code null} if no data needs to be written to
+ * the REQUESTS table.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setRequestLogRecord(@Nullable RequestLogRecord value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x1;
+ mRequestLogRecord = value;
+ return this;
+ }
+
+ /**
+ * A list of {@link EventLogRecord} objects to be written to the EVENTS table. Each
+ * {@link EventLogRecord} must be associated with an existing {@link RequestLogRecord} in
+ * the REQUESTS table, specified using
+ * {@link EventLogRecord.Builder#setRequestLogRecord(RequestLogRecord)}.
+ * If the {@link RequestLogRecord} is not specified, the {@link EventLogRecord} will not be
+ * written. The list can be empty if no data needs to be written to the EVENTS table.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setEventLogRecords(@NonNull List<EventLogRecord> value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x2;
+ mEventLogRecords = value;
+ return this;
+ }
+
+ /** @see #setEventLogRecords */
+ @DataClass.Generated.Member
+ public @NonNull Builder addEventLogRecord(@NonNull EventLogRecord value) {
+ if (mEventLogRecords == null) setEventLogRecords(new java.util.ArrayList<>());
+ mEventLogRecords.add(value);
+ return this;
+ }
+
+ /** Builds the instance. This builder should not be touched after calling this! */
+ public @NonNull WebTriggerOutput build() {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x4; // Mark builder used
+
+ if ((mBuilderFieldsSet & 0x1) == 0) {
+ mRequestLogRecord = null;
+ }
+ if ((mBuilderFieldsSet & 0x2) == 0) {
+ mEventLogRecords = Collections.emptyList();
+ }
+ WebTriggerOutput o = new WebTriggerOutput(
+ mRequestLogRecord,
+ mEventLogRecords);
+ return o;
+ }
+
+ private void checkNotUsed() {
+ if ((mBuilderFieldsSet & 0x4) != 0) {
+ throw new IllegalStateException(
+ "This Builder should not be reused. Use a new Builder instance instead");
+ }
+ }
+ }
+
+ @DataClass.Generated(
+ time = 1707251898683L,
+ codegenVersion = "1.0.23",
+ sourceFile = "packages/modules/OnDevicePersonalization/framework/java/android/adservices/ondevicepersonalization/WebTriggerOutput.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.PluralOf(\"eventLogRecord\") @android.annotation.NonNull java.util.List<android.adservices.ondevicepersonalization.EventLogRecord> mEventLogRecords\nclass WebTriggerOutput extends java.lang.Object implements []\[email protected](genBuilder=true, genEqualsHashCode=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/android-35/android/adservices/ondevicepersonalization/WebTriggerOutputParcel.java b/android-35/android/adservices/ondevicepersonalization/WebTriggerOutputParcel.java
new file mode 100644
index 0000000..0671a31
--- /dev/null
+++ b/android-35/android/adservices/ondevicepersonalization/WebTriggerOutputParcel.java
@@ -0,0 +1,184 @@
+/*
+ * 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.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcelable;
+
+import com.android.ondevicepersonalization.internal.util.AnnotationValidations;
+import com.android.ondevicepersonalization.internal.util.DataClass;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Parcelable version of {@link WebTriggerOutput}.
+ * @hide
+ */
+@DataClass(genAidl = false, genBuilder = false)
+public final class WebTriggerOutputParcel implements Parcelable {
+ /**
+ * Persistent data to be written to the REQUESTS table after
+ * {@link IsolatedWorker#onWebTrigger(WebTriggerInput, android.os.OutcomeReceiver)}
+ * completes. If null, no persistent data will be written.
+ */
+ @Nullable private RequestLogRecord mRequestLogRecord = null;
+
+ /**
+ * 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} that was previously written
+ * by this service, the {@link EventLogRecord} is not written.
+ *
+ */
+ @DataClass.PluralOf("eventLogRecord")
+ @NonNull private List<EventLogRecord> mEventLogRecords = Collections.emptyList();
+
+ /** @hide */
+ public WebTriggerOutputParcel(@NonNull WebTriggerOutput value) {
+ this(value.getRequestLogRecord(), value.getEventLogRecords());
+ }
+
+
+
+ // 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/WebTriggerOutputParcel.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /**
+ * Creates a new WebTriggerOutputParcel.
+ *
+ * @param requestLogRecord
+ * Persistent data to be written to the REQUESTS table after
+ * {@link IsolatedWorker#onWebTrigger(WebTriggerInput, android.os.OutcomeReceiver)}
+ * completes. If null, no persistent data will be written.
+ * @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} that was previously written
+ * by this service, the {@link EventLogRecord} is not written.
+ */
+ @DataClass.Generated.Member
+ public WebTriggerOutputParcel(
+ @Nullable RequestLogRecord requestLogRecord,
+ @NonNull List<EventLogRecord> eventLogRecords) {
+ this.mRequestLogRecord = requestLogRecord;
+ this.mEventLogRecords = eventLogRecords;
+ AnnotationValidations.validate(
+ NonNull.class, null, mEventLogRecords);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * Persistent data to be written to the REQUESTS table after
+ * {@link IsolatedWorker#onWebTrigger(WebTriggerInput, android.os.OutcomeReceiver)}
+ * completes. If null, no persistent data will be written.
+ */
+ @DataClass.Generated.Member
+ public @Nullable RequestLogRecord getRequestLogRecord() {
+ return mRequestLogRecord;
+ }
+
+ /**
+ * 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} that was previously written
+ * by this service, the {@link EventLogRecord} is not written.
+ */
+ @DataClass.Generated.Member
+ public @NonNull List<EventLogRecord> getEventLogRecords() {
+ return mEventLogRecords;
+ }
+
+ @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) { ... }
+
+ byte flg = 0;
+ if (mRequestLogRecord != null) flg |= 0x1;
+ dest.writeByte(flg);
+ if (mRequestLogRecord != null) dest.writeTypedObject(mRequestLogRecord, flags);
+ dest.writeParcelableList(mEventLogRecords, flags);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ WebTriggerOutputParcel(@NonNull android.os.Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ byte flg = in.readByte();
+ RequestLogRecord requestLogRecord = (flg & 0x1) == 0 ? null : (RequestLogRecord) in.readTypedObject(RequestLogRecord.CREATOR);
+ List<EventLogRecord> eventLogRecords = new java.util.ArrayList<>();
+ in.readParcelableList(eventLogRecords, EventLogRecord.class.getClassLoader());
+
+ this.mRequestLogRecord = requestLogRecord;
+ this.mEventLogRecords = eventLogRecords;
+ AnnotationValidations.validate(
+ NonNull.class, null, mEventLogRecords);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<WebTriggerOutputParcel> CREATOR
+ = new Parcelable.Creator<WebTriggerOutputParcel>() {
+ @Override
+ public WebTriggerOutputParcel[] newArray(int size) {
+ return new WebTriggerOutputParcel[size];
+ }
+
+ @Override
+ public WebTriggerOutputParcel createFromParcel(@NonNull android.os.Parcel in) {
+ return new WebTriggerOutputParcel(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1704482141383L,
+ codegenVersion = "1.0.23",
+ sourceFile = "packages/modules/OnDevicePersonalization/framework/java/android/adservices/ondevicepersonalization/WebTriggerOutputParcel.java",
+ inputSignatures = "private @android.annotation.Nullable android.adservices.ondevicepersonalization.RequestLogRecord mRequestLogRecord\nprivate @com.android.ondevicepersonalization.internal.util.DataClass.PluralOf(\"eventLogRecord\") @android.annotation.NonNull java.util.List<android.adservices.ondevicepersonalization.EventLogRecord> mEventLogRecords\nclass WebTriggerOutputParcel 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/android-35/android/adservices/shell/ShellCommandParam.java b/android-35/android/adservices/shell/ShellCommandParam.java
new file mode 100644
index 0000000..2b35410
--- /dev/null
+++ b/android-35/android/adservices/shell/ShellCommandParam.java
@@ -0,0 +1,88 @@
+/*
+ * 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.shell;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * Represents request which contains command and args as an input to runShellCommand API.
+ *
+ * @hide
+ */
+public class ShellCommandParam implements Parcelable {
+
+ /* Array containing command name with all the args */
+ private final String[] mCommandArgs;
+
+ public ShellCommandParam(String... commandArgs) {
+ mCommandArgs = Objects.requireNonNull(commandArgs);
+ }
+
+ private ShellCommandParam(Parcel in) {
+ this(in.createStringArray());
+ }
+
+ public static final Creator<ShellCommandParam> CREATOR =
+ new Parcelable.Creator<>() {
+ @Override
+ public ShellCommandParam createFromParcel(Parcel in) {
+ return new ShellCommandParam(in);
+ }
+
+ @Override
+ public ShellCommandParam[] newArray(int size) {
+ return new ShellCommandParam[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeStringArray(mCommandArgs);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof ShellCommandParam)) {
+ return false;
+ }
+
+ ShellCommandParam that = (ShellCommandParam) o;
+ return Arrays.equals(mCommandArgs, that.mCommandArgs);
+ }
+
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(mCommandArgs);
+ }
+
+ /** Get the command name with all the args as a list. */
+ public String[] getCommandArgs() {
+ return mCommandArgs;
+ }
+}
diff --git a/android-35/android/adservices/shell/ShellCommandResult.java b/android-35/android/adservices/shell/ShellCommandResult.java
new file mode 100644
index 0000000..442c67e
--- /dev/null
+++ b/android-35/android/adservices/shell/ShellCommandResult.java
@@ -0,0 +1,136 @@
+/*
+ * 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.shell;
+
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Represents the response from the runShellCommand API.
+ *
+ * @hide
+ */
+public class ShellCommandResult implements Parcelable {
+
+ private static final int RESULT_OK = 0;
+
+ private final int mResultCode;
+ @Nullable private final String mOut;
+ @Nullable private final String mErr;
+
+ private ShellCommandResult(int resultCode, @Nullable String out, @Nullable String err) {
+ mResultCode = resultCode;
+ mOut = out;
+ mErr = err;
+ }
+
+ private ShellCommandResult(Parcel in) {
+ this(in.readInt(), in.readString(), in.readString());
+ }
+
+ private ShellCommandResult(Builder builder) {
+ this(builder.mResultCode, builder.mOut, builder.mErr);
+ }
+
+ public static final Creator<ShellCommandResult> CREATOR =
+ new Parcelable.Creator<>() {
+ @Override
+ public ShellCommandResult createFromParcel(Parcel in) {
+ Objects.requireNonNull(in);
+ return new ShellCommandResult(in);
+ }
+
+ @Override
+ public ShellCommandResult[] newArray(int size) {
+ return new ShellCommandResult[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ Objects.requireNonNull(dest);
+ dest.writeInt(mResultCode);
+ dest.writeString(mOut);
+ dest.writeString(mErr);
+ }
+
+ /** Returns the command status. */
+ public int getResultCode() {
+ return mResultCode;
+ }
+
+ /** Returns {@code true} if {@link #getResultCode} is greater than equal to 0. */
+ public boolean isSuccess() {
+ return getResultCode() >= 0;
+ }
+
+ /** Returns the output of the shell command result. */
+ @Nullable
+ public String getOut() {
+ return mOut;
+ }
+
+ /** Returns the error message associated with this response. */
+ @Nullable
+ public String getErr() {
+ return mErr;
+ }
+
+ /**
+ * Builder for {@link ShellCommandResult}.
+ *
+ * @hide
+ */
+ public static final class Builder {
+ private int mResultCode = RESULT_OK;
+ @Nullable private String mOut;
+ @Nullable private String mErr;
+
+ public Builder() {}
+
+ /** Sets the Status Code. */
+ public Builder setResultCode(int resultCode) {
+ mResultCode = resultCode;
+ return this;
+ }
+
+ /** Sets the shell command output in case of success. */
+ public Builder setOut(@Nullable String out) {
+ mOut = out;
+ return this;
+ }
+
+ /** Sets the error message in case of command failure. */
+ public Builder setErr(@Nullable String err) {
+ mErr = err;
+ return this;
+ }
+
+ /** Builds a {@link ShellCommandResult} object. */
+ public ShellCommandResult build() {
+ return new ShellCommandResult(this);
+ }
+ }
+}
diff --git a/android-35/android/adservices/signals/ProtectedSignalsManager.java b/android-35/android/adservices/signals/ProtectedSignalsManager.java
new file mode 100644
index 0000000..566d3da
--- /dev/null
+++ b/android-35/android/adservices/signals/ProtectedSignalsManager.java
@@ -0,0 +1,233 @@
+/*
+ * 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.signals;
+
+import static android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_PROTECTED_SIGNALS;
+
+import android.adservices.common.AdServicesStatusUtils;
+import android.adservices.common.FledgeErrorResponse;
+import android.adservices.common.SandboxedSdkContextUtils;
+import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.RequiresApi;
+import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
+import android.app.sdksandbox.SandboxedSdkContext;
+import android.content.Context;
+import android.os.Build;
+import android.os.LimitExceededException;
+import android.os.OutcomeReceiver;
+import android.os.RemoteException;
+
+import com.android.adservices.AdServicesCommon;
+import com.android.adservices.LoggerFactory;
+import com.android.adservices.ServiceBinder;
+import com.android.adservices.flags.Flags;
+
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/** ProtectedSignalsManager provides APIs for apps and ad-SDKs to manage their protected signals. */
+@FlaggedApi(Flags.FLAG_PROTECTED_SIGNALS_ENABLED)
+@RequiresApi(Build.VERSION_CODES.S)
+public class ProtectedSignalsManager {
+ private static final LoggerFactory.Logger sLogger = LoggerFactory.getFledgeLogger();
+ /**
+ * Constant that represents the service name for {@link ProtectedSignalsManager} to be used in
+ * {@link android.adservices.AdServicesFrameworkInitializer#registerServiceWrappers}
+ *
+ * @hide
+ */
+ public static final String PROTECTED_SIGNALS_SERVICE = "protected_signals_service";
+
+ @NonNull private Context mContext;
+ @NonNull private ServiceBinder<IProtectedSignalsService> mServiceBinder;
+
+ /**
+ * Factory method for creating an instance of ProtectedSignalsManager.
+ *
+ * @param context The {@link Context} to use
+ * @return A {@link ProtectedSignalsManager} instance
+ */
+ @SuppressLint("ManagerLookup")
+ @NonNull
+ // TODO(b/303896680): Investigate why this lint was not triggered for similar managers
+ public static ProtectedSignalsManager get(@NonNull Context context) {
+ // On T+, context.getSystemService() does more than just call constructor.
+ return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
+ ? context.getSystemService(ProtectedSignalsManager.class)
+ : new ProtectedSignalsManager(context);
+ }
+
+ /**
+ * Create a service binder ProtectedSignalsManager
+ *
+ * @hide
+ */
+ public ProtectedSignalsManager(@NonNull Context context) {
+ Objects.requireNonNull(context);
+
+ // In case the ProtectedSignalsManager is initiated from inside a sdk_sandbox process the
+ // fields will be immediately rewritten by the initialize method below.
+ initialize(context);
+ }
+
+ /**
+ * Initializes {@link ProtectedSignalsManager} with the given {@code context}.
+ *
+ * <p>This method is called by the {@link SandboxedSdkContext} to propagate the correct context.
+ * For more information check the javadoc on the {@link
+ * android.app.sdksandbox.SdkSandboxSystemServiceRegistry}.
+ *
+ * @hide
+ * @see android.app.sdksandbox.SdkSandboxSystemServiceRegistry
+ */
+ public ProtectedSignalsManager initialize(@NonNull Context context) {
+ Objects.requireNonNull(context);
+
+ mContext = context;
+ mServiceBinder =
+ ServiceBinder.getServiceBinder(
+ context,
+ AdServicesCommon.ACTION_PROTECTED_SIGNALS_SERVICE,
+ IProtectedSignalsService.Stub::asInterface);
+ return this;
+ }
+
+ @NonNull
+ IProtectedSignalsService getService() {
+ IProtectedSignalsService service = mServiceBinder.getService();
+ if (service == null) {
+ throw new IllegalStateException("Unable to find the service");
+ }
+ return service;
+ }
+
+ /**
+ * The updateSignals API will retrieve a JSON from the URI that describes which signals to add
+ * or remove. This API also allows registering the encoder endpoint. The endpoint is used to
+ * download an encoding logic, which enables encoding the signals.
+ *
+ * <p>The top level keys for the JSON must correspond to one of 5 commands:
+ *
+ * <p>"put" - Adds a new signal, overwriting any existing signals with the same key. The value
+ * for this is a JSON object where the keys are base 64 strings corresponding to the key to put
+ * for and the values are base 64 string corresponding to the value to put.
+ *
+ * <p>"append" - Appends a new signal/signals to a time series of signals, removing the oldest
+ * signals to make room for the new ones if the size of the series exceeds the given maximum.
+ * The value for this is a JSON object where the keys are base 64 strings corresponding to the
+ * key to append to and the values are objects with two fields: "values" and "maxSignals" .
+ * "values" is a list of base 64 strings corresponding to signal values to append to the time
+ * series. "maxSignals" is the maximum number of values that are allowed in this timeseries. If
+ * the current number of signals associated with the key exceeds maxSignals the oldest signals
+ * will be removed. Note that you can append to a key added by put. Not that appending more than
+ * the maximum number of values will cause a failure.
+ *
+ * <p>"put_if_not_present" - Adds a new signal only if there are no existing signals with the
+ * same key. The value for this is a JSON object where the keys are base 64 strings
+ * corresponding to the key to put for and the values are base 64 string corresponding to the
+ * value to put.
+ *
+ * <p>"remove" - Removes the signal for a key. The value of this is a list of base 64 strings
+ * corresponding to the keys of signals that should be deleted.
+ *
+ * <p>"update_encoder" - Provides an action to update the endpoint, and a URI which can be used
+ * to retrieve an encoding logic. The sub-key for providing an update action is "action" and the
+ * values currently supported are:
+ *
+ * <ol>
+ * <li>"REGISTER" : Registers the encoder endpoint if provided for the first time or
+ * overwrites the existing one with the newly provided endpoint. Providing the "endpoint"
+ * is required for the "REGISTER" action.
+ * </ol>
+ *
+ * <p>The sub-key for providing an encoder endpoint is "endpoint" and the value is the URI
+ * string for the endpoint.
+ *
+ * <p>On success, the onResult method of the provided OutcomeReceiver will be called with an
+ * empty Object. This Object has no significance and is used merely as a placeholder.
+ *
+ * <p>Key may only be operated on by one command per JSON. If two command attempt to operate on
+ * the same key, this method will through an {@link IllegalArgumentException}
+ *
+ * <p>This call fails with an {@link SecurityException} if
+ *
+ * <ol>
+ * <li>the {@code ownerPackageName} is not calling app's package name and/or
+ * <li>the buyer is not authorized to use the API.
+ * </ol>
+ *
+ * <p>This call fails with an {@link IllegalArgumentException} if
+ *
+ * <ol>
+ * <li>The JSON retrieved from the server is not valid.
+ * <li>The provided URI is invalid.
+ * </ol>
+ *
+ * <p>This call fails with {@link LimitExceededException} if the calling package exceeds the
+ * allowed rate limits and is throttled.
+ *
+ * <p>This call fails with an {@link IllegalStateException} if an internal service error is
+ * encountered.
+ */
+ @RequiresPermission(ACCESS_ADSERVICES_PROTECTED_SIGNALS)
+ public void updateSignals(
+ @NonNull UpdateSignalsRequest updateSignalsRequest,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OutcomeReceiver<Object, Exception> receiver) {
+ Objects.requireNonNull(updateSignalsRequest);
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(receiver);
+
+ try {
+ final IProtectedSignalsService service = getService();
+
+ service.updateSignals(
+ new UpdateSignalsInput.Builder(
+ updateSignalsRequest.getUpdateUri(), getCallerPackageName())
+ .build(),
+ new UpdateSignalsCallback.Stub() {
+ @Override
+ public void onSuccess() {
+ executor.execute(() -> receiver.onResult(new Object()));
+ }
+
+ @Override
+ public void onFailure(FledgeErrorResponse failureParcel) {
+ executor.execute(
+ () ->
+ receiver.onError(
+ AdServicesStatusUtils.asException(
+ failureParcel)));
+ }
+ });
+ } catch (RemoteException e) {
+ sLogger.e(e, "Exception");
+ receiver.onError(new IllegalStateException("Internal Error!", e));
+ }
+ }
+
+ private String getCallerPackageName() {
+ SandboxedSdkContext sandboxedSdkContext =
+ SandboxedSdkContextUtils.getAsSandboxedSdkContext(mContext);
+ return sandboxedSdkContext == null
+ ? mContext.getPackageName()
+ : sandboxedSdkContext.getClientPackageName();
+ }
+}
diff --git a/android-35/android/adservices/signals/UpdateSignalsInput.java b/android-35/android/adservices/signals/UpdateSignalsInput.java
new file mode 100644
index 0000000..26a909f
--- /dev/null
+++ b/android-35/android/adservices/signals/UpdateSignalsInput.java
@@ -0,0 +1,189 @@
+/*
+ * 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.signals;
+
+import android.annotation.NonNull;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * The input object wrapping the parameters for the updateSignals API.
+ *
+ * <p>Refer to {@link UpdateSignalsRequest} for more information about the parameters.
+ *
+ * @hide
+ */
+public final class UpdateSignalsInput implements Parcelable {
+ @NonNull private final Uri mUpdateUri;
+ @NonNull private final String mCallerPackageName;
+
+ @NonNull
+ public static final Creator<UpdateSignalsInput> CREATOR =
+ new Creator<>() {
+ @NonNull
+ @Override
+ public UpdateSignalsInput createFromParcel(@NonNull Parcel in) {
+ return new UpdateSignalsInput(in);
+ }
+
+ @NonNull
+ @Override
+ public UpdateSignalsInput[] newArray(int size) {
+ return new UpdateSignalsInput[size];
+ }
+ };
+
+ private UpdateSignalsInput(@NonNull Uri updateUri, @NonNull String callerPackageName) {
+ Objects.requireNonNull(updateUri);
+ Objects.requireNonNull(callerPackageName);
+
+ mUpdateUri = updateUri;
+ mCallerPackageName = callerPackageName;
+ }
+
+ private UpdateSignalsInput(@NonNull Parcel in) {
+ Objects.requireNonNull(in);
+
+ Uri updateUri = Uri.CREATOR.createFromParcel(in);
+ Objects.requireNonNull(updateUri);
+ mUpdateUri = updateUri;
+ String callerPackageName = in.readString();
+ Objects.requireNonNull(callerPackageName);
+ mCallerPackageName = callerPackageName;
+ }
+
+ /**
+ * @return the {@link Uri} from which the signal updates will be fetched.
+ */
+ @NonNull
+ public Uri getUpdateUri() {
+ return mUpdateUri;
+ }
+
+ /**
+ * @return the caller app's package name.
+ */
+ @NonNull
+ public String getCallerPackageName() {
+ return mCallerPackageName;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ Objects.requireNonNull(dest);
+
+ mUpdateUri.writeToParcel(dest, flags);
+ dest.writeString(mCallerPackageName);
+ }
+
+ /**
+ * @return {@code true} if and only if the other object is {@link UpdateSignalsRequest} with the
+ * same update URI and package name
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof UpdateSignalsInput)) return false;
+ UpdateSignalsInput that = (UpdateSignalsInput) o;
+ return mUpdateUri.equals(that.mUpdateUri)
+ && mCallerPackageName.equals(that.mCallerPackageName);
+ }
+
+ /**
+ * @return the hash of the {@link UpdateSignalsInput} object's data.
+ */
+ @Override
+ public int hashCode() {
+ return Objects.hash(mUpdateUri, mCallerPackageName);
+ }
+
+ @Override
+ public String toString() {
+ return "UpdateSignalsInput{"
+ + "mUpdateUri="
+ + mUpdateUri
+ + ", mCallerPackageName='"
+ + mCallerPackageName
+ + '\''
+ + '}';
+ }
+
+ /**
+ * Builder for {@link UpdateSignalsInput} objects.
+ *
+ * @hide
+ */
+ public static final class Builder {
+ @NonNull private Uri mUpdateUri;
+ @NonNull private String mCallerPackageName;
+
+ /**
+ * Instantiates a {@link UpdateSignalsInput.Builder} with the {@link Uri} from which the
+ * JSON is to be fetched and the caller app's package name.
+ *
+ * @throws NullPointerException if any non-null parameter is null.
+ */
+ public Builder(@NonNull Uri updateUri, @NonNull String callerPackageName) {
+ Objects.requireNonNull(updateUri);
+ Objects.requireNonNull(callerPackageName);
+
+ this.mUpdateUri = updateUri;
+ this.mCallerPackageName = callerPackageName;
+ }
+
+ /**
+ * Sets the {@link Uri} from which the signal updates will be fetched.
+ *
+ * <p>See {@link #getUpdateUri()} ()} for details.
+ */
+ @NonNull
+ public Builder setUpdateUri(@NonNull Uri updateUri) {
+ Objects.requireNonNull(updateUri);
+ this.mUpdateUri = updateUri;
+ return this;
+ }
+
+ /**
+ * Sets the caller app's package name.
+ *
+ * <p>See {@link #getCallerPackageName()} for details.
+ */
+ @NonNull
+ public Builder setCallerPackageName(@NonNull String callerPackageName) {
+ Objects.requireNonNull(callerPackageName);
+ this.mCallerPackageName = callerPackageName;
+ return this;
+ }
+
+ /**
+ * Builds an instance of a {@link UpdateSignalsInput}.
+ *
+ * @throws NullPointerException if any non-null parameter is null.
+ */
+ @NonNull
+ public UpdateSignalsInput build() {
+ return new UpdateSignalsInput(mUpdateUri, mCallerPackageName);
+ }
+ }
+}
diff --git a/android-35/android/adservices/signals/UpdateSignalsRequest.java b/android-35/android/adservices/signals/UpdateSignalsRequest.java
new file mode 100644
index 0000000..717d913
--- /dev/null
+++ b/android-35/android/adservices/signals/UpdateSignalsRequest.java
@@ -0,0 +1,113 @@
+/*
+ * 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.signals;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.net.Uri;
+
+import com.android.adservices.flags.Flags;
+
+import java.util.Objects;
+
+/**
+ * The request object for updateSignals.
+ *
+ * <p>{@code updateUri} is the only parameter. It represents the URI that the service will reach out
+ * to retrieve the signals updates.
+ */
+@FlaggedApi(Flags.FLAG_PROTECTED_SIGNALS_ENABLED)
+public final class UpdateSignalsRequest {
+ @NonNull private final Uri mUpdateUri;
+
+ private UpdateSignalsRequest(@NonNull Uri updateUri) {
+ Objects.requireNonNull(updateUri, "updateUri must not be null in UpdateSignalsRequest");
+
+ mUpdateUri = updateUri;
+ }
+
+ /**
+ * @return the {@link Uri} from which the signal updates will be fetched.
+ */
+ @NonNull
+ public Uri getUpdateUri() {
+ return mUpdateUri;
+ }
+
+ /**
+ * @return {@code true} if and only if the other object is {@link UpdateSignalsRequest} with the
+ * same update URI.
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof UpdateSignalsRequest)) return false;
+ UpdateSignalsRequest other = (UpdateSignalsRequest) o;
+ return mUpdateUri.equals(other.mUpdateUri);
+ }
+
+ /**
+ * @return the hash of the {@link UpdateSignalsRequest} object's data.
+ */
+ @Override
+ public int hashCode() {
+ return Objects.hash(mUpdateUri);
+ }
+
+ /**
+ * @return a human-readable representation of {@link UpdateSignalsRequest}.
+ */
+ @Override
+ public String toString() {
+ return "UpdateSignalsRequest{" + "updateUri=" + mUpdateUri + '}';
+ }
+
+ /** Builder for {@link UpdateSignalsRequest} objects. */
+ public static final class Builder {
+ @NonNull private Uri mUpdateUri;
+
+ /**
+ * Instantiates a {@link Builder} with the {@link Uri} from which the signal updates will be
+ * fetched.
+ */
+ public Builder(@NonNull Uri updateUri) {
+ Objects.requireNonNull(updateUri);
+ this.mUpdateUri = updateUri;
+ }
+
+ /**
+ * Sets the {@link Uri} from which the JSON is to be fetched.
+ *
+ * <p>See {@link #getUpdateUri()} ()} for details.
+ */
+ @NonNull
+ public Builder setUpdateUri(@NonNull Uri updateUri) {
+ Objects.requireNonNull(updateUri, "updateUri must not be null in UpdateSignalsRequest");
+ this.mUpdateUri = updateUri;
+ return this;
+ }
+
+ /**
+ * Builds an instance of a {@link UpdateSignalsRequest}.
+ *
+ * @throws NullPointerException if any non-null parameter is null.
+ */
+ @NonNull
+ public UpdateSignalsRequest build() {
+ return new UpdateSignalsRequest(mUpdateUri);
+ }
+ }
+}
diff --git a/android-35/android/adservices/topics/EncryptedTopic.java b/android-35/android/adservices/topics/EncryptedTopic.java
new file mode 100644
index 0000000..3892c70
--- /dev/null
+++ b/android-35/android/adservices/topics/EncryptedTopic.java
@@ -0,0 +1,111 @@
+/*
+ * 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.topics;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+
+import com.android.adservices.flags.Flags;
+
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * Encrypted form of {@link android.adservices.topics.Topic}. This object will be used to return
+ * encrypted topic cipher text along with necessary fields required to decrypt it.
+ *
+ * <p>Decryption of {@link EncryptedTopic#getEncryptedTopic()} should give json string for {@link
+ * Topic}. Example of decrypted json string: {@code { "taxonomy_version": 5, "model_version": 2,
+ * "topic_id": 10010 }}
+ *
+ * <p>Decryption of cipher text is expected to happen on the server with the corresponding algorithm
+ * and private key for the public key {@link EncryptedTopic#getKeyIdentifier()}}.
+ *
+ * <p>Detailed steps on decryption can be found on <a
+ * href="https://developer.android.com/design-for-safety/privacy-sandbox/guides/topics">Developer
+ * Guide</a>.
+ */
+@FlaggedApi(Flags.FLAG_TOPICS_ENCRYPTION_ENABLED)
+public final class EncryptedTopic {
+ @NonNull private final byte[] mEncryptedTopic;
+ @NonNull private final String mKeyIdentifier;
+ @NonNull private final byte[] mEncapsulatedKey;
+
+ /**
+ * Creates encrypted version of the {@link Topic} object.
+ *
+ * @param encryptedTopic byte array cipher text containing encrypted {@link Topic} json string.
+ * @param keyIdentifier key used to identify the public key used for encryption.
+ * @param encapsulatedKey encapsulated key generated during HPKE setup.
+ */
+ public EncryptedTopic(
+ @NonNull byte[] encryptedTopic,
+ @NonNull String keyIdentifier,
+ @NonNull byte[] encapsulatedKey) {
+ mEncryptedTopic = Objects.requireNonNull(encryptedTopic);
+ mKeyIdentifier = Objects.requireNonNull(keyIdentifier);
+ mEncapsulatedKey = Objects.requireNonNull(encapsulatedKey);
+ }
+
+ /** Returns encrypted bytes for the JSON version of the {@link Topic} object as cipher text. */
+ @NonNull
+ public byte[] getEncryptedTopic() {
+ return mEncryptedTopic;
+ }
+
+ /** Returns key identifier for the used encryption key. */
+ @NonNull
+ public String getKeyIdentifier() {
+ return mKeyIdentifier;
+ }
+
+ /** Returns the encapsulated key generated during HPKE setup. */
+ @NonNull
+ public byte[] getEncapsulatedKey() {
+ return mEncapsulatedKey;
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ if (this == object) return true;
+ if (!(object instanceof EncryptedTopic)) return false;
+ EncryptedTopic encryptedTopic = (EncryptedTopic) object;
+ return Arrays.equals(getEncryptedTopic(), encryptedTopic.getEncryptedTopic())
+ && getKeyIdentifier().equals(encryptedTopic.getKeyIdentifier())
+ && Arrays.equals(getEncapsulatedKey(), encryptedTopic.getEncapsulatedKey());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(
+ Arrays.hashCode(getEncryptedTopic()),
+ getKeyIdentifier(),
+ Arrays.hashCode(getEncapsulatedKey()));
+ }
+
+ @Override
+ public java.lang.String toString() {
+ return "EncryptedTopic{"
+ + "mEncryptedTopic="
+ + Arrays.toString(mEncryptedTopic)
+ + ", mKeyIdentifier="
+ + mKeyIdentifier
+ + ", mEncapsulatedKey="
+ + Arrays.toString(mEncapsulatedKey)
+ + '}';
+ }
+}
diff --git a/android-35/android/adservices/topics/GetTopicsParam.java b/android-35/android/adservices/topics/GetTopicsParam.java
new file mode 100644
index 0000000..ce80436
--- /dev/null
+++ b/android-35/android/adservices/topics/GetTopicsParam.java
@@ -0,0 +1,171 @@
+/*
+ * 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.topics;
+
+import static android.adservices.topics.TopicsManager.EMPTY_SDK;
+import static android.adservices.topics.TopicsManager.RECORD_OBSERVATION_DEFAULT;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Represent input params to the getTopics API.
+ *
+ * @hide
+ */
+public final class GetTopicsParam implements Parcelable {
+ private final String mSdkName;
+ private final String mSdkPackageName;
+ private final String mAppPackageName;
+ private final boolean mRecordObservation;
+
+ private GetTopicsParam(
+ @NonNull String sdkName,
+ @Nullable String sdkPackageName,
+ @NonNull String appPackageName,
+ boolean recordObservation) {
+ mSdkName = sdkName;
+ mSdkPackageName = sdkPackageName;
+ mAppPackageName = appPackageName;
+ mRecordObservation = recordObservation;
+ }
+
+ private GetTopicsParam(@NonNull Parcel in) {
+ mSdkName = in.readString();
+ mSdkPackageName = in.readString();
+ mAppPackageName = in.readString();
+ mRecordObservation = in.readBoolean();
+ }
+
+ public static final @NonNull Creator<GetTopicsParam> CREATOR =
+ new Parcelable.Creator<GetTopicsParam>() {
+ @Override
+ public GetTopicsParam createFromParcel(Parcel in) {
+ return new GetTopicsParam(in);
+ }
+
+ @Override
+ public GetTopicsParam[] newArray(int size) {
+ return new GetTopicsParam[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeString(mSdkName);
+ out.writeString(mSdkPackageName);
+ out.writeString(mAppPackageName);
+ out.writeBoolean(mRecordObservation);
+ }
+
+ /** Get the Sdk Name. This is the name in the <sdk-library> tag of the Manifest. */
+ @NonNull
+ public String getSdkName() {
+ return mSdkName;
+ }
+
+ /** Get the Sdk Package Name. This is the package name in the Manifest. */
+ @NonNull
+ public String getSdkPackageName() {
+ return mSdkPackageName;
+ }
+
+ /** Get the App PackageName. */
+ @NonNull
+ public String getAppPackageName() {
+ return mAppPackageName;
+ }
+
+ /** Get the Record Observation. */
+ public boolean shouldRecordObservation() {
+ return mRecordObservation;
+ }
+
+ /** Builder for {@link GetTopicsParam} objects. */
+ public static final class Builder {
+ private String mSdkName;
+ private String mSdkPackageName;
+ private String mAppPackageName;
+ private boolean mRecordObservation = RECORD_OBSERVATION_DEFAULT;
+
+ public Builder() {}
+
+ /**
+ * Set the Sdk Name. When the app calls the Topics API directly without using a SDK, don't
+ * set this field.
+ */
+ public @NonNull Builder setSdkName(@NonNull String sdkName) {
+ mSdkName = sdkName;
+ return this;
+ }
+
+ /**
+ * Set the Sdk Package Name. When the app calls the Topics API directly without using an
+ * SDK, don't set this field.
+ */
+ public @NonNull Builder setSdkPackageName(@NonNull String sdkPackageName) {
+ mSdkPackageName = sdkPackageName;
+ return this;
+ }
+
+ /** Set the App PackageName. */
+ public @NonNull Builder setAppPackageName(@NonNull String appPackageName) {
+ mAppPackageName = appPackageName;
+ return this;
+ }
+
+ /**
+ * Set the Record Observation. Whether to record that the caller has observed the topics of
+ * the host app or not. This will be used to determine if the caller can receive the topic
+ * in the next epoch.
+ */
+ public @NonNull Builder setShouldRecordObservation(boolean recordObservation) {
+ mRecordObservation = recordObservation;
+ return this;
+ }
+
+ /** Builds a {@link GetTopicsParam} instance. */
+ public @NonNull GetTopicsParam build() {
+ if (mSdkName == null) {
+ // When Sdk name is not set, we assume the App calls the Topics API directly.
+ // We set the Sdk name to empty to mark this.
+ mSdkName = EMPTY_SDK;
+ }
+
+ if (mSdkPackageName == null) {
+ // When Sdk package name is not set, we assume the App calls the Topics API
+ // directly.
+ // We set the Sdk package name to empty to mark this.
+ mSdkPackageName = EMPTY_SDK;
+ }
+
+ if (mAppPackageName == null || mAppPackageName.isEmpty()) {
+ throw new IllegalArgumentException("App PackageName must not be empty or null");
+ }
+
+ return new GetTopicsParam(
+ mSdkName, mSdkPackageName, mAppPackageName, mRecordObservation);
+ }
+ }
+}
diff --git a/android-35/android/adservices/topics/GetTopicsRequest.java b/android-35/android/adservices/topics/GetTopicsRequest.java
new file mode 100644
index 0000000..cc8e51b
--- /dev/null
+++ b/android-35/android/adservices/topics/GetTopicsRequest.java
@@ -0,0 +1,97 @@
+/*
+ * 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.topics;
+
+import static android.adservices.topics.TopicsManager.EMPTY_SDK;
+import static android.adservices.topics.TopicsManager.RECORD_OBSERVATION_DEFAULT;
+
+import android.annotation.NonNull;
+
+/** Get Topics Request. */
+public final class GetTopicsRequest {
+
+ /** Name of Ads SDK that is involved in this request. */
+ private final String mAdsSdkName;
+
+ /** Whether to record that the caller has observed the topics of the host app or not. */
+ private final boolean mRecordObservation;
+
+ private GetTopicsRequest(@NonNull Builder builder) {
+ mAdsSdkName = builder.mAdsSdkName;
+ mRecordObservation = builder.mRecordObservation;
+ }
+
+ /** Get the Sdk Name. */
+ @NonNull
+ public String getAdsSdkName() {
+ return mAdsSdkName;
+ }
+
+ /** Get Record Observation. */
+ public boolean shouldRecordObservation() {
+ return mRecordObservation;
+ }
+
+ /** Builder for {@link GetTopicsRequest} objects. */
+ public static final class Builder {
+ private String mAdsSdkName = EMPTY_SDK;
+ private boolean mRecordObservation = RECORD_OBSERVATION_DEFAULT;
+
+ /** Creates a {@link Builder} for {@link GetTopicsRequest} objects. */
+ public Builder() {}
+
+ /**
+ * Set Ads Sdk Name.
+ *
+ * <p>This must be called by SDKs running outside of the Sandbox. Other clients must not
+ * call it.
+ *
+ * @param adsSdkName the Ads Sdk Name.
+ */
+ @NonNull
+ public Builder setAdsSdkName(@NonNull String adsSdkName) {
+ // This is the case the SDK calling from outside of the Sandbox.
+ // Check if the caller set the adsSdkName
+ if (adsSdkName == null) {
+ throw new IllegalArgumentException(
+ "When calling Topics API outside of the Sandbox, caller should set Ads Sdk"
+ + " Name");
+ }
+
+ mAdsSdkName = adsSdkName;
+ return this;
+ }
+
+ /**
+ * Set the Record Observation.
+ *
+ * @param recordObservation whether to record that the caller has observed the topics of the
+ * host app or not. This will be used to determine if the caller can receive the topic
+ * in the next epoch.
+ */
+ @NonNull
+ public Builder setShouldRecordObservation(boolean recordObservation) {
+ mRecordObservation = recordObservation;
+ return this;
+ }
+
+ /** Builds a {@link GetTopicsRequest} instance. */
+ @NonNull
+ public GetTopicsRequest build() {
+ return new GetTopicsRequest(this);
+ }
+ }
+}
diff --git a/android-35/android/adservices/topics/GetTopicsResponse.java b/android-35/android/adservices/topics/GetTopicsResponse.java
new file mode 100644
index 0000000..45e8de4
--- /dev/null
+++ b/android-35/android/adservices/topics/GetTopicsResponse.java
@@ -0,0 +1,114 @@
+/*
+ * 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.topics;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+
+import com.android.adservices.flags.Flags;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/** Represent the result from the getTopics API. */
+public final class GetTopicsResponse {
+ /** List of Topic objects returned by getTopics API. */
+ private final List<Topic> mTopics;
+
+ /** List of EncryptedTopic objects returned by getTopics API. */
+ private final List<EncryptedTopic> mEncryptedTopics;
+
+ private GetTopicsResponse(List<Topic> topics, List<EncryptedTopic> encryptedTopics) {
+ mTopics = topics;
+ mEncryptedTopics = encryptedTopics;
+ }
+
+ /** Returns a {@link List} of {@link Topic} objects returned by getTopics API. */
+ @NonNull
+ public List<Topic> getTopics() {
+ return mTopics;
+ }
+
+ /** Returns a {@link List} of {@link EncryptedTopic} objects returned by getTopics API. */
+ @NonNull
+ @FlaggedApi(Flags.FLAG_TOPICS_ENCRYPTION_ENABLED)
+ public List<EncryptedTopic> getEncryptedTopics() {
+ return mEncryptedTopics;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof GetTopicsResponse)) {
+ return false;
+ }
+ GetTopicsResponse that = (GetTopicsResponse) o;
+ return mTopics.equals(that.mTopics) && mEncryptedTopics.equals(that.mEncryptedTopics);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mTopics, mEncryptedTopics);
+ }
+
+ /**
+ * Builder for {@link GetTopicsResponse} objects. This class should be used in test
+ * implementation as expected response from Topics API
+ */
+ public static final class Builder {
+ private List<Topic> mTopics = new ArrayList<>();
+ private List<EncryptedTopic> mEncryptedTopics = new ArrayList<>();
+
+ /**
+ * Creates a {@link Builder} for {@link GetTopicsResponse} objects.
+ *
+ * @param topics The list of the returned Topics.
+ * @deprecated This function is deprecated.
+ */
+ @Deprecated
+ public Builder(@NonNull List<Topic> topics) {
+ mTopics = Objects.requireNonNull(topics);
+ }
+
+ /**
+ * Creates a {@link Builder} for {@link GetTopicsResponse} objects.
+ *
+ * @param topics The list of the returned Topics.
+ * @param encryptedTopics The list of encrypted Topics.
+ */
+ @FlaggedApi(Flags.FLAG_TOPICS_ENCRYPTION_ENABLED)
+ public Builder(@NonNull List<Topic> topics, @NonNull List<EncryptedTopic> encryptedTopics) {
+ mTopics = Objects.requireNonNull(topics);
+ mEncryptedTopics = Objects.requireNonNull(encryptedTopics);
+ }
+
+ /**
+ * Builds a {@link GetTopicsResponse} instance.
+ *
+ * @throws IllegalArgumentException if any of the params are null.
+ */
+ @NonNull
+ public GetTopicsResponse build() {
+ if (mTopics == null || mEncryptedTopics == null) {
+ throw new IllegalArgumentException("Topics is null");
+ }
+ return new GetTopicsResponse(mTopics, mEncryptedTopics);
+ }
+ }
+}
diff --git a/android-35/android/adservices/topics/GetTopicsResult.java b/android-35/android/adservices/topics/GetTopicsResult.java
new file mode 100644
index 0000000..0cc654b
--- /dev/null
+++ b/android-35/android/adservices/topics/GetTopicsResult.java
@@ -0,0 +1,466 @@
+/*
+ * 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.topics;
+
+import static android.adservices.common.AdServicesStatusUtils.STATUS_SUCCESS;
+
+import android.adservices.common.AdServicesResponse;
+import android.adservices.common.AdServicesStatusUtils;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Represent the result from the getTopics API.
+ *
+ * @hide
+ */
+public final class GetTopicsResult extends AdServicesResponse {
+ private final List<Long> mTaxonomyVersions;
+ private final List<Long> mModelVersions;
+ private final List<Integer> mTopics;
+ private final List<byte[]> mEncryptedTopics;
+ private final List<String> mEncryptionKeys;
+ private final List<byte[]> mEncapsulatedKeys;
+
+ private GetTopicsResult(
+ @AdServicesStatusUtils.StatusCode int resultCode,
+ String errorMessage,
+ List<Long> taxonomyVersions,
+ List<Long> modelVersions,
+ List<Integer> topics,
+ List<byte[]> encryptedTopics,
+ List<String> encryptionKeys,
+ List<byte[]> encapsulatedKeys) {
+ super(resultCode, errorMessage);
+ mTaxonomyVersions = taxonomyVersions;
+ mModelVersions = modelVersions;
+ mTopics = topics;
+ mEncryptedTopics = encryptedTopics;
+ mEncryptionKeys = encryptionKeys;
+ mEncapsulatedKeys = encapsulatedKeys;
+ }
+
+ private GetTopicsResult(@NonNull Parcel in) {
+ super(in.readInt(), in.readString());
+
+ mTaxonomyVersions = Collections.unmodifiableList(readLongList(in));
+ mModelVersions = Collections.unmodifiableList(readLongList(in));
+ mTopics = Collections.unmodifiableList(readIntegerList(in));
+ mEncryptedTopics = Collections.unmodifiableList(readByteArrayList(in));
+ mEncryptionKeys = Collections.unmodifiableList(readStringList(in));
+ mEncapsulatedKeys = Collections.unmodifiableList(readByteArrayList(in));
+ }
+
+ public static final @NonNull Creator<GetTopicsResult> CREATOR =
+ new Parcelable.Creator<GetTopicsResult>() {
+ @Override
+ public GetTopicsResult createFromParcel(Parcel in) {
+ return new GetTopicsResult(in);
+ }
+
+ @Override
+ public GetTopicsResult[] newArray(int size) {
+ return new GetTopicsResult[size];
+ }
+ };
+
+ /** @hide */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** @hide */
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeInt(mStatusCode);
+ out.writeString(mErrorMessage);
+ writeLongList(out, mTaxonomyVersions);
+ writeLongList(out, mModelVersions);
+ writeIntegerList(out, mTopics);
+ writeByteArrayList(out, mEncryptedTopics);
+ writeStringList(out, mEncryptionKeys);
+ writeByteArrayList(out, mEncapsulatedKeys);
+ }
+
+ /**
+ * Returns {@code true} if {@link #getResultCode} equals {@link
+ * AdServicesStatusUtils#STATUS_SUCCESS}.
+ */
+ public boolean isSuccess() {
+ return getResultCode() == STATUS_SUCCESS;
+ }
+
+ /** Returns one of the {@code RESULT} constants defined in {@link GetTopicsResult}. */
+ public @AdServicesStatusUtils.StatusCode int getResultCode() {
+ return mStatusCode;
+ }
+
+ /**
+ * Returns the error message associated with this result.
+ *
+ * <p>If {@link #isSuccess} is {@code true}, the error message is always {@code null}. The error
+ * message may be {@code null} even if {@link #isSuccess} is {@code false}.
+ */
+ @Nullable
+ public String getErrorMessage() {
+ return mErrorMessage;
+ }
+
+ /** Get the Taxonomy Versions. */
+ public List<Long> getTaxonomyVersions() {
+ return mTaxonomyVersions;
+ }
+
+ /** Get the Model Versions. */
+ public List<Long> getModelVersions() {
+ return mModelVersions;
+ }
+
+ @NonNull
+ public List<Integer> getTopics() {
+ return mTopics;
+ }
+
+ @NonNull
+ public List<byte[]> getEncryptedTopics() {
+ return mEncryptedTopics;
+ }
+
+ @NonNull
+ public List<String> getEncryptionKeys() {
+ return mEncryptionKeys;
+ }
+
+ @NonNull
+ public List<byte[]> getEncapsulatedKeys() {
+ return mEncapsulatedKeys;
+ }
+
+ @Override
+ public String toString() {
+ return "GetTopicsResult{"
+ + "mResultCode="
+ + mStatusCode
+ + ", mErrorMessage='"
+ + mErrorMessage
+ + '\''
+ + ", mTaxonomyVersions="
+ + mTaxonomyVersions
+ + ", mModelVersions="
+ + mModelVersions
+ + ", mTopics="
+ + mTopics
+ + ", mEncryptedTopics="
+ + prettyPrint(mEncryptedTopics)
+ + ", mEncryptionKeys="
+ + mEncryptionKeys
+ + ", mEncapsulatedKeys="
+ + prettyPrint(mEncapsulatedKeys)
+ + '}';
+ }
+
+ private String prettyPrint(List<byte[]> listOfByteArrays) {
+ StringBuilder stringBuilder = new StringBuilder();
+ stringBuilder.append("[");
+ for (int index = 0; index < listOfByteArrays.size(); index++) {
+ stringBuilder.append(Arrays.toString(listOfByteArrays.get(index)));
+ if (index != listOfByteArrays.size() - 1) {
+ stringBuilder.append(", ");
+ }
+ }
+ stringBuilder.append("]");
+ return stringBuilder.toString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+
+ if (!(o instanceof GetTopicsResult)) {
+ return false;
+ }
+
+ GetTopicsResult that = (GetTopicsResult) o;
+
+ return mStatusCode == that.mStatusCode
+ && Objects.equals(mErrorMessage, that.mErrorMessage)
+ && mTaxonomyVersions.equals(that.mTaxonomyVersions)
+ && mModelVersions.equals(that.mModelVersions)
+ && mTopics.equals(that.mTopics)
+ && equals(mEncryptedTopics, that.mEncryptedTopics)
+ && mEncryptionKeys.equals(that.mEncryptionKeys);
+ }
+
+ private static boolean equals(List<byte[]> list1, List<byte[]> list2) {
+ if (list1 == null || list2 == null) {
+ return false;
+ }
+ if (list1.size() != list2.size()) {
+ return false;
+ }
+ for (int i = 0; i < list1.size(); i++) {
+ if (!Arrays.equals(list1.get(i), list2.get(i))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(
+ mStatusCode,
+ mErrorMessage,
+ mTaxonomyVersions,
+ mModelVersions,
+ mTopics,
+ hashCode(mEncryptedTopics),
+ mEncryptionKeys,
+ hashCode(mEncryptedTopics));
+ }
+
+ private static int hashCode(List<byte[]> list) {
+ int hash = 0;
+ for (byte[] bytes : list) {
+ hash += Arrays.hashCode(bytes);
+ }
+ return hash;
+ }
+
+ // Read the list of long from parcel.
+ private static List<Long> readLongList(@NonNull Parcel in) {
+ List<Long> list = new ArrayList<>();
+
+ int toReadCount = in.readInt();
+ // Negative toReadCount is handled implicitly
+ for (int i = 0; i < toReadCount; i++) {
+ list.add(in.readLong());
+ }
+
+ return list;
+ }
+
+ // Read the list of integer from parcel.
+ private static List<Integer> readIntegerList(@NonNull Parcel in) {
+ List<Integer> list = new ArrayList<>();
+
+ int toReadCount = in.readInt();
+ // Negative toReadCount is handled implicitly
+ for (int i = 0; i < toReadCount; i++) {
+ list.add(in.readInt());
+ }
+
+ return list;
+ }
+
+ // Read the list of integer from parcel.
+ private static List<String> readStringList(@NonNull Parcel in) {
+ List<String> list = new ArrayList<>();
+
+ int toReadCount = in.readInt();
+ // Negative toReadCount is handled implicitly
+ for (int i = 0; i < toReadCount; i++) {
+ list.add(in.readString());
+ }
+
+ return list;
+ }
+
+ // Read the list of byte arrays from parcel.
+ private static List<byte[]> readByteArrayList(@NonNull Parcel in) {
+ List<byte[]> list = new ArrayList<>();
+
+ int toReadCount = in.readInt();
+ // Negative toReadCount is handled implicitly
+ for (int i = 0; i < toReadCount; i++) {
+ list.add(in.createByteArray());
+ }
+
+ return list;
+ }
+
+ // Write a List of Long to parcel.
+ private static void writeLongList(@NonNull Parcel out, @Nullable List<Long> val) {
+ if (val == null) {
+ out.writeInt(-1);
+ return;
+ }
+ out.writeInt(val.size());
+ for (Long l : val) {
+ out.writeLong(l);
+ }
+ }
+
+ // Write a List of Integer to parcel.
+ private static void writeIntegerList(@NonNull Parcel out, @Nullable List<Integer> val) {
+ if (val == null) {
+ out.writeInt(-1);
+ return;
+ }
+ out.writeInt(val.size());
+ for (Integer integer : val) {
+ out.writeInt(integer);
+ }
+ }
+
+ // Write a List of String to parcel.
+ private static void writeStringList(@NonNull Parcel out, @Nullable List<String> val) {
+ if (val == null) {
+ out.writeInt(-1);
+ return;
+ }
+ out.writeInt(val.size());
+ for (String string : val) {
+ out.writeString(string);
+ }
+ }
+
+ // Write a List of byte array to parcel.
+ private static void writeByteArrayList(@NonNull Parcel out, @Nullable List<byte[]> val) {
+ if (val == null) {
+ out.writeInt(-1);
+ return;
+ }
+ out.writeInt(val.size());
+ for (byte[] bytes : val) {
+ out.writeByteArray(bytes);
+ }
+ }
+
+ /**
+ * Builder for {@link GetTopicsResult} objects.
+ *
+ * @hide
+ */
+ public static final class Builder {
+ private @AdServicesStatusUtils.StatusCode int mResultCode;
+ private String mErrorMessage;
+ private List<Long> mTaxonomyVersions = new ArrayList<>();
+ private List<Long> mModelVersions = new ArrayList<>();
+ private List<Integer> mTopics = new ArrayList<>();
+ private List<byte[]> mEncryptedTopics = new ArrayList<>();
+ private List<String> mEncryptionKeys = new ArrayList<>();
+ private List<byte[]> mEncapsulatedKeys = new ArrayList<>();
+
+ public Builder() {}
+
+ /** Set the Result Code. */
+ @NonNull
+ public Builder setResultCode(@AdServicesStatusUtils.StatusCode int resultCode) {
+ mResultCode = resultCode;
+ return this;
+ }
+
+ /** Set the Error Message. */
+ @NonNull
+ public Builder setErrorMessage(@Nullable String errorMessage) {
+ mErrorMessage = errorMessage;
+ return this;
+ }
+
+ /** Set the Taxonomy Version. */
+ @NonNull
+ public Builder setTaxonomyVersions(@NonNull List<Long> taxonomyVersions) {
+ mTaxonomyVersions = taxonomyVersions;
+ return this;
+ }
+
+ /** Set the Model Version. */
+ @NonNull
+ public Builder setModelVersions(@NonNull List<Long> modelVersions) {
+ mModelVersions = modelVersions;
+ return this;
+ }
+
+ /** Set the list of the returned Topics */
+ @NonNull
+ public Builder setTopics(@NonNull List<Integer> topics) {
+ mTopics = topics;
+ return this;
+ }
+
+ /** Set the list of the returned encrypted topics */
+ @NonNull
+ public Builder setEncryptedTopics(@NonNull List<byte[]> encryptedTopics) {
+ mEncryptedTopics = encryptedTopics;
+ return this;
+ }
+
+ /** Set the list of the encryption keys */
+ @NonNull
+ public Builder setEncryptionKeys(@NonNull List<String> encryptionKeys) {
+ mEncryptionKeys = encryptionKeys;
+ return this;
+ }
+
+ /** Set the list of encapsulated keys generated via encryption */
+ @NonNull
+ public Builder setEncapsulatedKeys(@NonNull List<byte[]> encapsulatedKeys) {
+ mEncapsulatedKeys = encapsulatedKeys;
+ return this;
+ }
+
+ /**
+ * Builds a {@link GetTopicsResult} instance.
+ *
+ * <p>throws IllegalArgumentException if any of the params are null or there is any mismatch
+ * in the size of lists.
+ */
+ @NonNull
+ public GetTopicsResult build() {
+ if (mTopics == null
+ || mTaxonomyVersions == null
+ || mModelVersions == null
+ || mEncryptedTopics == null
+ || mEncryptionKeys == null) {
+ throw new IllegalArgumentException(
+ "One of the mandatory params of GetTopicsResult is null");
+ }
+
+ if (mTopics.size() != mTaxonomyVersions.size()
+ || mTopics.size() != mModelVersions.size()) {
+ throw new IllegalArgumentException("Size mismatch in Topics");
+ }
+
+ if (mEncryptedTopics.size() != mEncryptionKeys.size()
+ || mEncryptedTopics.size() != mEncapsulatedKeys.size()) {
+ throw new IllegalArgumentException("Size mismatch in EncryptedTopic lists");
+ }
+
+ return new GetTopicsResult(
+ mResultCode,
+ mErrorMessage,
+ mTaxonomyVersions,
+ mModelVersions,
+ mTopics,
+ mEncryptedTopics,
+ mEncryptionKeys,
+ mEncapsulatedKeys);
+ }
+ }
+}
diff --git a/android-35/android/adservices/topics/Topic.java b/android-35/android/adservices/topics/Topic.java
new file mode 100644
index 0000000..593762a
--- /dev/null
+++ b/android-35/android/adservices/topics/Topic.java
@@ -0,0 +1,80 @@
+/*
+ * 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.topics;
+
+import java.util.Objects;
+
+/** Represent the topic result from the getTopics API. */
+public final class Topic {
+ private final long mTaxonomyVersion;
+ private final long mModelVersion;
+ private final int mTopicId;
+
+ /**
+ * Creates an object which represents the result from the getTopics API.
+ *
+ * @param mTaxonomyVersion a long representing the version of the taxonomy.
+ * @param mModelVersion a long representing the version of the model.
+ * @param mTopicId an integer representing the unique id of a topic.
+ */
+ public Topic(long mTaxonomyVersion, long mModelVersion, int mTopicId) {
+ this.mTaxonomyVersion = mTaxonomyVersion;
+ this.mModelVersion = mModelVersion;
+ this.mTopicId = mTopicId;
+ }
+
+ /** Get the ModelVersion. */
+ public long getModelVersion() {
+ return mModelVersion;
+ }
+
+ /** Get the TaxonomyVersion. */
+ public long getTaxonomyVersion() {
+ return mTaxonomyVersion;
+ }
+
+ /** Get the Topic ID. */
+ public int getTopicId() {
+ return mTopicId;
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ if (this == object) return true;
+ if (!(object instanceof Topic)) return false;
+ Topic topic = (Topic) object;
+ return getTaxonomyVersion() == topic.getTaxonomyVersion()
+ && getModelVersion() == topic.getModelVersion()
+ && getTopicId() == topic.getTopicId();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(getTaxonomyVersion(), getModelVersion(), getTopicId());
+ }
+
+ @Override
+ public java.lang.String toString() {
+ return "Topic{"
+ + "mTaxonomyVersion="
+ + mTaxonomyVersion
+ + ", mModelVersion="
+ + mModelVersion
+ + ", mTopicCode="
+ + mTopicId
+ + '}';
+ }
+}
diff --git a/android-35/android/adservices/topics/TopicsManager.java b/android-35/android/adservices/topics/TopicsManager.java
new file mode 100644
index 0000000..b1c8dd6
--- /dev/null
+++ b/android-35/android/adservices/topics/TopicsManager.java
@@ -0,0 +1,279 @@
+/*
+ * 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.topics;
+
+import static android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_TOPICS;
+
+import android.adservices.common.AdServicesStatusUtils;
+import android.adservices.common.CallerMetadata;
+import android.adservices.common.SandboxedSdkContextUtils;
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.TestApi;
+import android.app.sdksandbox.SandboxedSdkContext;
+import android.content.Context;
+import android.os.Build;
+import android.os.OutcomeReceiver;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.text.TextUtils;
+
+import androidx.annotation.RequiresApi;
+
+import com.android.adservices.AdServicesCommon;
+import com.android.adservices.LoggerFactory;
+import com.android.adservices.ServiceBinder;
+import com.android.adservices.shared.common.ServiceUnavailableException;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/**
+ * TopicsManager provides APIs for App and Ad-Sdks to get the user interest topics in a privacy
+ * preserving way.
+ *
+ * <p>The instance of the {@link TopicsManager} can be obtained using {@link
+ * Context#getSystemService} and {@link TopicsManager} class.
+ */
+@RequiresApi(Build.VERSION_CODES.S)
+public final class TopicsManager {
+ private static final LoggerFactory.Logger sLogger = LoggerFactory.getTopicsLogger();
+ /**
+ * Constant that represents the service name for {@link TopicsManager} to be used in {@link
+ * android.adservices.AdServicesFrameworkInitializer#registerServiceWrappers}
+ *
+ * @hide
+ */
+ public static final String TOPICS_SERVICE = "topics_service";
+
+ // When an app calls the Topics API directly, it sets the SDK name to empty string.
+ static final String EMPTY_SDK = "";
+
+ // Default value is true to record SDK's Observation when it calls Topics API.
+ static final boolean RECORD_OBSERVATION_DEFAULT = true;
+
+ private Context mContext;
+ private ServiceBinder<ITopicsService> mServiceBinder;
+
+ /**
+ * Factory method for creating an instance of TopicsManager.
+ *
+ * @param context The {@link Context} to use
+ * @return A {@link TopicsManager} instance
+ */
+ @NonNull
+ public static TopicsManager get(@NonNull Context context) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
+ throw new ServiceUnavailableException();
+ }
+ // On TM+, context.getSystemService() does more than just call constructor.
+ return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
+ ? context.getSystemService(TopicsManager.class)
+ : new TopicsManager(context);
+ }
+
+ /**
+ * Create TopicsManager
+ *
+ * @hide
+ */
+ public TopicsManager(Context context) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
+ throw new ServiceUnavailableException();
+ }
+ // In case the TopicsManager is initiated from inside a sdk_sandbox process the fields
+ // will be immediately rewritten by the initialize method below.
+ initialize(context);
+ }
+
+ /**
+ * Initializes {@link TopicsManager} with the given {@code context}.
+ *
+ * <p>This method is called by the {@link SandboxedSdkContext} to propagate the correct context.
+ * For more information check the javadoc on the {@link
+ * android.app.sdksandbox.SdkSandboxSystemServiceRegistry}.
+ *
+ * @hide
+ * @see android.app.sdksandbox.SdkSandboxSystemServiceRegistry
+ */
+ public TopicsManager initialize(Context context) {
+ mContext = context;
+ mServiceBinder =
+ ServiceBinder.getServiceBinder(
+ context,
+ AdServicesCommon.ACTION_TOPICS_SERVICE,
+ ITopicsService.Stub::asInterface);
+ return this;
+ }
+
+ @NonNull
+ private ITopicsService getService() {
+ ITopicsService service = mServiceBinder.getService();
+ if (service == null) {
+ throw new ServiceUnavailableException();
+ }
+ return service;
+ }
+
+ /**
+ * Return the topics.
+ *
+ * @param getTopicsRequest The request for obtaining Topics.
+ * @param executor The executor to run callback.
+ * @param callback The callback that's called after topics are available or an error occurs.
+ * @throws IllegalStateException if this API is not available.
+ */
+ @NonNull
+ @RequiresPermission(ACCESS_ADSERVICES_TOPICS)
+ public void getTopics(
+ @NonNull GetTopicsRequest getTopicsRequest,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OutcomeReceiver<GetTopicsResponse, Exception> callback) {
+ Objects.requireNonNull(getTopicsRequest);
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+ CallerMetadata callerMetadata =
+ new CallerMetadata.Builder()
+ .setBinderElapsedTimestamp(SystemClock.elapsedRealtime())
+ .build();
+ final ITopicsService service = getService();
+ String sdkName = getTopicsRequest.getAdsSdkName();
+ String appPackageName = "";
+ String sdkPackageName = "";
+ // First check if context is SandboxedSdkContext or not
+ SandboxedSdkContext sandboxedSdkContext =
+ SandboxedSdkContextUtils.getAsSandboxedSdkContext(mContext);
+ if (sandboxedSdkContext != null) {
+ // This is the case with the Sandbox.
+ sdkPackageName = sandboxedSdkContext.getSdkPackageName();
+ appPackageName = sandboxedSdkContext.getClientPackageName();
+
+ if (!TextUtils.isEmpty(sdkName)) {
+ throw new IllegalArgumentException(
+ "When calling Topics API from Sandbox, caller should not set Ads Sdk Name");
+ }
+
+ String sdkNameFromSandboxedContext = sandboxedSdkContext.getSdkName();
+ if (null == sdkNameFromSandboxedContext || sdkNameFromSandboxedContext.isEmpty()) {
+ throw new IllegalArgumentException(
+ "Sdk Name From SandboxedSdkContext should not be null or empty");
+ }
+
+ sdkName = sdkNameFromSandboxedContext;
+ } else {
+ // This is the case without the Sandbox.
+ if (null == sdkName) {
+ // When adsSdkName is not set, we assume the App calls the Topics API directly.
+ // We set the adsSdkName to empty to mark this.
+ sdkName = EMPTY_SDK;
+ }
+ appPackageName = mContext.getPackageName();
+ }
+ try {
+ service.getTopics(
+ new GetTopicsParam.Builder()
+ .setAppPackageName(appPackageName)
+ .setSdkName(sdkName)
+ .setSdkPackageName(sdkPackageName)
+ .setShouldRecordObservation(getTopicsRequest.shouldRecordObservation())
+ .build(),
+ callerMetadata,
+ new IGetTopicsCallback.Stub() {
+ @Override
+ public void onResult(GetTopicsResult resultParcel) {
+ executor.execute(
+ () -> {
+ if (resultParcel.isSuccess()) {
+ callback.onResult(buildGetTopicsResponse(resultParcel));
+ } else {
+ // TODO: Errors should be returned in onFailure method.
+ callback.onError(
+ AdServicesStatusUtils.asException(
+ resultParcel));
+ }
+ });
+ }
+
+ @Override
+ public void onFailure(int resultCode) {
+ executor.execute(
+ () ->
+ callback.onError(
+ AdServicesStatusUtils.asException(resultCode)));
+ }
+ });
+ } catch (RemoteException e) {
+ sLogger.e(e, "RemoteException");
+ callback.onError(e);
+ }
+ }
+
+ private GetTopicsResponse buildGetTopicsResponse(GetTopicsResult resultParcel) {
+ return new GetTopicsResponse.Builder(
+ getTopicList(resultParcel), getEncryptedTopicList(resultParcel))
+ .build();
+ }
+
+ private List<Topic> getTopicList(GetTopicsResult resultParcel) {
+ List<Long> taxonomyVersionsList = resultParcel.getTaxonomyVersions();
+ List<Long> modelVersionsList = resultParcel.getModelVersions();
+ List<Integer> topicsCodeList = resultParcel.getTopics();
+ List<Topic> topicList = new ArrayList<>();
+ int size = taxonomyVersionsList.size();
+ for (int i = 0; i < size; i++) {
+ Topic topic =
+ new Topic(
+ taxonomyVersionsList.get(i),
+ modelVersionsList.get(i),
+ topicsCodeList.get(i));
+ topicList.add(topic);
+ }
+
+ return topicList;
+ }
+
+ private List<EncryptedTopic> getEncryptedTopicList(GetTopicsResult resultParcel) {
+ List<EncryptedTopic> encryptedTopicList = new ArrayList<>();
+ List<byte[]> encryptedTopics = resultParcel.getEncryptedTopics();
+ List<String> encryptionKeys = resultParcel.getEncryptionKeys();
+ List<byte[]> encapsulatedKeys = resultParcel.getEncapsulatedKeys();
+ int size = encryptedTopics.size();
+ for (int i = 0; i < size; i++) {
+ EncryptedTopic encryptedTopic =
+ new EncryptedTopic(
+ encryptedTopics.get(i), encryptionKeys.get(i), encapsulatedKeys.get(i));
+ encryptedTopicList.add(encryptedTopic);
+ }
+
+ return encryptedTopicList;
+ }
+
+ /**
+ * If the service is in an APK (as opposed to the system service), unbind it from the service to
+ * allow the APK process to die.
+ *
+ * @hide Not sure if we'll need this functionality in the final API. For now, we need it for
+ * performance testing to simulate "cold-start" situations.
+ */
+ // TODO: change to @VisibleForTesting
+ @TestApi
+ public void unbindFromService() {
+ mServiceBinder.unbindFromService();
+ }
+}