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/META-INF/MANIFEST.MF b/android-35/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..8032f19
--- /dev/null
+++ b/android-35/META-INF/MANIFEST.MF
@@ -0,0 +1,3 @@
+Manifest-Version: 1.0
+Created-By: soong_zip
+
diff --git a/android-35/android/accessibilityservice/AccessibilityButtonController.java b/android-35/android/accessibilityservice/AccessibilityButtonController.java
new file mode 100644
index 0000000..2ccad1d
--- /dev/null
+++ b/android-35/android/accessibilityservice/AccessibilityButtonController.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2017 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.accessibilityservice;
+
+import android.annotation.NonNull;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.Slog;
+
+import java.util.Objects;
+
+/**
+ * Controller for the accessibility button within the system's navigation area
+ * <p>
+ * This class may be used to query the accessibility button's state and register
+ * callbacks for interactions with and state changes to the accessibility button when
+ * {@link AccessibilityServiceInfo#FLAG_REQUEST_ACCESSIBILITY_BUTTON} is set.
+ * </p>
+ * <p>
+ * <strong>Note:</strong> This class and
+ * {@link AccessibilityServiceInfo#FLAG_REQUEST_ACCESSIBILITY_BUTTON} should not be used as
+ * the sole means for offering functionality to users via an {@link AccessibilityService}.
+ * Some device implementations may choose not to provide a software-rendered system
+ * navigation area, making this affordance permanently unavailable.
+ * </p>
+ * <p>
+ * <strong>Note:</strong> On device implementations where the accessibility button is
+ * supported, it may not be available at all times, such as when a foreground application uses
+ * {@link android.view.View#SYSTEM_UI_FLAG_HIDE_NAVIGATION}. A user may also choose to assign
+ * this button to another accessibility service or feature. In each of these cases, a
+ * registered {@link AccessibilityButtonCallback}'s
+ * {@link AccessibilityButtonCallback#onAvailabilityChanged(AccessibilityButtonController, boolean)}
+ * method will be invoked to provide notifications of changes in the accessibility button's
+ * availability to the registering service.
+ * </p>
+ */
+public final class AccessibilityButtonController {
+ private static final String LOG_TAG = "A11yButtonController";
+
+ private final IAccessibilityServiceConnection mServiceConnection;
+ private final Object mLock;
+ private ArrayMap<AccessibilityButtonCallback, Handler> mCallbacks;
+
+ AccessibilityButtonController(@NonNull IAccessibilityServiceConnection serviceConnection) {
+ mServiceConnection = serviceConnection;
+ mLock = new Object();
+ }
+
+ /**
+ * Retrieves whether the accessibility button in the system's navigation area is
+ * available to the calling service.
+ * <p>
+ * <strong>Note:</strong> If the service is not yet connected (e.g.
+ * {@link AccessibilityService#onServiceConnected()} has not yet been called) or the
+ * service has been disconnected, this method will have no effect and return {@code false}.
+ * </p>
+ *
+ * @return {@code true} if the accessibility button in the system's navigation area is
+ * available to the calling service, {@code false} otherwise
+ */
+ public boolean isAccessibilityButtonAvailable() {
+ if (mServiceConnection != null) {
+ try {
+ return mServiceConnection.isAccessibilityButtonAvailable();
+ } catch (RemoteException re) {
+ Slog.w(LOG_TAG, "Failed to get accessibility button availability.", re);
+ re.rethrowFromSystemServer();
+ return false;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Registers the provided {@link AccessibilityButtonCallback} for interaction and state
+ * changes callbacks related to the accessibility button.
+ *
+ * @param callback the callback to add, must be non-null
+ */
+ public void registerAccessibilityButtonCallback(@NonNull AccessibilityButtonCallback callback) {
+ registerAccessibilityButtonCallback(callback, new Handler(Looper.getMainLooper()));
+ }
+
+ /**
+ * Registers the provided {@link AccessibilityButtonCallback} for interaction and state
+ * change callbacks related to the accessibility button. The callback will occur on the
+ * specified {@link Handler}'s thread, or on the services's main thread if the handler is
+ * {@code null}.
+ *
+ * @param callback the callback to add, must be non-null
+ * @param handler the handler on which the callback should execute, must be non-null
+ */
+ public void registerAccessibilityButtonCallback(@NonNull AccessibilityButtonCallback callback,
+ @NonNull Handler handler) {
+ Objects.requireNonNull(callback);
+ Objects.requireNonNull(handler);
+ synchronized (mLock) {
+ if (mCallbacks == null) {
+ mCallbacks = new ArrayMap<>();
+ }
+
+ mCallbacks.put(callback, handler);
+ }
+ }
+
+ /**
+ * Unregisters the provided {@link AccessibilityButtonCallback} for interaction and state
+ * change callbacks related to the accessibility button.
+ *
+ * @param callback the callback to remove, must be non-null
+ */
+ public void unregisterAccessibilityButtonCallback(
+ @NonNull AccessibilityButtonCallback callback) {
+ Objects.requireNonNull(callback);
+ synchronized (mLock) {
+ if (mCallbacks == null) {
+ return;
+ }
+
+ final int keyIndex = mCallbacks.indexOfKey(callback);
+ final boolean hasKey = keyIndex >= 0;
+ if (hasKey) {
+ mCallbacks.removeAt(keyIndex);
+ }
+ }
+ }
+
+ /**
+ * Dispatches the accessibility button click to any registered callbacks. This should
+ * be called on the service's main thread.
+ */
+ void dispatchAccessibilityButtonClicked() {
+ final ArrayMap<AccessibilityButtonCallback, Handler> entries;
+ synchronized (mLock) {
+ if (mCallbacks == null || mCallbacks.isEmpty()) {
+ Slog.w(LOG_TAG, "Received accessibility button click with no callbacks!");
+ return;
+ }
+
+ // Callbacks may remove themselves. Perform a shallow copy to avoid concurrent
+ // modification.
+ entries = new ArrayMap<>(mCallbacks);
+ }
+
+ for (int i = 0, count = entries.size(); i < count; i++) {
+ final AccessibilityButtonCallback callback = entries.keyAt(i);
+ final Handler handler = entries.valueAt(i);
+ handler.post(() -> callback.onClicked(this));
+ }
+ }
+
+ /**
+ * Dispatches the accessibility button availability changes to any registered callbacks.
+ * This should be called on the service's main thread.
+ */
+ void dispatchAccessibilityButtonAvailabilityChanged(boolean available) {
+ final ArrayMap<AccessibilityButtonCallback, Handler> entries;
+ synchronized (mLock) {
+ if (mCallbacks == null || mCallbacks.isEmpty()) {
+ Slog.w(LOG_TAG,
+ "Received accessibility button availability change with no callbacks!");
+ return;
+ }
+
+ // Callbacks may remove themselves. Perform a shallow copy to avoid concurrent
+ // modification.
+ entries = new ArrayMap<>(mCallbacks);
+ }
+
+ for (int i = 0, count = entries.size(); i < count; i++) {
+ final AccessibilityButtonCallback callback = entries.keyAt(i);
+ final Handler handler = entries.valueAt(i);
+ handler.post(() -> callback.onAvailabilityChanged(this, available));
+ }
+ }
+
+ /**
+ * Callback for interaction with and changes to state of the accessibility button
+ * within the system's navigation area.
+ */
+ public static abstract class AccessibilityButtonCallback {
+
+ /**
+ * Called when the accessibility button in the system's navigation area is clicked.
+ *
+ * @param controller the controller used to register for this callback
+ */
+ public void onClicked(AccessibilityButtonController controller) {}
+
+ /**
+ * Called when the availability of the accessibility button in the system's
+ * navigation area has changed. The accessibility button may become unavailable
+ * because the device shopped showing the button, the button was assigned to another
+ * service, or for other reasons.
+ *
+ * @param controller the controller used to register for this callback
+ * @param available {@code true} if the accessibility button is available to this
+ * service, {@code false} otherwise
+ */
+ public void onAvailabilityChanged(AccessibilityButtonController controller,
+ boolean available) {
+ }
+ }
+}
diff --git a/android-35/android/accessibilityservice/AccessibilityGestureEvent.java b/android-35/android/accessibilityservice/AccessibilityGestureEvent.java
new file mode 100644
index 0000000..15e29c2
--- /dev/null
+++ b/android-35/android/accessibilityservice/AccessibilityGestureEvent.java
@@ -0,0 +1,347 @@
+/*
+ * Copyright (C) 2019 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.accessibilityservice;
+
+
+import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_DOUBLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD;
+import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SINGLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_DOWN;
+import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_LEFT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_RIGHT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_UP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_TRIPLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_TRIPLE_TAP_AND_HOLD;
+import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_DOUBLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD;
+import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SINGLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SINGLE_TAP_AND_HOLD;
+import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_DOWN;
+import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_LEFT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_RIGHT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_UP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_TRIPLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_TRIPLE_TAP_AND_HOLD;
+import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_DOUBLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_DOUBLE_TAP_AND_HOLD;
+import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SINGLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SWIPE_DOWN;
+import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SWIPE_LEFT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SWIPE_RIGHT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SWIPE_UP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_TRIPLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_DOUBLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_DOUBLE_TAP_AND_HOLD;
+import static android.accessibilityservice.AccessibilityService.GESTURE_PASSTHROUGH;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_DOWN;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_DOWN_AND_LEFT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_DOWN_AND_RIGHT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_DOWN_AND_UP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_LEFT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_LEFT_AND_DOWN;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_LEFT_AND_RIGHT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_LEFT_AND_UP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_RIGHT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_RIGHT_AND_DOWN;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_RIGHT_AND_LEFT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_RIGHT_AND_UP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_UP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_UP_AND_DOWN;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_UP_AND_LEFT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_UP_AND_RIGHT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_TOUCH_EXPLORATION;
+import static android.accessibilityservice.AccessibilityService.GESTURE_UNKNOWN;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.TestApi;
+import android.content.pm.ParceledListSlice;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.MotionEvent;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class describes the gesture event including gesture id and which display it happens
+ * on.
+ * <p>
+ * <strong>Note:</strong> Accessibility services setting the
+ * {@link android.accessibilityservice.AccessibilityServiceInfo#FLAG_REQUEST_TOUCH_EXPLORATION_MODE}
+ * flag can receive gestures.
+ *
+ * @see AccessibilityService#onGesture(AccessibilityGestureEvent)
+ */
+
+public final class AccessibilityGestureEvent implements Parcelable {
+
+ /** @hide */
+ @IntDef(prefix = { "GESTURE_" }, value = {
+ GESTURE_UNKNOWN,
+ GESTURE_TOUCH_EXPLORATION,
+ GESTURE_2_FINGER_SINGLE_TAP,
+ GESTURE_2_FINGER_DOUBLE_TAP,
+ GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD,
+ GESTURE_2_FINGER_TRIPLE_TAP,
+ GESTURE_2_FINGER_TRIPLE_TAP_AND_HOLD,
+ GESTURE_3_FINGER_SINGLE_TAP,
+ GESTURE_3_FINGER_SINGLE_TAP_AND_HOLD,
+ GESTURE_3_FINGER_DOUBLE_TAP,
+ GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD,
+ GESTURE_3_FINGER_TRIPLE_TAP,
+ GESTURE_3_FINGER_TRIPLE_TAP_AND_HOLD,
+ GESTURE_DOUBLE_TAP,
+ GESTURE_DOUBLE_TAP_AND_HOLD,
+ GESTURE_SWIPE_UP,
+ GESTURE_SWIPE_UP_AND_LEFT,
+ GESTURE_SWIPE_UP_AND_DOWN,
+ GESTURE_SWIPE_UP_AND_RIGHT,
+ GESTURE_SWIPE_DOWN,
+ GESTURE_SWIPE_DOWN_AND_LEFT,
+ GESTURE_SWIPE_DOWN_AND_UP,
+ GESTURE_SWIPE_DOWN_AND_RIGHT,
+ GESTURE_SWIPE_LEFT,
+ GESTURE_SWIPE_LEFT_AND_UP,
+ GESTURE_SWIPE_LEFT_AND_RIGHT,
+ GESTURE_SWIPE_LEFT_AND_DOWN,
+ GESTURE_SWIPE_RIGHT,
+ GESTURE_SWIPE_RIGHT_AND_UP,
+ GESTURE_SWIPE_RIGHT_AND_LEFT,
+ GESTURE_SWIPE_RIGHT_AND_DOWN,
+ GESTURE_2_FINGER_SWIPE_DOWN,
+ GESTURE_2_FINGER_SWIPE_LEFT,
+ GESTURE_2_FINGER_SWIPE_RIGHT,
+ GESTURE_2_FINGER_SWIPE_UP,
+ GESTURE_3_FINGER_SWIPE_DOWN,
+ GESTURE_3_FINGER_SWIPE_LEFT,
+ GESTURE_3_FINGER_SWIPE_RIGHT,
+ GESTURE_3_FINGER_SWIPE_UP,
+ GESTURE_4_FINGER_DOUBLE_TAP,
+ GESTURE_4_FINGER_DOUBLE_TAP_AND_HOLD,
+ GESTURE_4_FINGER_SINGLE_TAP,
+ GESTURE_4_FINGER_SWIPE_DOWN,
+ GESTURE_4_FINGER_SWIPE_LEFT,
+ GESTURE_4_FINGER_SWIPE_RIGHT,
+ GESTURE_4_FINGER_SWIPE_UP,
+ GESTURE_4_FINGER_TRIPLE_TAP
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface GestureId {}
+
+ @GestureId
+ private final int mGestureId;
+ private final int mDisplayId;
+ private List<MotionEvent> mMotionEvents = new ArrayList<>();
+
+ /**
+ * Constructs an AccessibilityGestureEvent to be dispatched to an accessibility service.
+ *
+ * @param gestureId the id number of the gesture.
+ * @param displayId the display on which this gesture was performed.
+ * @param motionEvents the motion events that lead to this gesture.
+ */
+ public AccessibilityGestureEvent(
+ int gestureId, int displayId, @NonNull List<MotionEvent> motionEvents) {
+ mGestureId = gestureId;
+ mDisplayId = displayId;
+ mMotionEvents.addAll(motionEvents);
+ }
+
+ /** @hide */
+ @TestApi
+ public AccessibilityGestureEvent(int gestureId, int displayId) {
+ this(gestureId, displayId, new ArrayList<MotionEvent>());
+ }
+
+ private AccessibilityGestureEvent(@NonNull Parcel parcel) {
+ mGestureId = parcel.readInt();
+ mDisplayId = parcel.readInt();
+ ParceledListSlice<MotionEvent> slice = parcel.readParcelable(getClass().getClassLoader(), android.content.pm.ParceledListSlice.class);
+ mMotionEvents = slice.getList();
+ }
+
+ /**
+ * Returns the display id of the received-gesture display, for use with
+ * {@link android.hardware.display.DisplayManager#getDisplay(int)}.
+ *
+ * @return the display id.
+ */
+ public int getDisplayId() {
+ return mDisplayId;
+ }
+
+ /**
+ * Returns performed gesture id.
+ *
+ * @return the performed gesture id.
+ *
+ */
+ @GestureId public int getGestureId() {
+ return mGestureId;
+ }
+
+ /**
+ * Returns the motion events that lead to this gesture.
+ *
+ */
+ @NonNull
+ public List<MotionEvent> getMotionEvents() {
+ return mMotionEvents;
+ }
+
+ /**
+ * When we asynchronously use {@link AccessibilityGestureEvent}, we should make a copy,
+ * because motionEvent may be recycled before we use async.
+ *
+ * @hide
+ */
+ @NonNull
+ public AccessibilityGestureEvent copyForAsync() {
+ return new AccessibilityGestureEvent(mGestureId, mDisplayId,
+ mMotionEvents.stream().map(MotionEvent::copy).toList());
+ }
+
+ /**
+ * After we use {@link AccessibilityGestureEvent} asynchronously, we should recycle the
+ * MotionEvent, avoid memory leaks.
+ *
+ * @hide
+ */
+ public void recycle() {
+ mMotionEvents.forEach(MotionEvent::recycle);
+ mMotionEvents.clear();
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ StringBuilder stringBuilder = new StringBuilder("AccessibilityGestureEvent[");
+ stringBuilder.append("gestureId: ").append(gestureIdToString(mGestureId));
+ stringBuilder.append(", ");
+ stringBuilder.append("displayId: ").append(mDisplayId);
+ stringBuilder.append(", ");
+ stringBuilder.append("Motion Events: [");
+ for (int i = 0; i < mMotionEvents.size(); ++i) {
+ String action = MotionEvent.actionToString(mMotionEvents.get(i).getActionMasked());
+ stringBuilder.append(action);
+ if (i < (mMotionEvents.size() - 1)) {
+ stringBuilder.append(", ");
+ } else {
+ stringBuilder.append("]");
+ }
+ }
+ stringBuilder.append(']');
+ return stringBuilder.toString();
+ }
+
+ /**
+ * Returns a string representation of the specified gesture id.
+ */
+ @NonNull
+ public static String gestureIdToString(int id) {
+ switch (id) {
+ case GESTURE_UNKNOWN: return "GESTURE_UNKNOWN";
+ case GESTURE_PASSTHROUGH: return "GESTURE_PASSTHROUGH";
+ case GESTURE_TOUCH_EXPLORATION: return "GESTURE_TOUCH_EXPLORATION";
+ case GESTURE_2_FINGER_SINGLE_TAP: return "GESTURE_2_FINGER_SINGLE_TAP";
+ case GESTURE_2_FINGER_TRIPLE_TAP_AND_HOLD:
+ return "GESTURE_2_FINGER_TRIPLE_TAP_AND_HOLD";
+ case GESTURE_2_FINGER_DOUBLE_TAP: return "GESTURE_2_FINGER_DOUBLE_TAP";
+ case GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD:
+ return "GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD";
+ case GESTURE_2_FINGER_TRIPLE_TAP: return "GESTURE_2_FINGER_TRIPLE_TAP";
+ case GESTURE_3_FINGER_SINGLE_TAP: return "GESTURE_3_FINGER_SINGLE_TAP";
+ case GESTURE_3_FINGER_SINGLE_TAP_AND_HOLD:
+ return "GESTURE_3_FINGER_SINGLE_TAP_AND_HOLD";
+ case GESTURE_3_FINGER_DOUBLE_TAP: return "GESTURE_3_FINGER_DOUBLE_TAP";
+ case GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD:
+ return "GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD";
+ case GESTURE_3_FINGER_TRIPLE_TAP: return "GESTURE_3_FINGER_TRIPLE_TAP";
+ case GESTURE_3_FINGER_TRIPLE_TAP_AND_HOLD:
+ return "GESTURE_3_FINGER_TRIPLE_TAP_AND_HOLD";
+ case GESTURE_4_FINGER_SINGLE_TAP: return "GESTURE_4_FINGER_SINGLE_TAP";
+ case GESTURE_4_FINGER_DOUBLE_TAP: return "GESTURE_4_FINGER_DOUBLE_TAP";
+ case GESTURE_4_FINGER_DOUBLE_TAP_AND_HOLD:
+ return "GESTURE_4_FINGER_DOUBLE_TAP_AND_HOLD";
+ case GESTURE_4_FINGER_TRIPLE_TAP: return "GESTURE_4_FINGER_TRIPLE_TAP";
+ case GESTURE_DOUBLE_TAP: return "GESTURE_DOUBLE_TAP";
+ case GESTURE_DOUBLE_TAP_AND_HOLD: return "GESTURE_DOUBLE_TAP_AND_HOLD";
+ case GESTURE_SWIPE_DOWN: return "GESTURE_SWIPE_DOWN";
+ case GESTURE_SWIPE_DOWN_AND_LEFT: return "GESTURE_SWIPE_DOWN_AND_LEFT";
+ case GESTURE_SWIPE_DOWN_AND_UP: return "GESTURE_SWIPE_DOWN_AND_UP";
+ case GESTURE_SWIPE_DOWN_AND_RIGHT: return "GESTURE_SWIPE_DOWN_AND_RIGHT";
+ case GESTURE_SWIPE_LEFT: return "GESTURE_SWIPE_LEFT";
+ case GESTURE_SWIPE_LEFT_AND_UP: return "GESTURE_SWIPE_LEFT_AND_UP";
+ case GESTURE_SWIPE_LEFT_AND_RIGHT: return "GESTURE_SWIPE_LEFT_AND_RIGHT";
+ case GESTURE_SWIPE_LEFT_AND_DOWN: return "GESTURE_SWIPE_LEFT_AND_DOWN";
+ case GESTURE_SWIPE_RIGHT: return "GESTURE_SWIPE_RIGHT";
+ case GESTURE_SWIPE_RIGHT_AND_UP: return "GESTURE_SWIPE_RIGHT_AND_UP";
+ case GESTURE_SWIPE_RIGHT_AND_LEFT: return "GESTURE_SWIPE_RIGHT_AND_LEFT";
+ case GESTURE_SWIPE_RIGHT_AND_DOWN: return "GESTURE_SWIPE_RIGHT_AND_DOWN";
+ case GESTURE_SWIPE_UP: return "GESTURE_SWIPE_UP";
+ case GESTURE_SWIPE_UP_AND_LEFT: return "GESTURE_SWIPE_UP_AND_LEFT";
+ case GESTURE_SWIPE_UP_AND_DOWN: return "GESTURE_SWIPE_UP_AND_DOWN";
+ case GESTURE_SWIPE_UP_AND_RIGHT: return "GESTURE_SWIPE_UP_AND_RIGHT";
+ case GESTURE_2_FINGER_SWIPE_DOWN: return "GESTURE_2_FINGER_SWIPE_DOWN";
+ case GESTURE_2_FINGER_SWIPE_LEFT: return "GESTURE_2_FINGER_SWIPE_LEFT";
+ case GESTURE_2_FINGER_SWIPE_RIGHT: return "GESTURE_2_FINGER_SWIPE_RIGHT";
+ case GESTURE_2_FINGER_SWIPE_UP: return "GESTURE_2_FINGER_SWIPE_UP";
+ case GESTURE_3_FINGER_SWIPE_DOWN: return "GESTURE_3_FINGER_SWIPE_DOWN";
+ case GESTURE_3_FINGER_SWIPE_LEFT: return "GESTURE_3_FINGER_SWIPE_LEFT";
+ case GESTURE_3_FINGER_SWIPE_RIGHT: return "GESTURE_3_FINGER_SWIPE_RIGHT";
+ case GESTURE_3_FINGER_SWIPE_UP: return "GESTURE_3_FINGER_SWIPE_UP";
+ case GESTURE_4_FINGER_SWIPE_DOWN: return "GESTURE_4_FINGER_SWIPE_DOWN";
+ case GESTURE_4_FINGER_SWIPE_LEFT: return "GESTURE_4_FINGER_SWIPE_LEFT";
+ case GESTURE_4_FINGER_SWIPE_RIGHT: return "GESTURE_4_FINGER_SWIPE_RIGHT";
+ case GESTURE_4_FINGER_SWIPE_UP: return "GESTURE_4_FINGER_SWIPE_UP";
+ default: return Integer.toHexString(id);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel parcel, int flags) {
+ parcel.writeInt(mGestureId);
+ parcel.writeInt(mDisplayId);
+ parcel.writeParcelable(new ParceledListSlice<MotionEvent>(mMotionEvents), 0);
+ }
+
+ /**
+ * @see Parcelable.Creator
+ */
+ public static final @NonNull Parcelable.Creator<AccessibilityGestureEvent> CREATOR =
+ new Parcelable.Creator<AccessibilityGestureEvent>() {
+ public AccessibilityGestureEvent createFromParcel(Parcel parcel) {
+ return new AccessibilityGestureEvent(parcel);
+ }
+
+ public AccessibilityGestureEvent[] newArray(int size) {
+ return new AccessibilityGestureEvent[size];
+ }
+ };
+
+}
diff --git a/android-35/android/accessibilityservice/AccessibilityInputMethodSession.java b/android-35/android/accessibilityservice/AccessibilityInputMethodSession.java
new file mode 100644
index 0000000..ecf449d
--- /dev/null
+++ b/android-35/android/accessibilityservice/AccessibilityInputMethodSession.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.accessibilityservice;
+
+import android.view.inputmethod.EditorInfo;
+
+import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection;
+
+interface AccessibilityInputMethodSession {
+ void finishInput();
+
+ void updateSelection(int oldSelStart, int oldSelEnd, int newSelStart, int newSelEnd,
+ int candidatesStart, int candidatesEnd);
+
+ void invalidateInput(EditorInfo editorInfo, IRemoteAccessibilityInputConnection connection,
+ int sessionId);
+
+ void setEnabled(boolean enabled);
+}
diff --git a/android-35/android/accessibilityservice/AccessibilityInputMethodSessionWrapper.java b/android-35/android/accessibilityservice/AccessibilityInputMethodSessionWrapper.java
new file mode 100644
index 0000000..3252ab2
--- /dev/null
+++ b/android-35/android/accessibilityservice/AccessibilityInputMethodSessionWrapper.java
@@ -0,0 +1,116 @@
+/*
+ * 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.accessibilityservice;
+
+import android.annotation.AnyThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Handler;
+import android.os.Looper;
+import android.view.inputmethod.EditorInfo;
+
+import com.android.internal.inputmethod.IAccessibilityInputMethodSession;
+import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+final class AccessibilityInputMethodSessionWrapper extends IAccessibilityInputMethodSession.Stub {
+ private final Handler mHandler;
+
+ @NonNull
+ private final AtomicReference<AccessibilityInputMethodSession> mSessionRef;
+
+ AccessibilityInputMethodSessionWrapper(
+ @NonNull Looper looper, @NonNull AccessibilityInputMethodSession session) {
+ mSessionRef = new AtomicReference<>(session);
+ mHandler = Handler.createAsync(looper);
+ }
+
+ @AnyThread
+ @Nullable
+ AccessibilityInputMethodSession getSession() {
+ return mSessionRef.get();
+ }
+
+ @Override
+ public void updateSelection(int oldSelStart, int oldSelEnd,
+ int newSelStart, int newSelEnd, int candidatesStart, int candidatesEnd) {
+ if (mHandler.getLooper().isCurrentThread()) {
+ doUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd, candidatesStart,
+ candidatesEnd);
+ } else {
+ mHandler.post(() -> doUpdateSelection(oldSelStart, oldSelEnd, newSelStart,
+ newSelEnd, candidatesStart, candidatesEnd));
+ }
+ }
+
+ private void doUpdateSelection(int oldSelStart, int oldSelEnd,
+ int newSelStart, int newSelEnd, int candidatesStart, int candidatesEnd) {
+ final AccessibilityInputMethodSession session = mSessionRef.get();
+ if (session != null) {
+ session.updateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd, candidatesStart,
+ candidatesEnd);
+ }
+ }
+
+ @Override
+ public void finishInput() {
+ if (mHandler.getLooper().isCurrentThread()) {
+ doFinishInput();
+ } else {
+ mHandler.post(this::doFinishInput);
+ }
+ }
+
+ private void doFinishInput() {
+ final AccessibilityInputMethodSession session = mSessionRef.get();
+ if (session != null) {
+ session.finishInput();
+ }
+ }
+
+ @Override
+ public void finishSession() {
+ if (mHandler.getLooper().isCurrentThread()) {
+ doFinishSession();
+ } else {
+ mHandler.post(this::doFinishSession);
+ }
+ }
+
+ private void doFinishSession() {
+ mSessionRef.set(null);
+ }
+
+ @Override
+ public void invalidateInput(EditorInfo editorInfo,
+ IRemoteAccessibilityInputConnection connection, int sessionId) {
+ if (mHandler.getLooper().isCurrentThread()) {
+ doInvalidateInput(editorInfo, connection, sessionId);
+ } else {
+ mHandler.post(() -> doInvalidateInput(editorInfo, connection, sessionId));
+ }
+ }
+
+ private void doInvalidateInput(EditorInfo editorInfo,
+ IRemoteAccessibilityInputConnection connection, int sessionId) {
+ final AccessibilityInputMethodSession session = mSessionRef.get();
+ if (session != null) {
+ session.invalidateInput(editorInfo, connection, sessionId);
+ }
+ }
+}
diff --git a/android-35/android/accessibilityservice/AccessibilityService.java b/android-35/android/accessibilityservice/AccessibilityService.java
new file mode 100644
index 0000000..fd9600c
--- /dev/null
+++ b/android-35/android/accessibilityservice/AccessibilityService.java
@@ -0,0 +1,3657 @@
+/*
+ * Copyright (C) 2009 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.accessibilityservice;
+
+import static android.accessibilityservice.MagnificationConfig.MAGNIFICATION_MODE_FULLSCREEN;
+import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
+
+import android.accessibilityservice.GestureDescription.MotionEventGenerator;
+import android.annotation.CallbackExecutor;
+import android.annotation.CheckResult;
+import android.annotation.ColorInt;
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.TestApi;
+import android.app.Service;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.Intent;
+import android.content.pm.ParceledListSlice;
+import android.graphics.Bitmap;
+import android.graphics.ColorSpace;
+import android.graphics.ParcelableColorSpace;
+import android.graphics.Region;
+import android.hardware.HardwareBuffer;
+import android.hardware.display.DisplayManager;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.view.Display;
+import android.view.InputDevice;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.SurfaceControl;
+import android.view.WindowManager;
+import android.view.WindowManagerImpl;
+import android.view.accessibility.AccessibilityCache;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityInteractionClient;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
+import android.view.accessibility.AccessibilityWindowInfo;
+import android.view.inputmethod.EditorInfo;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.inputmethod.CancellationGroup;
+import com.android.internal.inputmethod.IAccessibilityInputMethodSession;
+import com.android.internal.inputmethod.IAccessibilityInputMethodSessionCallback;
+import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection;
+import com.android.internal.inputmethod.RemoteAccessibilityInputConnection;
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+import java.util.function.IntConsumer;
+
+/**
+ * Accessibility services should only be used to assist users with disabilities in using
+ * Android devices and apps. They run in the background and receive callbacks by the system
+ * when {@link AccessibilityEvent}s are fired. Such events denote some state transition
+ * in the user interface, for example, the focus has changed, a button has been clicked,
+ * etc. Such a service can optionally request the capability for querying the content
+ * of the active window. Development of an accessibility service requires extending this
+ * class and implementing its abstract methods.
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For more information about creating AccessibilityServices, read the
+ * <a href="{@docRoot}guide/topics/ui/accessibility/index.html">Accessibility</a>
+ * developer guide.</p>
+ * </div>
+ *
+ * <h3>Lifecycle</h3>
+ * <p>
+ * The lifecycle of an accessibility service is managed exclusively by the system and
+ * follows the established service life cycle. Starting an accessibility service is triggered
+ * exclusively by the user explicitly turning the service on in device settings. After the system
+ * binds to a service, it calls {@link AccessibilityService#onServiceConnected()}. This method can
+ * be overridden by clients that want to perform post binding setup.
+ * </p>
+ * <p>
+ * An accessibility service stops either when the user turns it off in device settings or when
+ * it calls {@link AccessibilityService#disableSelf()}.
+ * </p>
+ * <h3>Declaration</h3>
+ * <p>
+ * An accessibility is declared as any other service in an AndroidManifest.xml, but it
+ * must do two things:
+ * <ul>
+ * <li>
+ * Specify that it handles the "android.accessibilityservice.AccessibilityService"
+ * {@link android.content.Intent}.
+ * </li>
+ * <li>
+ * Request the {@link android.Manifest.permission#BIND_ACCESSIBILITY_SERVICE} permission to
+ * ensure that only the system can bind to it.
+ * </li>
+ * </ul>
+ * If either of these items is missing, the system will ignore the accessibility service.
+ * Following is an example declaration:
+ * </p>
+ * <pre> <service android:name=".MyAccessibilityService"
+ * android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
+ * <intent-filter>
+ * <action android:name="android.accessibilityservice.AccessibilityService" />
+ * </intent-filter>
+ * . . .
+ * </service></pre>
+ * <h3>Configuration</h3>
+ * <p>
+ * An accessibility service can be configured to receive specific types of accessibility events,
+ * listen only to specific packages, get events from each type only once in a given time frame,
+ * retrieve window content, specify a settings activity, etc.
+ * </p>
+ * <p>
+ * There are two approaches for configuring an accessibility service:
+ * </p>
+ * <ul>
+ * <li>
+ * Providing a {@link #SERVICE_META_DATA meta-data} entry in the manifest when declaring
+ * the service. A service declaration with a meta-data tag is presented below:
+ * <pre> <service android:name=".MyAccessibilityService">
+ * <intent-filter>
+ * <action android:name="android.accessibilityservice.AccessibilityService" />
+ * </intent-filter>
+ * <meta-data android:name="android.accessibilityservice" android:resource="@xml/accessibilityservice" />
+ * </service></pre>
+ * <p class="note">
+ * <strong>Note:</strong> This approach enables setting all properties.
+ * </p>
+ * <p>
+ * For more details refer to {@link #SERVICE_META_DATA} and
+ * <code><{@link android.R.styleable#AccessibilityService accessibility-service}></code>.
+ * </p>
+ * </li>
+ * <li>
+ * Calling {@link AccessibilityService#setServiceInfo(AccessibilityServiceInfo)}. Note
+ * that this method can be called any time to dynamically change the service configuration.
+ * <p class="note">
+ * <strong>Note:</strong> This approach enables setting only dynamically configurable properties:
+ * {@link AccessibilityServiceInfo#eventTypes},
+ * {@link AccessibilityServiceInfo#feedbackType},
+ * {@link AccessibilityServiceInfo#flags},
+ * {@link AccessibilityServiceInfo#notificationTimeout},
+ * {@link AccessibilityServiceInfo#packageNames}
+ * </p>
+ * <p>
+ * For more details refer to {@link AccessibilityServiceInfo}.
+ * </p>
+ * </li>
+ * </ul>
+ * <h3>Retrieving window content</h3>
+ * <p>
+ * A service can specify in its declaration that it can retrieve window
+ * content which is represented as a tree of {@link AccessibilityWindowInfo} and
+ * {@link AccessibilityNodeInfo} objects. Note that
+ * declaring this capability requires that the service declares its configuration via
+ * an XML resource referenced by {@link #SERVICE_META_DATA}.
+ * </p>
+ * <p>
+ * Window content may be retrieved with
+ * {@link AccessibilityEvent#getSource() AccessibilityEvent.getSource()},
+ * {@link AccessibilityService#findFocus(int)},
+ * {@link AccessibilityService#getWindows()}, or
+ * {@link AccessibilityService#getRootInActiveWindow()}.
+ * </p>
+ * <p class="note">
+ * <strong>Note</strong> An accessibility service may have requested to be notified for
+ * a subset of the event types, and thus be unaware when the node hierarchy has changed. It is also
+ * possible for a node to contain outdated information because the window content may change at any
+ * time.
+ * </p>
+ * <h3>Drawing Accessibility Overlays</h3>
+ * <p>Accessibility services can draw overlays on top of existing screen contents.
+ * Accessibility overlays can be used to visually highlight items on the screen
+ * e.g. indicate the current item with accessibility focus.
+ * Overlays can also offer the user a way to interact with the service directly and quickly
+ * customize the service's behavior.</p>
+ * <p>Accessibility overlays can be attached to a particular window or to the display itself.
+ * Attaching an overlay to a window allows the overly to move, grow and shrink as the window does.
+ * The overlay will maintain the same relative position within the window bounds as the window
+ * moves. The overlay will also maintain the same relative position within the window bounds if
+ * the window is resized.
+ * To attach an overlay to a window, use {@link #attachAccessibilityOverlayToWindow}.
+ * Attaching an overlay to the display means that the overlay is independent of the active
+ * windows on that display.
+ * To attach an overlay to a display, use {@link #attachAccessibilityOverlayToDisplay}. </p>
+ * <p> When positioning an overlay that is attached to a window, the service must use window
+ * coordinates. In order to position an overlay on top of an existing UI element it is necessary
+ * to know the bounds of that element in window coordinates. To find the bounds in window
+ * coordinates of an element, find the corresponding {@link AccessibilityNodeInfo} as discussed
+ * above and call {@link AccessibilityNodeInfo#getBoundsInWindow}. </p>
+ * <h3>Notification strategy</h3>
+ * <p>
+ * All accessibility services are notified of all events they have requested, regardless of their
+ * feedback type.
+ * </p>
+ * <p class="note">
+ * <strong>Note:</strong> The event notification timeout is useful to avoid propagating
+ * events to the client too frequently since this is accomplished via an expensive
+ * interprocess call. One can think of the timeout as a criteria to determine when
+ * event generation has settled down.</p>
+ * <h3>Event types</h3>
+ * <ul>
+ * <li>{@link AccessibilityEvent#TYPE_VIEW_CLICKED}</li>
+ * <li>{@link AccessibilityEvent#TYPE_VIEW_LONG_CLICKED}</li>
+ * <li>{@link AccessibilityEvent#TYPE_VIEW_FOCUSED}</li>
+ * <li>{@link AccessibilityEvent#TYPE_VIEW_SELECTED}</li>
+ * <li>{@link AccessibilityEvent#TYPE_VIEW_TEXT_CHANGED}</li>
+ * <li>{@link AccessibilityEvent#TYPE_WINDOW_STATE_CHANGED}</li>
+ * <li>{@link AccessibilityEvent#TYPE_NOTIFICATION_STATE_CHANGED}</li>
+ * <li>{@link AccessibilityEvent#TYPE_TOUCH_EXPLORATION_GESTURE_START}</li>
+ * <li>{@link AccessibilityEvent#TYPE_TOUCH_EXPLORATION_GESTURE_END}</li>
+ * <li>{@link AccessibilityEvent#TYPE_VIEW_HOVER_ENTER}</li>
+ * <li>{@link AccessibilityEvent#TYPE_VIEW_HOVER_EXIT}</li>
+ * <li>{@link AccessibilityEvent#TYPE_VIEW_SCROLLED}</li>
+ * <li>{@link AccessibilityEvent#TYPE_VIEW_TEXT_SELECTION_CHANGED}</li>
+ * <li>{@link AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED}</li>
+ * <li>{@link AccessibilityEvent#TYPE_ANNOUNCEMENT}</li>
+ * <li>{@link AccessibilityEvent#TYPE_GESTURE_DETECTION_START}</li>
+ * <li>{@link AccessibilityEvent#TYPE_GESTURE_DETECTION_END}</li>
+ * <li>{@link AccessibilityEvent#TYPE_TOUCH_INTERACTION_START}</li>
+ * <li>{@link AccessibilityEvent#TYPE_TOUCH_INTERACTION_END}</li>
+ * <li>{@link AccessibilityEvent#TYPE_VIEW_ACCESSIBILITY_FOCUSED}</li>
+ * <li>{@link AccessibilityEvent#TYPE_WINDOWS_CHANGED}</li>
+ * <li>{@link AccessibilityEvent#TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED}</li>
+ * </ul>
+ * <h3>Feedback types</h3>
+ * <ul>
+ * <li>{@link AccessibilityServiceInfo#FEEDBACK_AUDIBLE}</li>
+ * <li>{@link AccessibilityServiceInfo#FEEDBACK_HAPTIC}</li>
+ * <li>{@link AccessibilityServiceInfo#FEEDBACK_SPOKEN}</li>
+ * <li>{@link AccessibilityServiceInfo#FEEDBACK_VISUAL}</li>
+ * <li>{@link AccessibilityServiceInfo#FEEDBACK_GENERIC}</li>
+ * <li>{@link AccessibilityServiceInfo#FEEDBACK_BRAILLE}</li>
+ * </ul>
+ * @see AccessibilityEvent
+ * @see AccessibilityServiceInfo
+ * @see android.view.accessibility.AccessibilityManager
+ */
+public abstract class AccessibilityService extends Service {
+
+ /**
+ * The user has performed a touch-exploration gesture on the touch screen without ever
+ * triggering gesture detection. This gesture is only dispatched when {@link
+ * AccessibilityServiceInfo#FLAG_SEND_MOTION_EVENTS} is set.
+ *
+ * @hide
+ */
+ public static final int GESTURE_TOUCH_EXPLORATION = -2;
+
+ /**
+ * The user has performed a passthrough gesture on the touch screen without ever triggering
+ * gesture detection. This gesture is only dispatched when {@link
+ * AccessibilityServiceInfo#FLAG_SEND_MOTION_EVENTS} is set.
+ * @hide
+ */
+ public static final int GESTURE_PASSTHROUGH = -1;
+
+ /**
+ * The user has performed an unrecognized gesture on the touch screen. This gesture is only
+ * dispatched when {@link AccessibilityServiceInfo#FLAG_SEND_MOTION_EVENTS} is set.
+ */
+ public static final int GESTURE_UNKNOWN = 0;
+
+ /**
+ * The user has performed a swipe up gesture on the touch screen.
+ */
+ public static final int GESTURE_SWIPE_UP = 1;
+
+ /**
+ * The user has performed a swipe down gesture on the touch screen.
+ */
+ public static final int GESTURE_SWIPE_DOWN = 2;
+
+ /**
+ * The user has performed a swipe left gesture on the touch screen.
+ */
+ public static final int GESTURE_SWIPE_LEFT = 3;
+
+ /**
+ * The user has performed a swipe right gesture on the touch screen.
+ */
+ public static final int GESTURE_SWIPE_RIGHT = 4;
+
+ /**
+ * The user has performed a swipe left and right gesture on the touch screen.
+ */
+ public static final int GESTURE_SWIPE_LEFT_AND_RIGHT = 5;
+
+ /**
+ * The user has performed a swipe right and left gesture on the touch screen.
+ */
+ public static final int GESTURE_SWIPE_RIGHT_AND_LEFT = 6;
+
+ /**
+ * The user has performed a swipe up and down gesture on the touch screen.
+ */
+ public static final int GESTURE_SWIPE_UP_AND_DOWN = 7;
+
+ /**
+ * The user has performed a swipe down and up gesture on the touch screen.
+ */
+ public static final int GESTURE_SWIPE_DOWN_AND_UP = 8;
+
+ /**
+ * The user has performed a left and up gesture on the touch screen.
+ */
+ public static final int GESTURE_SWIPE_LEFT_AND_UP = 9;
+
+ /**
+ * The user has performed a left and down gesture on the touch screen.
+ */
+ public static final int GESTURE_SWIPE_LEFT_AND_DOWN = 10;
+
+ /**
+ * The user has performed a right and up gesture on the touch screen.
+ */
+ public static final int GESTURE_SWIPE_RIGHT_AND_UP = 11;
+
+ /**
+ * The user has performed a right and down gesture on the touch screen.
+ */
+ public static final int GESTURE_SWIPE_RIGHT_AND_DOWN = 12;
+
+ /**
+ * The user has performed an up and left gesture on the touch screen.
+ */
+ public static final int GESTURE_SWIPE_UP_AND_LEFT = 13;
+
+ /**
+ * The user has performed an up and right gesture on the touch screen.
+ */
+ public static final int GESTURE_SWIPE_UP_AND_RIGHT = 14;
+
+ /**
+ * The user has performed a down and left gesture on the touch screen.
+ */
+ public static final int GESTURE_SWIPE_DOWN_AND_LEFT = 15;
+
+ /**
+ * The user has performed a down and right gesture on the touch screen.
+ */
+ public static final int GESTURE_SWIPE_DOWN_AND_RIGHT = 16;
+
+ /**
+ * The user has performed a double tap gesture on the touch screen.
+ */
+ public static final int GESTURE_DOUBLE_TAP = 17;
+
+ /**
+ * The user has performed a double tap and hold gesture on the touch screen.
+ */
+ public static final int GESTURE_DOUBLE_TAP_AND_HOLD = 18;
+
+ /**
+ * The user has performed a two-finger single tap gesture on the touch screen.
+ */
+ public static final int GESTURE_2_FINGER_SINGLE_TAP = 19;
+
+ /**
+ * The user has performed a two-finger double tap gesture on the touch screen.
+ */
+ public static final int GESTURE_2_FINGER_DOUBLE_TAP = 20;
+
+ /**
+ * The user has performed a two-finger triple tap gesture on the touch screen.
+ */
+ public static final int GESTURE_2_FINGER_TRIPLE_TAP = 21;
+
+ /**
+ * The user has performed a three-finger single tap gesture on the touch screen.
+ */
+ public static final int GESTURE_3_FINGER_SINGLE_TAP = 22;
+
+ /**
+ * The user has performed a three-finger double tap gesture on the touch screen.
+ */
+ public static final int GESTURE_3_FINGER_DOUBLE_TAP = 23;
+
+ /**
+ * The user has performed a three-finger triple tap gesture on the touch screen.
+ */
+ public static final int GESTURE_3_FINGER_TRIPLE_TAP = 24;
+
+ /**
+ * The user has performed a two-finger swipe up gesture on the touch screen.
+ */
+ public static final int GESTURE_2_FINGER_SWIPE_UP = 25;
+
+ /**
+ * The user has performed a two-finger swipe down gesture on the touch screen.
+ */
+ public static final int GESTURE_2_FINGER_SWIPE_DOWN = 26;
+
+ /**
+ * The user has performed a two-finger swipe left gesture on the touch screen.
+ */
+ public static final int GESTURE_2_FINGER_SWIPE_LEFT = 27;
+
+ /**
+ * The user has performed a two-finger swipe right gesture on the touch screen.
+ */
+ public static final int GESTURE_2_FINGER_SWIPE_RIGHT = 28;
+
+ /**
+ * The user has performed a three-finger swipe up gesture on the touch screen.
+ */
+ public static final int GESTURE_3_FINGER_SWIPE_UP = 29;
+
+ /**
+ * The user has performed a three-finger swipe down gesture on the touch screen.
+ */
+ public static final int GESTURE_3_FINGER_SWIPE_DOWN = 30;
+
+ /**
+ * The user has performed a three-finger swipe left gesture on the touch screen.
+ */
+ public static final int GESTURE_3_FINGER_SWIPE_LEFT = 31;
+
+ /**
+ * The user has performed a three-finger swipe right gesture on the touch screen.
+ */
+ public static final int GESTURE_3_FINGER_SWIPE_RIGHT = 32;
+
+ /** The user has performed a four-finger swipe up gesture on the touch screen. */
+ public static final int GESTURE_4_FINGER_SWIPE_UP = 33;
+
+ /** The user has performed a four-finger swipe down gesture on the touch screen. */
+ public static final int GESTURE_4_FINGER_SWIPE_DOWN = 34;
+
+ /** The user has performed a four-finger swipe left gesture on the touch screen. */
+ public static final int GESTURE_4_FINGER_SWIPE_LEFT = 35;
+
+ /** The user has performed a four-finger swipe right gesture on the touch screen. */
+ public static final int GESTURE_4_FINGER_SWIPE_RIGHT = 36;
+
+ /** The user has performed a four-finger single tap gesture on the touch screen. */
+ public static final int GESTURE_4_FINGER_SINGLE_TAP = 37;
+
+ /** The user has performed a four-finger double tap gesture on the touch screen. */
+ public static final int GESTURE_4_FINGER_DOUBLE_TAP = 38;
+
+ /** The user has performed a four-finger triple tap gesture on the touch screen. */
+ public static final int GESTURE_4_FINGER_TRIPLE_TAP = 39;
+
+ /** The user has performed a two-finger double tap and hold gesture on the touch screen. */
+ public static final int GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD = 40;
+
+ /** The user has performed a three-finger double tap and hold gesture on the touch screen. */
+ public static final int GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD = 41;
+
+ /** The user has performed a two-finger triple-tap and hold gesture on the touch screen. */
+ public static final int GESTURE_2_FINGER_TRIPLE_TAP_AND_HOLD = 43;
+
+ /** The user has performed a three-finger single-tap and hold gesture on the touch screen. */
+ public static final int GESTURE_3_FINGER_SINGLE_TAP_AND_HOLD = 44;
+
+ /** The user has performed a three-finger triple-tap and hold gesture on the touch screen. */
+ public static final int GESTURE_3_FINGER_TRIPLE_TAP_AND_HOLD = 45;
+
+ /** The user has performed a two-finger double tap and hold gesture on the touch screen. */
+ public static final int GESTURE_4_FINGER_DOUBLE_TAP_AND_HOLD = 42;
+
+ /**
+ * The {@link Intent} that must be declared as handled by the service.
+ */
+ public static final String SERVICE_INTERFACE =
+ "android.accessibilityservice.AccessibilityService";
+
+ /**
+ * Name under which an AccessibilityService component publishes information
+ * about itself. This meta-data must reference an XML resource containing an
+ * <code><{@link android.R.styleable#AccessibilityService accessibility-service}></code>
+ * tag. This is a sample XML file configuring an accessibility service:
+ * <pre> <accessibility-service
+ * android:accessibilityEventTypes="typeViewClicked|typeViewFocused"
+ * android:packageNames="foo.bar, foo.baz"
+ * android:accessibilityFeedbackType="feedbackSpoken"
+ * android:notificationTimeout="100"
+ * android:accessibilityFlags="flagDefault"
+ * android:settingsActivity="foo.bar.TestBackActivity"
+ * android:canRetrieveWindowContent="true"
+ * android:canRequestTouchExplorationMode="true"
+ * . . .
+ * /></pre>
+ */
+ public static final String SERVICE_META_DATA = "android.accessibilityservice";
+
+ /**
+ * Action to go back.
+ */
+ public static final int GLOBAL_ACTION_BACK = 1;
+
+ /**
+ * Action to go home.
+ */
+ public static final int GLOBAL_ACTION_HOME = 2;
+
+ /**
+ * Action to toggle showing the overview of recent apps. Will fail on platforms that don't
+ * show recent apps.
+ */
+ public static final int GLOBAL_ACTION_RECENTS = 3;
+
+ /**
+ * Action to open the notifications.
+ */
+ public static final int GLOBAL_ACTION_NOTIFICATIONS = 4;
+
+ /**
+ * Action to open the quick settings.
+ */
+ public static final int GLOBAL_ACTION_QUICK_SETTINGS = 5;
+
+ /**
+ * Action to open the power long-press dialog.
+ */
+ public static final int GLOBAL_ACTION_POWER_DIALOG = 6;
+
+ /**
+ * Action to toggle docking the current app's window.
+ * <p>
+ * <strong>Note:</strong> It is effective only if it appears in {@link #getSystemActions()}.
+ */
+ public static final int GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN = 7;
+
+ /**
+ * Action to lock the screen
+ */
+ public static final int GLOBAL_ACTION_LOCK_SCREEN = 8;
+
+ /**
+ * Action to take a screenshot
+ */
+ public static final int GLOBAL_ACTION_TAKE_SCREENSHOT = 9;
+
+ /**
+ * Action to send the KEYCODE_HEADSETHOOK KeyEvent, which is used to answer and hang up calls
+ * and play and stop media. Calling takes priority. If there is an incoming call,
+ * this action can be used to answer that call, and if there is an ongoing call, to hang up on
+ * that call.
+ */
+ public static final int GLOBAL_ACTION_KEYCODE_HEADSETHOOK = 10;
+
+ /**
+ * Action to trigger the Accessibility Button
+ */
+ public static final int GLOBAL_ACTION_ACCESSIBILITY_BUTTON = 11;
+
+ /**
+ * Action to bring up the Accessibility Button's chooser menu
+ */
+ public static final int GLOBAL_ACTION_ACCESSIBILITY_BUTTON_CHOOSER = 12;
+
+ /**
+ * Action to trigger the Accessibility Shortcut. This shortcut has a hardware trigger and can
+ * be activated by holding down the two volume keys.
+ */
+ public static final int GLOBAL_ACTION_ACCESSIBILITY_SHORTCUT = 13;
+
+ /**
+ * Action to show Launcher's all apps.
+ */
+ public static final int GLOBAL_ACTION_ACCESSIBILITY_ALL_APPS = 14;
+
+ /**
+ * Action to dismiss the notification shade
+ */
+ public static final int GLOBAL_ACTION_DISMISS_NOTIFICATION_SHADE = 15;
+
+ /**
+ * Action to trigger dpad up keyevent.
+ */
+ public static final int GLOBAL_ACTION_DPAD_UP = 16;
+
+ /**
+ * Action to trigger dpad down keyevent.
+ */
+ public static final int GLOBAL_ACTION_DPAD_DOWN = 17;
+
+ /**
+ * Action to trigger dpad left keyevent.
+ */
+ public static final int GLOBAL_ACTION_DPAD_LEFT = 18;
+
+ /**
+ * Action to trigger dpad right keyevent.
+ */
+ public static final int GLOBAL_ACTION_DPAD_RIGHT = 19;
+
+ /**
+ * Action to trigger dpad center keyevent.
+ */
+ public static final int GLOBAL_ACTION_DPAD_CENTER = 20;
+
+ private static final String LOG_TAG = "AccessibilityService";
+
+ /**
+ * Interface used by IAccessibilityServiceClientWrapper to call the service from its main
+ * thread.
+ * @hide
+ */
+ public interface Callbacks {
+ void onAccessibilityEvent(AccessibilityEvent event);
+ void onInterrupt();
+ void onServiceConnected();
+ void init(int connectionId, IBinder windowToken);
+ /** The detected gesture information for different displays */
+ boolean onGesture(AccessibilityGestureEvent gestureInfo);
+ boolean onKeyEvent(KeyEvent event);
+ /** Magnification changed callbacks for different displays */
+ void onMagnificationChanged(int displayId, @NonNull Region region,
+ MagnificationConfig config);
+ /** Callbacks for receiving motion events. */
+ void onMotionEvent(MotionEvent event);
+ /** Callback for tuch state changes. */
+ void onTouchStateChanged(int displayId, int state);
+ void onSoftKeyboardShowModeChanged(int showMode);
+ void onPerformGestureResult(int sequence, boolean completedSuccessfully);
+ void onFingerprintCapturingGesturesChanged(boolean active);
+ void onFingerprintGesture(int gesture);
+ /** Accessbility button clicked callbacks for different displays */
+ void onAccessibilityButtonClicked(int displayId);
+ void onAccessibilityButtonAvailabilityChanged(boolean available);
+ /** This is called when the system action list is changed. */
+ void onSystemActionsChanged();
+ /** This is called when an app requests ime sessions or when the service is enabled. */
+ void createImeSession(IAccessibilityInputMethodSessionCallback callback);
+ /** This is called when an app starts input or when the service is enabled. */
+ void startInput(@Nullable RemoteAccessibilityInputConnection inputConnection,
+ @NonNull EditorInfo editorInfo, boolean restarting);
+ }
+
+ /**
+ * Annotations for Soft Keyboard show modes so tools can catch invalid show modes.
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "SHOW_MODE_" }, value = {
+ SHOW_MODE_AUTO,
+ SHOW_MODE_HIDDEN,
+ SHOW_MODE_IGNORE_HARD_KEYBOARD
+ })
+ public @interface SoftKeyboardShowMode {}
+
+ /**
+ * Allow the system to control when the soft keyboard is shown.
+ * @see SoftKeyboardController
+ */
+ public static final int SHOW_MODE_AUTO = 0;
+
+ /**
+ * Never show the soft keyboard.
+ * @see SoftKeyboardController
+ */
+ public static final int SHOW_MODE_HIDDEN = 1;
+
+ /**
+ * Allow the soft keyboard to be shown, even if a hard keyboard is connected
+ * @see SoftKeyboardController
+ */
+ public static final int SHOW_MODE_IGNORE_HARD_KEYBOARD = 2;
+
+ /**
+ * Mask used to cover the show modes supported in public API
+ * @hide
+ */
+ public static final int SHOW_MODE_MASK = 0x03;
+
+ /**
+ * Bit used to hold the old value of the hard IME setting to restore when a service is shut
+ * down.
+ * @hide
+ */
+ public static final int SHOW_MODE_HARD_KEYBOARD_ORIGINAL_VALUE = 0x20000000;
+
+ /**
+ * Bit for show mode setting to indicate that the user has overridden the hard keyboard
+ * behavior.
+ * @hide
+ */
+ public static final int SHOW_MODE_HARD_KEYBOARD_OVERRIDDEN = 0x40000000;
+
+ /**
+ * Annotations for error codes of taking screenshot.
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "TAKE_SCREENSHOT_" }, value = {
+ ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR,
+ ERROR_TAKE_SCREENSHOT_NO_ACCESSIBILITY_ACCESS,
+ ERROR_TAKE_SCREENSHOT_INTERVAL_TIME_SHORT,
+ ERROR_TAKE_SCREENSHOT_INVALID_DISPLAY,
+ ERROR_TAKE_SCREENSHOT_INVALID_WINDOW
+ })
+ public @interface ScreenshotErrorCode {}
+
+ /**
+ * The status of taking screenshot is success.
+ * @hide
+ */
+ public static final int TAKE_SCREENSHOT_SUCCESS = 0;
+
+ /**
+ * The status of taking screenshot is failure and the reason is internal error.
+ */
+ public static final int ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR = 1;
+
+ /**
+ * The status of taking screenshot is failure and the reason is no accessibility access.
+ */
+ public static final int ERROR_TAKE_SCREENSHOT_NO_ACCESSIBILITY_ACCESS = 2;
+
+ /**
+ * The status of taking screenshot is failure and the reason is that too little time has
+ * elapsed since the last screenshot.
+ */
+ public static final int ERROR_TAKE_SCREENSHOT_INTERVAL_TIME_SHORT = 3;
+
+ /**
+ * The status of taking screenshot is failure and the reason is invalid display Id.
+ */
+ public static final int ERROR_TAKE_SCREENSHOT_INVALID_DISPLAY = 4;
+
+ /**
+ * The status of taking screenshot is failure and the reason is invalid accessibility window Id.
+ */
+ public static final int ERROR_TAKE_SCREENSHOT_INVALID_WINDOW = 5;
+
+ /**
+ * The status of taking screenshot is failure and the reason is the window contains secure
+ * content.
+ * @see WindowManager.LayoutParams#FLAG_SECURE
+ */
+ public static final int ERROR_TAKE_SCREENSHOT_SECURE_WINDOW = 6;
+
+ /**
+ * The interval time of calling
+ * {@link AccessibilityService#takeScreenshot(int, Executor, Consumer)} API.
+ * @hide
+ */
+ @TestApi
+ public static final int ACCESSIBILITY_TAKE_SCREENSHOT_REQUEST_INTERVAL_TIMES_MS = 333;
+
+ /** @hide */
+ public static final String KEY_ACCESSIBILITY_SCREENSHOT_STATUS =
+ "screenshot_status";
+
+ /** @hide */
+ public static final String KEY_ACCESSIBILITY_SCREENSHOT_HARDWAREBUFFER =
+ "screenshot_hardwareBuffer";
+
+ /** @hide */
+ public static final String KEY_ACCESSIBILITY_SCREENSHOT_COLORSPACE =
+ "screenshot_colorSpace";
+
+ /** @hide */
+ public static final String KEY_ACCESSIBILITY_SCREENSHOT_TIMESTAMP =
+ "screenshot_timestamp";
+
+
+ /**
+ * Annotations for result codes of attaching accessibility overlays.
+ *
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks")
+ @IntDef(
+ prefix = {"OVERLAY_RESULT_"},
+ value = {
+ OVERLAY_RESULT_SUCCESS,
+ OVERLAY_RESULT_INTERNAL_ERROR,
+ OVERLAY_RESULT_INVALID,
+ })
+ public @interface AttachOverlayResult {}
+
+ /** Result code indicating the overlay was successfully attached. */
+ @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks")
+ public static final int OVERLAY_RESULT_SUCCESS = 0;
+
+ /**
+ * Result code indicating the overlay could not be attached due to an internal
+ * error and not
+ * because of problems with the input.
+ */
+ @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks")
+ public static final int OVERLAY_RESULT_INTERNAL_ERROR = 1;
+
+ /**
+ * Result code indicating the overlay could not be attached because the
+ * specified display or
+ * window id was invalid.
+ */
+ @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks")
+ public static final int OVERLAY_RESULT_INVALID = 2;
+
+ private int mConnectionId = AccessibilityInteractionClient.NO_ID;
+
+ @UnsupportedAppUsage
+ private AccessibilityServiceInfo mInfo;
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ private IBinder mWindowToken;
+
+ private WindowManager mWindowManager;
+
+ /** List of magnification controllers, mapping from displayId -> MagnificationController. */
+ private final SparseArray<MagnificationController> mMagnificationControllers =
+ new SparseArray<>(0);
+ /**
+ * List of touch interaction controllers, mapping from displayId -> TouchInteractionController.
+ */
+ private final SparseArray<TouchInteractionController> mTouchInteractionControllers =
+ new SparseArray<>(0);
+
+ private SoftKeyboardController mSoftKeyboardController;
+ private InputMethod mInputMethod;
+ private boolean mInputMethodInitialized = false;
+ private final SparseArray<AccessibilityButtonController> mAccessibilityButtonControllers =
+ new SparseArray<>(0);
+ private BrailleDisplayController mBrailleDisplayController;
+
+ private int mGestureStatusCallbackSequence;
+
+ private SparseArray<GestureResultCallbackInfo> mGestureStatusCallbackInfos;
+
+ private final Object mLock = new Object();
+
+ private FingerprintGestureController mFingerprintGestureController;
+
+ private int mMotionEventSources;
+
+ /**
+ * Callback for {@link android.view.accessibility.AccessibilityEvent}s.
+ *
+ * @param event The new event. This event is owned by the caller and cannot be used after
+ * this method returns. Services wishing to use the event after this method returns should
+ * make a copy.
+ */
+ public abstract void onAccessibilityEvent(AccessibilityEvent event);
+
+ /**
+ * Callback for interrupting the accessibility feedback.
+ */
+ public abstract void onInterrupt();
+
+ /**
+ * Dispatches service connection to internal components first, then the
+ * client code.
+ */
+ private void dispatchServiceConnected() {
+ synchronized (mLock) {
+ for (int i = 0; i < mMagnificationControllers.size(); i++) {
+ mMagnificationControllers.valueAt(i).onServiceConnectedLocked();
+ }
+ final AccessibilityServiceInfo info = getServiceInfo();
+ if (info != null) {
+ updateInputMethod(info);
+ mMotionEventSources = info.getMotionEventSources();
+ }
+ }
+ if (mSoftKeyboardController != null) {
+ mSoftKeyboardController.onServiceConnected();
+ }
+
+ // The client gets to handle service connection last, after we've set
+ // up any state upon which their code may rely.
+ onServiceConnected();
+ }
+
+ private void updateInputMethod(AccessibilityServiceInfo info) {
+ if (info != null) {
+ boolean requestIme = (info.flags
+ & AccessibilityServiceInfo.FLAG_INPUT_METHOD_EDITOR) != 0;
+ if (requestIme && !mInputMethodInitialized) {
+ mInputMethod = onCreateInputMethod();
+ mInputMethodInitialized = true;
+ } else if (!requestIme & mInputMethodInitialized) {
+ mInputMethod = null;
+ mInputMethodInitialized = false;
+ }
+ }
+ }
+
+ /**
+ * This method is a part of the {@link AccessibilityService} lifecycle and is
+ * called after the system has successfully bound to the service. If is
+ * convenient to use this method for setting the {@link AccessibilityServiceInfo}.
+ *
+ * @see AccessibilityServiceInfo
+ * @see #setServiceInfo(AccessibilityServiceInfo)
+ */
+ protected void onServiceConnected() {
+
+ }
+
+ /**
+ * Called by {@link #onGesture(AccessibilityGestureEvent)} when the user performs a specific
+ * gesture on the default display.
+ *
+ * <strong>Note:</strong> To receive gestures an accessibility service must
+ * request that the device is in touch exploration mode by setting the
+ * {@link AccessibilityServiceInfo#FLAG_REQUEST_TOUCH_EXPLORATION_MODE}
+ * flag.
+ *
+ * @param gestureId The unique id of the performed gesture.
+ *
+ * @return Whether the gesture was handled.
+ * @deprecated Override {@link #onGesture(AccessibilityGestureEvent)} instead.
+ *
+ * @see #GESTURE_SWIPE_UP
+ * @see #GESTURE_SWIPE_UP_AND_LEFT
+ * @see #GESTURE_SWIPE_UP_AND_DOWN
+ * @see #GESTURE_SWIPE_UP_AND_RIGHT
+ * @see #GESTURE_SWIPE_DOWN
+ * @see #GESTURE_SWIPE_DOWN_AND_LEFT
+ * @see #GESTURE_SWIPE_DOWN_AND_UP
+ * @see #GESTURE_SWIPE_DOWN_AND_RIGHT
+ * @see #GESTURE_SWIPE_LEFT
+ * @see #GESTURE_SWIPE_LEFT_AND_UP
+ * @see #GESTURE_SWIPE_LEFT_AND_RIGHT
+ * @see #GESTURE_SWIPE_LEFT_AND_DOWN
+ * @see #GESTURE_SWIPE_RIGHT
+ * @see #GESTURE_SWIPE_RIGHT_AND_UP
+ * @see #GESTURE_SWIPE_RIGHT_AND_LEFT
+ * @see #GESTURE_SWIPE_RIGHT_AND_DOWN
+ */
+ @Deprecated
+ protected boolean onGesture(int gestureId) {
+ return false;
+ }
+
+ /**
+ * Called by the system when the user performs a specific gesture on the
+ * specific touch screen.
+ *<p>
+ * <strong>Note:</strong> To receive gestures an accessibility service must
+ * request that the device is in touch exploration mode by setting the
+ * {@link AccessibilityServiceInfo#FLAG_REQUEST_TOUCH_EXPLORATION_MODE}
+ * flag.
+ *<p>
+ * <strong>Note:</strong> The default implementation calls {@link #onGesture(int)} when the
+ * touch screen is default display.
+ *
+ * @param gestureEvent The information of gesture.
+ *
+ * @return Whether the gesture was handled.
+ *
+ */
+ public boolean onGesture(@NonNull AccessibilityGestureEvent gestureEvent) {
+ if (gestureEvent.getDisplayId() == Display.DEFAULT_DISPLAY) {
+ onGesture(gestureEvent.getGestureId());
+ }
+ return false;
+ }
+
+ /**
+ * Callback that allows an accessibility service to observe the key events
+ * before they are passed to the rest of the system. This means that the events
+ * are first delivered here before they are passed to the device policy, the
+ * input method, or applications.
+ * <p>
+ * <strong>Note:</strong> It is important that key events are handled in such
+ * a way that the event stream that would be passed to the rest of the system
+ * is well-formed. For example, handling the down event but not the up event
+ * and vice versa would generate an inconsistent event stream.
+ * </p>
+ * <p>
+ * <strong>Note:</strong> The key events delivered in this method are copies
+ * and modifying them will have no effect on the events that will be passed
+ * to the system. This method is intended to perform purely filtering
+ * functionality.
+ * <p>
+ *
+ * @param event The event to be processed. This event is owned by the caller and cannot be used
+ * after this method returns. Services wishing to use the event after this method returns should
+ * make a copy.
+ * @return If true then the event will be consumed and not delivered to
+ * applications, otherwise it will be delivered as usual.
+ */
+ protected boolean onKeyEvent(KeyEvent event) {
+ return false;
+ }
+
+ /**
+ * Callback that allows an accessibility service to observe generic {@link MotionEvent}s.
+ * <p>
+ * Prefer {@link TouchInteractionController} to observe and control touchscreen events,
+ * including touch gestures. If this or any enabled service is using
+ * {@link AccessibilityServiceInfo#FLAG_REQUEST_TOUCH_EXPLORATION_MODE} then
+ * {@link #onMotionEvent} will not receive touchscreen events.
+ * </p>
+ * <p>
+ * <strong>Note:</strong> The service must first request to listen to events using
+ * {@link AccessibilityServiceInfo#setMotionEventSources}.
+ * {@link MotionEvent}s from sources in {@link AccessibilityServiceInfo#getMotionEventSources()}
+ * are not sent to the rest of the system. To stop listening to events from a given source, call
+ * {@link AccessibilityServiceInfo#setMotionEventSources} with that source removed.
+ * </p>
+ * @param event The event to be processed.
+ */
+ public void onMotionEvent(@NonNull MotionEvent event) { }
+
+ /**
+ * Gets the windows on the screen of the default display. This method returns only the windows
+ * that a sighted user can interact with, as opposed to all windows.
+ * For example, if there is a modal dialog shown and the user cannot touch
+ * anything behind it, then only the modal window will be reported
+ * (assuming it is the top one). For convenience the returned windows
+ * are ordered in a descending layer order, which is the windows that
+ * are on top are reported first. Since the user can always
+ * interact with the window that has input focus by typing, the focused
+ * window is always returned (even if covered by a modal window).
+ * <p>
+ * <strong>Note:</strong> In order to access the windows your service has
+ * to declare the capability to retrieve window content by setting the
+ * {@link android.R.styleable#AccessibilityService_canRetrieveWindowContent}
+ * property in its meta-data. For details refer to {@link #SERVICE_META_DATA}.
+ * Also the service has to opt-in to retrieve the interactive windows by
+ * setting the {@link AccessibilityServiceInfo#FLAG_RETRIEVE_INTERACTIVE_WINDOWS}
+ * flag.
+ * </p>
+ *
+ * @return The windows if there are windows and the service is can retrieve
+ * them, otherwise an empty list.
+ */
+ public List<AccessibilityWindowInfo> getWindows() {
+ return AccessibilityInteractionClient.getInstance(this).getWindows(mConnectionId);
+ }
+
+ /**
+ * Gets the windows on the screen of all displays. This method returns only the windows
+ * that a sighted user can interact with, as opposed to all windows.
+ * For example, if there is a modal dialog shown and the user cannot touch
+ * anything behind it, then only the modal window will be reported
+ * (assuming it is the top one). For convenience the returned windows
+ * are ordered in a descending layer order, which is the windows that
+ * are on top are reported first. Since the user can always
+ * interact with the window that has input focus by typing, the focused
+ * window is always returned (even if covered by a modal window).
+ * <p>
+ * <strong>Note:</strong> In order to access the windows your service has
+ * to declare the capability to retrieve window content by setting the
+ * {@link android.R.styleable#AccessibilityService_canRetrieveWindowContent}
+ * property in its meta-data. For details refer to {@link #SERVICE_META_DATA}.
+ * Also the service has to opt-in to retrieve the interactive windows by
+ * setting the {@link AccessibilityServiceInfo#FLAG_RETRIEVE_INTERACTIVE_WINDOWS}
+ * flag.
+ * </p>
+ *
+ * @return The windows of all displays if there are windows and the service is can retrieve
+ * them, otherwise an empty list. The key of SparseArray is display ID.
+ */
+ @NonNull
+ public final SparseArray<List<AccessibilityWindowInfo>> getWindowsOnAllDisplays() {
+ return AccessibilityInteractionClient.getInstance(this).getWindowsOnAllDisplays(
+ mConnectionId);
+ }
+
+ /**
+ * Gets the root node in the currently active window if this service
+ * can retrieve window content. The active window is the one that the user
+ * is currently touching or the window with input focus, if the user is not
+ * touching any window. It could be from any logical display.
+ * <p>
+ * <strong>Note:</strong> In order to access the root node your service has
+ * to declare the capability to retrieve window content by setting the
+ * {@link android.R.styleable#AccessibilityService_canRetrieveWindowContent}
+ * property in its meta-data. For details refer to {@link #SERVICE_META_DATA}.
+ * </p>
+ *
+ * @return The root node if this service can retrieve window content.
+ * @see AccessibilityWindowInfo#isActive() for more explanation about the active window.
+ */
+ public AccessibilityNodeInfo getRootInActiveWindow() {
+ return getRootInActiveWindow(AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_HYBRID);
+ }
+
+ /**
+ * Gets the root node in the currently active window if this service
+ * can retrieve window content. The active window is the one that the user
+ * is currently touching or the window with input focus, if the user is not
+ * touching any window. It could be from any logical display.
+ *
+ * @param prefetchingStrategy the prefetching strategy.
+ * @return The root node if this service can retrieve window content.
+ *
+ * @see #getRootInActiveWindow()
+ * @see AccessibilityNodeInfo#getParent(int) for a description of prefetching.
+ */
+ @Nullable
+ public AccessibilityNodeInfo getRootInActiveWindow(
+ @AccessibilityNodeInfo.PrefetchingStrategy int prefetchingStrategy) {
+ return AccessibilityInteractionClient.getInstance(this).getRootInActiveWindow(
+ mConnectionId, prefetchingStrategy);
+ }
+
+ /**
+ * Disables the service. After calling this method, the service will be disabled and settings
+ * will show that it is turned off.
+ */
+ public final void disableSelf() {
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance(this).getConnection(mConnectionId);
+ if (connection != null) {
+ try {
+ connection.disableSelf();
+ } catch (RemoteException re) {
+ throw new RuntimeException(re);
+ }
+ }
+ }
+
+ @NonNull
+ @Override
+ public Context createDisplayContext(Display display) {
+ return new AccessibilityContext(super.createDisplayContext(display), mConnectionId);
+ }
+
+ @NonNull
+ @Override
+ public Context createWindowContext(int type, @Nullable Bundle options) {
+ final Context context = super.createWindowContext(type, options);
+ if (type != TYPE_ACCESSIBILITY_OVERLAY) {
+ return context;
+ }
+ return new AccessibilityContext(context, mConnectionId);
+ }
+
+ @NonNull
+ @Override
+ public Context createWindowContext(@NonNull Display display, int type,
+ @Nullable Bundle options) {
+ final Context context = super.createWindowContext(display, type, options);
+ if (type != TYPE_ACCESSIBILITY_OVERLAY) {
+ return context;
+ }
+ return new AccessibilityContext(context, mConnectionId);
+ }
+
+ /**
+ * Returns the magnification controller, which may be used to query and
+ * modify the state of display magnification.
+ * <p>
+ * <strong>Note:</strong> In order to control magnification, your service
+ * must declare the capability by setting the
+ * {@link android.R.styleable#AccessibilityService_canControlMagnification}
+ * property in its meta-data. For more information, see
+ * {@link #SERVICE_META_DATA}.
+ *
+ * @return the magnification controller
+ */
+ @NonNull
+ public final MagnificationController getMagnificationController() {
+ return getMagnificationController(Display.DEFAULT_DISPLAY);
+ }
+
+ /**
+ * Returns the magnification controller of specified logical display, which may be used to
+ * query and modify the state of display magnification.
+ * <p>
+ * <strong>Note:</strong> In order to control magnification, your service
+ * must declare the capability by setting the
+ * {@link android.R.styleable#AccessibilityService_canControlMagnification}
+ * property in its meta-data. For more information, see
+ * {@link #SERVICE_META_DATA}.
+ *
+ * @param displayId The logic display id, use {@link Display#DEFAULT_DISPLAY} for
+ * default display.
+ * @return the magnification controller
+ *
+ * @hide
+ */
+ @NonNull
+ public final MagnificationController getMagnificationController(int displayId) {
+ synchronized (mLock) {
+ MagnificationController controller = mMagnificationControllers.get(displayId);
+ if (controller == null) {
+ controller = new MagnificationController(this, mLock, displayId);
+ mMagnificationControllers.put(displayId, controller);
+ }
+ return controller;
+ }
+ }
+
+ /**
+ * Get the controller for fingerprint gestures. This feature requires {@link
+ * AccessibilityServiceInfo#CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES}.
+ *
+ *<strong>Note: </strong> The service must be connected before this method is called.
+ *
+ * @return The controller for fingerprint gestures, or {@code null} if gestures are unavailable.
+ */
+ @RequiresPermission(android.Manifest.permission.USE_FINGERPRINT)
+ public final @NonNull FingerprintGestureController getFingerprintGestureController() {
+ if (mFingerprintGestureController == null) {
+ mFingerprintGestureController = new FingerprintGestureController(
+ AccessibilityInteractionClient.getInstance(this).getConnection(mConnectionId));
+ }
+ return mFingerprintGestureController;
+ }
+
+ /**
+ * Dispatch a gesture to the touch screen. Any gestures currently in progress, whether from
+ * the user, this service, or another service, will be cancelled.
+ * <p>
+ * The gesture will be dispatched as if it were performed directly on the screen by a user, so
+ * the events may be affected by features such as magnification and explore by touch.
+ * </p>
+ * <p>
+ * <strong>Note:</strong> In order to dispatch gestures, your service
+ * must declare the capability by setting the
+ * {@link android.R.styleable#AccessibilityService_canPerformGestures}
+ * property in its meta-data. For more information, see
+ * {@link #SERVICE_META_DATA}.
+ * </p>
+ * <p>Since many apps do not appropriately support {@link AccessibilityAction#ACTION_CLICK},
+ * if this action fails on an element that should be clickable, a service that is not a screen
+ * reader may send a tap directly to the element as a fallback. The example below
+ * demonstrates this fallback using the gesture dispatch APIs:
+ *
+ * <pre class="prettyprint"><code>
+ * private void tap(PointF point) {
+ * StrokeDescription tap = new StrokeDescription(path(point), 0,
+ * ViewConfiguration.getTapTimeout());
+ * GestureDescription.Builder builder = new GestureDescription.Builder();
+ * builder.addStroke(tap);
+ * dispatchGesture(builder.build(), null, null);
+ * }
+ *</code>
+ * </pre>
+ * @param gesture The gesture to dispatch
+ * @param callback The object to call back when the status of the gesture is known. If
+ * {@code null}, no status is reported.
+ * @param handler The handler on which to call back the {@code callback} object. If
+ * {@code null}, the object is called back on the service's main thread.
+ *
+ * @return {@code true} if the gesture is dispatched, {@code false} if not.
+ */
+ public final boolean dispatchGesture(@NonNull GestureDescription gesture,
+ @Nullable GestureResultCallback callback,
+ @Nullable Handler handler) {
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance(this).getConnection(mConnectionId);
+ if (connection == null) {
+ return false;
+ }
+ int sampleTimeMs = calculateGestureSampleTimeMs(gesture.getDisplayId());
+ List<GestureDescription.GestureStep> steps =
+ MotionEventGenerator.getGestureStepsFromGestureDescription(gesture, sampleTimeMs);
+ try {
+ synchronized (mLock) {
+ mGestureStatusCallbackSequence++;
+ if (callback != null) {
+ if (mGestureStatusCallbackInfos == null) {
+ mGestureStatusCallbackInfos = new SparseArray<>();
+ }
+ GestureResultCallbackInfo callbackInfo = new GestureResultCallbackInfo(gesture,
+ callback, handler);
+ mGestureStatusCallbackInfos.put(mGestureStatusCallbackSequence, callbackInfo);
+ }
+ connection.dispatchGesture(mGestureStatusCallbackSequence,
+ new ParceledListSlice<>(steps), gesture.getDisplayId());
+ }
+ } catch (RemoteException re) {
+ throw new RuntimeException(re);
+ }
+ return true;
+ }
+
+ /**
+ * Returns the sample time in millis of gesture steps for the current display.
+ *
+ * <p>For gestures to be smooth they should line up with the refresh rate of the display.
+ * On versions of Android before R, the sample time was fixed to 100ms.
+ */
+ private int calculateGestureSampleTimeMs(int displayId) {
+ if (getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.Q) {
+ return 100;
+ }
+ Display display = getSystemService(DisplayManager.class).getDisplay(
+ displayId);
+ if (display == null) {
+ return 100;
+ }
+ int msPerSecond = 1000;
+ int sampleTimeMs = (int) (msPerSecond / display.getRefreshRate());
+ if (sampleTimeMs < 1) {
+ // Should be impossible, but do not return 0.
+ return 100;
+ }
+ return sampleTimeMs;
+ }
+
+ void onPerformGestureResult(int sequence, final boolean completedSuccessfully) {
+ if (mGestureStatusCallbackInfos == null) {
+ return;
+ }
+ GestureResultCallbackInfo callbackInfo;
+ synchronized (mLock) {
+ callbackInfo = mGestureStatusCallbackInfos.get(sequence);
+ mGestureStatusCallbackInfos.remove(sequence);
+ }
+ final GestureResultCallbackInfo finalCallbackInfo = callbackInfo;
+ if ((callbackInfo != null) && (callbackInfo.gestureDescription != null)
+ && (callbackInfo.callback != null)) {
+ if (callbackInfo.handler != null) {
+ callbackInfo.handler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (completedSuccessfully) {
+ finalCallbackInfo.callback
+ .onCompleted(finalCallbackInfo.gestureDescription);
+ } else {
+ finalCallbackInfo.callback
+ .onCancelled(finalCallbackInfo.gestureDescription);
+ }
+ }
+ });
+ return;
+ }
+ if (completedSuccessfully) {
+ callbackInfo.callback.onCompleted(callbackInfo.gestureDescription);
+ } else {
+ callbackInfo.callback.onCancelled(callbackInfo.gestureDescription);
+ }
+ }
+ }
+
+ private void onMagnificationChanged(int displayId, @NonNull Region region,
+ MagnificationConfig config) {
+ MagnificationController controller;
+ synchronized (mLock) {
+ controller = mMagnificationControllers.get(displayId);
+ }
+ if (controller != null) {
+ controller.dispatchMagnificationChanged(region, config);
+ }
+ }
+
+ /**
+ * Callback for fingerprint gesture handling
+ * @param active If gesture detection is active
+ */
+ private void onFingerprintCapturingGesturesChanged(boolean active) {
+ getFingerprintGestureController().onGestureDetectionActiveChanged(active);
+ }
+
+ /**
+ * Callback for fingerprint gesture handling
+ * @param gesture The identifier for the gesture performed
+ */
+ private void onFingerprintGesture(int gesture) {
+ getFingerprintGestureController().onGesture(gesture);
+ }
+
+ /** @hide */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public int getConnectionId() {
+ return mConnectionId;
+ }
+
+ /**
+ * Used to control and query the state of display magnification.
+ */
+ public static final class MagnificationController {
+ private final AccessibilityService mService;
+ private final int mDisplayId;
+
+ /**
+ * Map of listeners to their handlers. Lazily created when adding the
+ * first magnification listener.
+ */
+ private ArrayMap<OnMagnificationChangedListener, Handler> mListeners;
+ private final Object mLock;
+
+ MagnificationController(@NonNull AccessibilityService service, @NonNull Object lock,
+ int displayId) {
+ mService = service;
+ mLock = lock;
+ mDisplayId = displayId;
+ }
+
+ /**
+ * Called when the service is connected.
+ */
+ void onServiceConnectedLocked() {
+ if (mListeners != null && !mListeners.isEmpty()) {
+ setMagnificationCallbackEnabled(true);
+ }
+ }
+
+ /**
+ * Adds the specified change listener to the list of magnification
+ * change listeners. The callback will occur on the service's main
+ * thread.
+ *
+ * @param listener the listener to add, must be non-{@code null}
+ */
+ public void addListener(@NonNull OnMagnificationChangedListener listener) {
+ addListener(listener, null);
+ }
+
+ /**
+ * Adds the specified change listener to the list of magnification
+ * change listeners. The callback will occur on the specified
+ * {@link Handler}'s thread, or on the service's main thread if the
+ * handler is {@code null}.
+ *
+ * @param listener the listener to add, must be non-null
+ * @param handler the handler on which the callback should execute, or
+ * {@code null} to execute on the service's main thread
+ */
+ public void addListener(@NonNull OnMagnificationChangedListener listener,
+ @Nullable Handler handler) {
+ synchronized (mLock) {
+ if (mListeners == null) {
+ mListeners = new ArrayMap<>();
+ }
+
+ final boolean shouldEnableCallback = mListeners.isEmpty();
+ mListeners.put(listener, handler);
+
+ if (shouldEnableCallback) {
+ // This may fail if the service is not connected yet, but if we
+ // still have listeners when it connects then we can try again.
+ setMagnificationCallbackEnabled(true);
+ }
+ }
+ }
+
+ /**
+ * Removes the specified change listener from the list of magnification change listeners.
+ *
+ * @param listener the listener to remove, must be non-null
+ * @return {@code true} if the listener was removed, {@code false} otherwise
+ */
+ public boolean removeListener(@NonNull OnMagnificationChangedListener listener) {
+ if (mListeners == null) {
+ return false;
+ }
+
+ synchronized (mLock) {
+ final int keyIndex = mListeners.indexOfKey(listener);
+ final boolean hasKey = keyIndex >= 0;
+ if (hasKey) {
+ mListeners.removeAt(keyIndex);
+ }
+
+ if (hasKey && mListeners.isEmpty()) {
+ // We just removed the last listener, so we don't need
+ // callbacks from the service anymore.
+ setMagnificationCallbackEnabled(false);
+ }
+
+ return hasKey;
+ }
+ }
+
+ private void setMagnificationCallbackEnabled(boolean enabled) {
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance(mService).getConnection(
+ mService.mConnectionId);
+ if (connection != null) {
+ try {
+ connection.setMagnificationCallbackEnabled(mDisplayId, enabled);
+ } catch (RemoteException re) {
+ throw new RuntimeException(re);
+ }
+ }
+ }
+
+ /**
+ * Dispatches magnification changes to any registered listeners. This
+ * should be called on the service's main thread.
+ */
+ void dispatchMagnificationChanged(final @NonNull Region region,
+ final MagnificationConfig config) {
+ final ArrayMap<OnMagnificationChangedListener, Handler> entries;
+ synchronized (mLock) {
+ if (mListeners == null || mListeners.isEmpty()) {
+ Slog.d(LOG_TAG, "Received magnification changed "
+ + "callback with no listeners registered!");
+ setMagnificationCallbackEnabled(false);
+ return;
+ }
+
+ // Listeners may remove themselves. Perform a shallow copy to avoid concurrent
+ // modification.
+ entries = new ArrayMap<>(mListeners);
+ }
+
+ for (int i = 0, count = entries.size(); i < count; i++) {
+ final OnMagnificationChangedListener listener = entries.keyAt(i);
+ final Handler handler = entries.valueAt(i);
+ if (handler != null) {
+ handler.post(() -> {
+ listener.onMagnificationChanged(MagnificationController.this,
+ region, config);
+ });
+ } else {
+ // We're already on the main thread, just run the listener.
+ listener.onMagnificationChanged(this, region, config);
+ }
+ }
+ }
+
+ /**
+ * Gets the {@link MagnificationConfig} of the controlling magnifier on the display.
+ * <p>
+ * <strong>Note:</strong> If the service is not yet connected (e.g.
+ * {@link AccessibilityService#onServiceConnected()} has not yet been
+ * called) or the service has been disconnected, this method will
+ * return null.
+ * </p>
+ *
+ * @return the magnification config that the service controls
+ */
+ public @Nullable MagnificationConfig getMagnificationConfig() {
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance(mService).getConnection(
+ mService.mConnectionId);
+ if (connection != null) {
+ try {
+ return connection.getMagnificationConfig(mDisplayId);
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Failed to obtain magnification config", re);
+ re.rethrowFromSystemServer();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the current magnification scale.
+ * <p>
+ * <strong>Note:</strong> If the service is not yet connected (e.g.
+ * {@link AccessibilityService#onServiceConnected()} has not yet been
+ * called) or the service has been disconnected, this method will
+ * return a default value of {@code 1.0f}.
+ * </p>
+ * <p>
+ * <strong>Note:</strong> This legacy API gets the scale of full-screen
+ * magnification. To get the scale of the current controlling magnifier,
+ * use {@link #getMagnificationConfig} instead.
+ * </p>
+ *
+ * @return the current magnification scale
+ * @deprecated Use {@link #getMagnificationConfig()} instead
+ */
+ @Deprecated
+ public float getScale() {
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance(mService).getConnection(
+ mService.mConnectionId);
+ if (connection != null) {
+ try {
+ return connection.getMagnificationScale(mDisplayId);
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Failed to obtain scale", re);
+ re.rethrowFromSystemServer();
+ }
+ }
+ return 1.0f;
+ }
+
+ /**
+ * Returns the unscaled screen-relative X coordinate of the focal
+ * center of the magnified region. This is the point around which
+ * zooming occurs and is guaranteed to lie within the magnified
+ * region.
+ * <p>
+ * <strong>Note:</strong> If the service is not yet connected (e.g.
+ * {@link AccessibilityService#onServiceConnected()} has not yet been
+ * called) or the service has been disconnected, this method will
+ * return a default value of {@code 0.0f}.
+ * </p>
+ * <p>
+ * <strong>Note:</strong> This legacy API gets the center position of full-screen
+ * magnification. To get the magnification center of the current controlling magnifier,
+ * use {@link #getMagnificationConfig} instead.
+ * </p>
+ *
+ * @return the unscaled screen-relative X coordinate of the center of
+ * the magnified region
+ * @deprecated Use {@link #getMagnificationConfig()} instead
+ */
+ @Deprecated
+ public float getCenterX() {
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance(mService).getConnection(
+ mService.mConnectionId);
+ if (connection != null) {
+ try {
+ return connection.getMagnificationCenterX(mDisplayId);
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Failed to obtain center X", re);
+ re.rethrowFromSystemServer();
+ }
+ }
+ return 0.0f;
+ }
+
+ /**
+ * Returns the unscaled screen-relative Y coordinate of the focal
+ * center of the magnified region. This is the point around which
+ * zooming occurs and is guaranteed to lie within the magnified
+ * region.
+ * <p>
+ * <strong>Note:</strong> If the service is not yet connected (e.g.
+ * {@link AccessibilityService#onServiceConnected()} has not yet been
+ * called) or the service has been disconnected, this method will
+ * return a default value of {@code 0.0f}.
+ * </p>
+ * <p>
+ * <strong>Note:</strong> This legacy API gets the center position of full-screen
+ * magnification. To get the magnification center of the current controlling magnifier,
+ * use {@link #getMagnificationConfig} instead.
+ * </p>
+ *
+ * @return the unscaled screen-relative Y coordinate of the center of
+ * the magnified region
+ * @deprecated Use {@link #getMagnificationConfig()} instead
+ */
+ @Deprecated
+ public float getCenterY() {
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance(mService).getConnection(
+ mService.mConnectionId);
+ if (connection != null) {
+ try {
+ return connection.getMagnificationCenterY(mDisplayId);
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Failed to obtain center Y", re);
+ re.rethrowFromSystemServer();
+ }
+ }
+ return 0.0f;
+ }
+
+ /**
+ * Returns the region of the screen currently active for magnification. Changes to
+ * magnification scale and center only affect this portion of the screen. The rest of the
+ * screen, for example input methods, cannot be magnified. This region is relative to the
+ * unscaled screen and is independent of the scale and center point.
+ * <p>
+ * The returned region will be empty if magnification is not active. Magnification is active
+ * if magnification gestures are enabled or if a service is running that can control
+ * magnification.
+ * <p>
+ * <strong>Note:</strong> If the service is not yet connected (e.g.
+ * {@link AccessibilityService#onServiceConnected()} has not yet been
+ * called) or the service has been disconnected, this method will
+ * return an empty region.
+ * </p>
+ * <p>
+ * <strong>Note:</strong> This legacy API gets the magnification region of full-screen
+ * magnification. To get the magnification region of the current controlling magnifier,
+ * use {@link #getCurrentMagnificationRegion()} instead.
+ * </p>
+ *
+ * @return the region of the screen currently active for magnification, or an empty region
+ * if magnification is not active.
+ * @deprecated Use {@link #getCurrentMagnificationRegion()} instead
+ */
+ @Deprecated
+ @NonNull
+ public Region getMagnificationRegion() {
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance(mService).getConnection(
+ mService.mConnectionId);
+ if (connection != null) {
+ try {
+ return connection.getMagnificationRegion(mDisplayId);
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Failed to obtain magnified region", re);
+ re.rethrowFromSystemServer();
+ }
+ }
+ return Region.obtain();
+ }
+
+ /**
+ * Returns the region of the screen currently active for magnification if the
+ * controlling magnification is {@link MagnificationConfig#MAGNIFICATION_MODE_FULLSCREEN}.
+ * Returns the region of screen projected on the magnification window if the
+ * controlling magnification is {@link MagnificationConfig#MAGNIFICATION_MODE_WINDOW}.
+ *
+ * <p>
+ * If the controlling mode is {@link MagnificationConfig#MAGNIFICATION_MODE_FULLSCREEN},
+ * the returned region will be empty if the magnification is
+ * not active. And the magnification is active if magnification gestures are enabled
+ * or if a service is running that can control magnification.
+ * </p><p>
+ * If the controlling mode is {@link MagnificationConfig#MAGNIFICATION_MODE_WINDOW},
+ * the returned region will be empty if the magnification is not activated.
+ * </p><p>
+ * <strong>Note:</strong> If the service is not yet connected (e.g.
+ * {@link AccessibilityService#onServiceConnected()} has not yet been
+ * called) or the service has been disconnected, this method will
+ * return an empty region.
+ * </p>
+ *
+ * @return the magnification region of the currently controlling magnification
+ */
+ @NonNull
+ public Region getCurrentMagnificationRegion() {
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance(mService).getConnection(
+ mService.mConnectionId);
+ if (connection != null) {
+ try {
+ return connection.getCurrentMagnificationRegion(mDisplayId);
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Failed to obtain the current magnified region", re);
+ re.rethrowFromSystemServer();
+ }
+ }
+ return Region.obtain();
+ }
+
+ /**
+ * Resets magnification scale and center to their default (e.g. no
+ * magnification) values.
+ * <p>
+ * <strong>Note:</strong> If the service is not yet connected (e.g.
+ * {@link AccessibilityService#onServiceConnected()} has not yet been
+ * called) or the service has been disconnected, this method will have
+ * no effect and return {@code false}.
+ * <p>
+ * <strong>Note:</strong> This legacy API reset full-screen magnification.
+ * To reset the current controlling magnifier, use
+ * {@link #resetCurrentMagnification(boolean)} ()} instead.
+ * </p>
+ *
+ * @param animate {@code true} to animate from the current scale and
+ * center or {@code false} to reset the scale and center
+ * immediately
+ * @return {@code true} on success, {@code false} on failure
+ */
+ public boolean reset(boolean animate) {
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance(mService).getConnection(
+ mService.mConnectionId);
+ if (connection != null) {
+ try {
+ return connection.resetMagnification(mDisplayId, animate);
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Failed to reset", re);
+ re.rethrowFromSystemServer();
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Resets magnification scale and center of the controlling magnification
+ * to their default (e.g. no magnification) values.
+ * <p>
+ * <strong>Note:</strong> If the service is not yet connected (e.g.
+ * {@link AccessibilityService#onServiceConnected()} has not yet been
+ * called) or the service has been disconnected, this method will have
+ * no effect and return {@code false}.
+ * </p>
+ *
+ * @param animate {@code true} to animate from the current scale and
+ * center or {@code false} to reset the scale and center
+ * immediately
+ * @return {@code true} on success, {@code false} on failure
+ */
+ public boolean resetCurrentMagnification(boolean animate) {
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance(mService).getConnection(
+ mService.mConnectionId);
+ if (connection != null) {
+ try {
+ return connection.resetCurrentMagnification(mDisplayId, animate);
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Failed to reset", re);
+ re.rethrowFromSystemServer();
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Sets the {@link MagnificationConfig}. The service controls the magnification by
+ * setting the config.
+ * <p>
+ * <strong>Note:</strong> If the service is not yet connected (e.g.
+ * {@link AccessibilityService#onServiceConnected()} has not yet been
+ * called) or the service has been disconnected, this method will have
+ * no effect and return {@code false}.
+ * </p>
+ *
+ * @param config the magnification config
+ * @param animate {@code true} to animate from the current spec or
+ * {@code false} to set the spec immediately
+ * @return {@code true} on success, {@code false} on failure
+ */
+ public boolean setMagnificationConfig(@NonNull MagnificationConfig config,
+ boolean animate) {
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance(mService).getConnection(
+ mService.mConnectionId);
+ if (connection != null) {
+ try {
+ return connection.setMagnificationConfig(mDisplayId, config, animate);
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Failed to set magnification config", re);
+ re.rethrowFromSystemServer();
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Sets the magnification scale.
+ * <p>
+ * <strong>Note:</strong> If the service is not yet connected (e.g.
+ * {@link AccessibilityService#onServiceConnected()} has not yet been
+ * called) or the service has been disconnected, this method will have
+ * no effect and return {@code false}.
+ * <p>
+ * <strong>Note:</strong> This legacy API sets the scale of full-screen
+ * magnification. To set the scale of the specified magnifier,
+ * use {@link #setMagnificationConfig} instead.
+ * </p>
+ *
+ * @param scale the magnification scale to set, must be >= 1 and <= 8
+ * @param animate {@code true} to animate from the current scale or
+ * {@code false} to set the scale immediately
+ * @return {@code true} on success, {@code false} on failure
+ * @deprecated Use {@link #setMagnificationConfig(MagnificationConfig, boolean)} instead
+ */
+ @Deprecated
+ public boolean setScale(float scale, boolean animate) {
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance(mService).getConnection(
+ mService.mConnectionId);
+ if (connection != null) {
+ try {
+ final MagnificationConfig config = new MagnificationConfig.Builder()
+ .setMode(MAGNIFICATION_MODE_FULLSCREEN)
+ .setScale(scale).build();
+ return connection.setMagnificationConfig(mDisplayId, config, animate);
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Failed to set scale", re);
+ re.rethrowFromSystemServer();
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Sets the center of the magnified viewport.
+ * <p>
+ * <strong>Note:</strong> If the service is not yet connected (e.g.
+ * {@link AccessibilityService#onServiceConnected()} has not yet been
+ * called) or the service has been disconnected, this method will have
+ * no effect and return {@code false}.
+ * </p>
+ * <p>
+ * <strong>Note:</strong> This legacy API sets the center of full-screen
+ * magnification. To set the center of the specified magnifier,
+ * use {@link #setMagnificationConfig} instead.
+ * </p>
+ *
+ * @param centerX the unscaled screen-relative X coordinate on which to
+ * center the viewport
+ * @param centerY the unscaled screen-relative Y coordinate on which to
+ * center the viewport
+ * @param animate {@code true} to animate from the current viewport
+ * center or {@code false} to set the center immediately
+ * @return {@code true} on success, {@code false} on failure
+ * @deprecated Use {@link #setMagnificationConfig(MagnificationConfig, boolean)} instead
+ */
+ @Deprecated
+ public boolean setCenter(float centerX, float centerY, boolean animate) {
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance(mService).getConnection(
+ mService.mConnectionId);
+ if (connection != null) {
+ try {
+ final MagnificationConfig config = new MagnificationConfig.Builder()
+ .setMode(MAGNIFICATION_MODE_FULLSCREEN)
+ .setCenterX(centerX).setCenterY(centerY).build();
+ return connection.setMagnificationConfig(mDisplayId, config, animate);
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Failed to set center", re);
+ re.rethrowFromSystemServer();
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Listener for changes in the state of magnification.
+ */
+ public interface OnMagnificationChangedListener {
+ /**
+ * Called when the magnified region, scale, or center changes.
+ * <p>
+ * <strong>Note:</strong> This legacy callback notifies only full-screen
+ * magnification change.
+ * </p>
+ *
+ * @param controller the magnification controller
+ * @param region the magnification region
+ * @param scale the new scale
+ * @param centerX the new X coordinate, in unscaled coordinates, around which
+ * magnification is focused
+ * @param centerY the new Y coordinate, in unscaled coordinates, around which
+ * magnification is focused
+ * @deprecated Override
+ * {@link #onMagnificationChanged(MagnificationController, Region, MagnificationConfig)}
+ * instead
+ */
+ @Deprecated
+ void onMagnificationChanged(@NonNull MagnificationController controller,
+ @NonNull Region region, float scale, float centerX, float centerY);
+
+ /**
+ * Called when the magnified region, mode, scale, or center changes of
+ * all magnification modes.
+ * <p>
+ * <strong>Note:</strong> This method can be overridden to listen to the
+ * magnification changes of all magnification modes then the legacy callback
+ * would not receive the notifications.
+ * Skipping calling super when overriding this method results in
+ * {@link #onMagnificationChanged(MagnificationController, Region, float, float, float)}
+ * not getting called.
+ * </p>
+ *
+ * @param controller the magnification controller
+ * @param region the magnification region
+ * If the config mode is
+ * {@link MagnificationConfig#MAGNIFICATION_MODE_FULLSCREEN},
+ * it is the region of the screen currently active for magnification.
+ * that is the same region as {@link #getMagnificationRegion()}.
+ * If the config mode is
+ * {@link MagnificationConfig#MAGNIFICATION_MODE_WINDOW},
+ * it is the region of screen projected on the magnification window.
+ * @param config The magnification config. That has the controlling magnification
+ * mode, the new scale and the new screen-relative center position
+ */
+ default void onMagnificationChanged(@NonNull MagnificationController controller,
+ @NonNull Region region, @NonNull MagnificationConfig config) {
+ if (config.getMode() == MAGNIFICATION_MODE_FULLSCREEN) {
+ onMagnificationChanged(controller, region,
+ config.getScale(), config.getCenterX(), config.getCenterY());
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns the soft keyboard controller, which may be used to query and modify the soft keyboard
+ * show mode.
+ *
+ * @return the soft keyboard controller
+ */
+ @NonNull
+ public final SoftKeyboardController getSoftKeyboardController() {
+ synchronized (mLock) {
+ if (mSoftKeyboardController == null) {
+ mSoftKeyboardController = new SoftKeyboardController(this, mLock);
+ }
+ return mSoftKeyboardController;
+ }
+ }
+
+ /**
+ * The default implementation returns our default {@link InputMethod}. Subclasses can override
+ * it to provide their own customized version. Accessibility services need to set the
+ * {@link AccessibilityServiceInfo#FLAG_INPUT_METHOD_EDITOR} flag to use input method APIs.
+ *
+ * @return the InputMethod.
+ */
+ @NonNull
+ public InputMethod onCreateInputMethod() {
+ return new InputMethod(this);
+ }
+
+ /**
+ * Returns the InputMethod instance after the system calls {@link #onCreateInputMethod()},
+ * which may be used to input text or get editable text selection change notifications. It will
+ * return null if the accessibility service doesn't set the
+ * {@link AccessibilityServiceInfo#FLAG_INPUT_METHOD_EDITOR} flag or the system doesn't call
+ * {@link #onCreateInputMethod()}.
+ *
+ * @return the InputMethod instance
+ */
+ @Nullable
+ public final InputMethod getInputMethod() {
+ return mInputMethod;
+ }
+
+ private void onSoftKeyboardShowModeChanged(int showMode) {
+ if (mSoftKeyboardController != null) {
+ mSoftKeyboardController.dispatchSoftKeyboardShowModeChanged(showMode);
+ }
+ }
+
+ /**
+ * Used to control, query, and listen for changes to the soft keyboard show mode.
+ * <p>
+ * Accessibility services may request to override the decisions normally made about whether or
+ * not the soft keyboard is shown.
+ * <p>
+ * If multiple services make conflicting requests, the last request is honored. A service may
+ * register a listener to find out if the mode has changed under it.
+ * <p>
+ * If the user takes action to override the behavior behavior requested by an accessibility
+ * service, the user's request takes precendence, the show mode will be reset to
+ * {@link AccessibilityService#SHOW_MODE_AUTO}, and services will no longer be able to control
+ * that aspect of the soft keyboard's behavior.
+ * <p>
+ * Note: Because soft keyboards are independent apps, the framework does not have total control
+ * over their behavior. They may choose to show themselves, or not, without regard to requests
+ * made here. So the framework will make a best effort to deliver the behavior requested, but
+ * cannot guarantee success.
+ *
+ * @see AccessibilityService#SHOW_MODE_AUTO
+ * @see AccessibilityService#SHOW_MODE_HIDDEN
+ * @see AccessibilityService#SHOW_MODE_IGNORE_HARD_KEYBOARD
+ */
+ public static final class SoftKeyboardController {
+ private final AccessibilityService mService;
+
+ /**
+ * Map of listeners to their handlers. Lazily created when adding the first
+ * soft keyboard change listener.
+ */
+ private ArrayMap<OnShowModeChangedListener, Handler> mListeners;
+ private final Object mLock;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ ENABLE_IME_SUCCESS,
+ ENABLE_IME_FAIL_BY_ADMIN,
+ ENABLE_IME_FAIL_UNKNOWN
+ })
+ public @interface EnableImeResult {}
+ /**
+ * Return value for {@link #setInputMethodEnabled(String, boolean)}. The action succeeded.
+ */
+ public static final int ENABLE_IME_SUCCESS = 0;
+ /**
+ * Return value for {@link #setInputMethodEnabled(String, boolean)}. The action failed
+ * because the InputMethod is not permitted by device policy manager.
+ */
+ public static final int ENABLE_IME_FAIL_BY_ADMIN = 1;
+ /**
+ * Return value for {@link #setInputMethodEnabled(String, boolean)}. The action failed
+ * and the reason is unknown.
+ */
+ public static final int ENABLE_IME_FAIL_UNKNOWN = 2;
+
+ SoftKeyboardController(@NonNull AccessibilityService service, @NonNull Object lock) {
+ mService = service;
+ mLock = lock;
+ }
+
+ /**
+ * Called when the service is connected.
+ */
+ void onServiceConnected() {
+ synchronized(mLock) {
+ if (mListeners != null && !mListeners.isEmpty()) {
+ setSoftKeyboardCallbackEnabled(true);
+ }
+ }
+ }
+
+ /**
+ * Adds the specified change listener to the list of show mode change listeners. The
+ * callback will occur on the service's main thread. Listener is not called on registration.
+ */
+ public void addOnShowModeChangedListener(@NonNull OnShowModeChangedListener listener) {
+ addOnShowModeChangedListener(listener, null);
+ }
+
+ /**
+ * Adds the specified change listener to the list of soft keyboard show mode change
+ * listeners. The callback will occur on the specified {@link Handler}'s thread, or on the
+ * services's main thread if the handler is {@code null}.
+ *
+ * @param listener the listener to add, must be non-null
+ * @param handler the handler on which to callback should execute, or {@code null} to
+ * execute on the service's main thread
+ */
+ public void addOnShowModeChangedListener(@NonNull OnShowModeChangedListener listener,
+ @Nullable Handler handler) {
+ synchronized (mLock) {
+ if (mListeners == null) {
+ mListeners = new ArrayMap<>();
+ }
+
+ final boolean shouldEnableCallback = mListeners.isEmpty();
+ mListeners.put(listener, handler);
+
+ if (shouldEnableCallback) {
+ // This may fail if the service is not connected yet, but if we still have
+ // listeners when it connects, we can try again.
+ setSoftKeyboardCallbackEnabled(true);
+ }
+ }
+ }
+
+ /**
+ * Removes the specified change listener from the list of keyboard show mode change
+ * listeners.
+ *
+ * @param listener the listener to remove, must be non-null
+ * @return {@code true} if the listener was removed, {@code false} otherwise
+ */
+ public boolean removeOnShowModeChangedListener(
+ @NonNull OnShowModeChangedListener listener) {
+ if (mListeners == null) {
+ return false;
+ }
+
+ synchronized (mLock) {
+ final int keyIndex = mListeners.indexOfKey(listener);
+ final boolean hasKey = keyIndex >= 0;
+ if (hasKey) {
+ mListeners.removeAt(keyIndex);
+ }
+
+ if (hasKey && mListeners.isEmpty()) {
+ // We just removed the last listener, so we don't need callbacks from the
+ // service anymore.
+ setSoftKeyboardCallbackEnabled(false);
+ }
+
+ return hasKey;
+ }
+ }
+
+ private void setSoftKeyboardCallbackEnabled(boolean enabled) {
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance(mService).getConnection(
+ mService.mConnectionId);
+ if (connection != null) {
+ try {
+ connection.setSoftKeyboardCallbackEnabled(enabled);
+ } catch (RemoteException re) {
+ throw new RuntimeException(re);
+ }
+ }
+ }
+
+ /**
+ * Dispatches the soft keyboard show mode change to any registered listeners. This should
+ * be called on the service's main thread.
+ */
+ void dispatchSoftKeyboardShowModeChanged(final int showMode) {
+ final ArrayMap<OnShowModeChangedListener, Handler> entries;
+ synchronized (mLock) {
+ if (mListeners == null || mListeners.isEmpty()) {
+ Slog.w(LOG_TAG, "Received soft keyboard show mode changed callback"
+ + " with no listeners registered!");
+ setSoftKeyboardCallbackEnabled(false);
+ return;
+ }
+
+ // Listeners may remove themselves. Perform a shallow copy to avoid concurrent
+ // modification.
+ entries = new ArrayMap<>(mListeners);
+ }
+
+ for (int i = 0, count = entries.size(); i < count; i++) {
+ final OnShowModeChangedListener listener = entries.keyAt(i);
+ final Handler handler = entries.valueAt(i);
+ if (handler != null) {
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ listener.onShowModeChanged(SoftKeyboardController.this, showMode);
+ }
+ });
+ } else {
+ // We're already on the main thread, just run the listener.
+ listener.onShowModeChanged(this, showMode);
+ }
+ }
+ }
+
+ /**
+ * Returns the show mode of the soft keyboard.
+ *
+ * @return the current soft keyboard show mode
+ *
+ * @see AccessibilityService#SHOW_MODE_AUTO
+ * @see AccessibilityService#SHOW_MODE_HIDDEN
+ * @see AccessibilityService#SHOW_MODE_IGNORE_HARD_KEYBOARD
+ */
+ @SoftKeyboardShowMode
+ public int getShowMode() {
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance(mService).getConnection(
+ mService.mConnectionId);
+ if (connection != null) {
+ try {
+ return connection.getSoftKeyboardShowMode();
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Failed to set soft keyboard behavior", re);
+ re.rethrowFromSystemServer();
+ }
+ }
+ return SHOW_MODE_AUTO;
+ }
+
+ /**
+ * Sets the soft keyboard show mode.
+ * <p>
+ * <strong>Note:</strong> If the service is not yet connected (e.g.
+ * {@link AccessibilityService#onServiceConnected()} has not yet been called) or the
+ * service has been disconnected, this method will have no effect and return {@code false}.
+ *
+ * @param showMode the new show mode for the soft keyboard
+ * @return {@code true} on success
+ *
+ * @see AccessibilityService#SHOW_MODE_AUTO
+ * @see AccessibilityService#SHOW_MODE_HIDDEN
+ * @see AccessibilityService#SHOW_MODE_IGNORE_HARD_KEYBOARD
+ */
+ public boolean setShowMode(@SoftKeyboardShowMode int showMode) {
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance(mService).getConnection(
+ mService.mConnectionId);
+ if (connection != null) {
+ try {
+ return connection.setSoftKeyboardShowMode(showMode);
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Failed to set soft keyboard behavior", re);
+ re.rethrowFromSystemServer();
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Listener for changes in the soft keyboard show mode.
+ */
+ public interface OnShowModeChangedListener {
+ /**
+ * Called when the soft keyboard behavior changes. The default show mode is
+ * {@code SHOW_MODE_AUTO}, where the soft keyboard is shown when a text input field is
+ * focused. An AccessibilityService can also request the show mode
+ * {@code SHOW_MODE_HIDDEN}, where the soft keyboard is never shown.
+ *
+ * @param controller the soft keyboard controller
+ * @param showMode the current soft keyboard show mode
+ */
+ void onShowModeChanged(@NonNull SoftKeyboardController controller,
+ @SoftKeyboardShowMode int showMode);
+ }
+
+ /**
+ * Switches the current IME for the user for whom the service is enabled. The change will
+ * persist until the current IME is explicitly changed again, and may persist beyond the
+ * life cycle of the requesting service.
+ *
+ * @param imeId The ID of the input method to make current. This IME must be installed and
+ * enabled.
+ * @return {@code true} if the current input method was successfully switched to the input
+ * method by {@code imeId},
+ * {@code false} if the input method specified is not installed, not enabled, or
+ * otherwise not available to become the current IME
+ *
+ * @see android.view.inputmethod.InputMethodInfo#getId()
+ */
+ public boolean switchToInputMethod(@NonNull String imeId) {
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance(mService).getConnection(
+ mService.mConnectionId);
+ if (connection != null) {
+ try {
+ return connection.switchToInputMethod(imeId);
+ } catch (RemoteException re) {
+ throw new RuntimeException(re);
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Enable or disable the specified IME for the user for whom the service is activated. The
+ * IME needs to be in the same package as the service and needs to be allowed by device
+ * policy, if there is one. The change will persist until the specified IME is next
+ * explicitly enabled or disabled by whatever means, such as user choice, and may persist
+ * beyond the life cycle of the requesting service.
+ *
+ * @param imeId The ID of the input method to enable or disable. This IME must be installed.
+ * @param enabled {@code true} if the input method associated with {@code imeId} should be
+ * enabled.
+ * @return status code for the result of enabling/disabling the input method associated
+ * with {@code imeId}.
+ * @throws SecurityException if the input method is not in the same package as the service.
+ *
+ * @see android.view.inputmethod.InputMethodInfo#getId()
+ */
+ @CheckResult
+ @EnableImeResult
+ public int setInputMethodEnabled(@NonNull String imeId, boolean enabled)
+ throws SecurityException {
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance(mService).getConnection(
+ mService.mConnectionId);
+ if (connection != null) {
+ try {
+ return connection.setInputMethodEnabled(imeId, enabled);
+ } catch (RemoteException re) {
+ throw new RuntimeException(re);
+ }
+ }
+ return ENABLE_IME_FAIL_UNKNOWN;
+ }
+ }
+
+ /**
+ * Returns the controller for the accessibility button within the system's navigation area.
+ * This instance may be used to query the accessibility button's state and register listeners
+ * for interactions with and state changes for the accessibility button when
+ * {@link AccessibilityServiceInfo#FLAG_REQUEST_ACCESSIBILITY_BUTTON} is set.
+ * <p>
+ * <strong>Note:</strong> Not all devices are capable of displaying the accessibility button
+ * within a navigation area, and as such, use of this class should be considered only as an
+ * optional feature or shortcut on supported device implementations.
+ * </p>
+ *
+ * @return the accessibility button controller for this {@link AccessibilityService}
+ */
+ @NonNull
+ public final AccessibilityButtonController getAccessibilityButtonController() {
+ return getAccessibilityButtonController(Display.DEFAULT_DISPLAY);
+ }
+
+ /**
+ * Returns the controller of specified logical display for the accessibility button within the
+ * system's navigation area. This instance may be used to query the accessibility button's
+ * state and register listeners for interactions with and state changes for the accessibility
+ * button when {@link AccessibilityServiceInfo#FLAG_REQUEST_ACCESSIBILITY_BUTTON} is set.
+ * <p>
+ * <strong>Note:</strong> Not all devices are capable of displaying the accessibility button
+ * within a navigation area, and as such, use of this class should be considered only as an
+ * optional feature or shortcut on supported device implementations.
+ * </p>
+ *
+ * @param displayId The logic display id, use {@link Display#DEFAULT_DISPLAY} for default
+ * display.
+ * @return the accessibility button controller for this {@link AccessibilityService}
+ */
+ @NonNull
+ public final AccessibilityButtonController getAccessibilityButtonController(int displayId) {
+ synchronized (mLock) {
+ AccessibilityButtonController controller = mAccessibilityButtonControllers.get(
+ displayId);
+ if (controller == null) {
+ controller = new AccessibilityButtonController(
+ AccessibilityInteractionClient.getInstance(this).getConnection(mConnectionId));
+ mAccessibilityButtonControllers.put(displayId, controller);
+ }
+ return controller;
+ }
+ }
+
+ private void onAccessibilityButtonClicked(int displayId) {
+ getAccessibilityButtonController(displayId).dispatchAccessibilityButtonClicked();
+ }
+
+ private void onAccessibilityButtonAvailabilityChanged(boolean available) {
+ getAccessibilityButtonController().dispatchAccessibilityButtonAvailabilityChanged(
+ available);
+ }
+
+ /** Sets the cache status.
+ *
+ * <p>If {@code enabled}, enable the cache and prefetching. Otherwise, disable the cache
+ * and prefetching.
+ * Note: By default the cache is enabled.
+ * @param enabled whether to enable or disable the cache.
+ * @return {@code true} if the cache and connection are not null, so the cache status is set.
+ */
+ public boolean setCacheEnabled(boolean enabled) {
+ AccessibilityCache cache =
+ AccessibilityInteractionClient.getCache(mConnectionId);
+ if (cache == null) {
+ return false;
+ }
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getConnection(mConnectionId);
+ if (connection == null) {
+ return false;
+ }
+ try {
+ connection.setCacheEnabled(enabled);
+ cache.setEnabled(enabled);
+ return true;
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Error while setting status of cache", re);
+ re.rethrowFromSystemServer();
+ }
+ return false;
+ }
+
+ /** Invalidates {@code node} and its subtree in the cache.
+ * @param node the node to invalidate.
+ * @return {@code true} if the subtree rooted at {@code node} was invalidated.
+ */
+ public boolean clearCachedSubtree(@NonNull AccessibilityNodeInfo node) {
+ AccessibilityCache cache =
+ AccessibilityInteractionClient.getCache(mConnectionId);
+ if (cache == null) {
+ return false;
+ }
+ return cache.clearSubTree(node);
+ }
+
+ /** Clears the cache.
+ * @return {@code true} if the cache was cleared
+ */
+ public boolean clearCache() {
+ AccessibilityCache cache =
+ AccessibilityInteractionClient.getCache(mConnectionId);
+ if (cache == null) {
+ return false;
+ }
+ cache.clear();
+ return true;
+ }
+
+ /** Checks if {@code node} is in the cache.
+ * @param node the node to check.
+ * @return {@code true} if {@code node} is in the cache.
+ */
+ public boolean isNodeInCache(@NonNull AccessibilityNodeInfo node) {
+ AccessibilityCache cache =
+ AccessibilityInteractionClient.getCache(mConnectionId);
+ if (cache == null) {
+ return false;
+ }
+ return cache.isNodeInCache(node);
+ }
+
+ /** Returns {@code true} if the cache is enabled. */
+ public boolean isCacheEnabled() {
+ AccessibilityCache cache =
+ AccessibilityInteractionClient.getCache(mConnectionId);
+ if (cache == null) {
+ return false;
+ }
+ return cache.isEnabled();
+ }
+
+ /** This is called when the system action list is changed. */
+ public void onSystemActionsChanged() {
+ }
+
+ /**
+ * Returns a list of system actions available in the system right now.
+ * <p>
+ * System actions that correspond to the global action constants will have matching action IDs.
+ * For example, an with id {@link #GLOBAL_ACTION_BACK} will perform the back action.
+ * </p>
+ * <p>
+ * These actions should be called by {@link #performGlobalAction}.
+ * </p>
+ *
+ * @return A list of available system actions.
+ */
+ public final @NonNull List<AccessibilityAction> getSystemActions() {
+ IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance(this).getConnection(mConnectionId);
+ if (connection != null) {
+ try {
+ return connection.getSystemActions();
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Error while calling getSystemActions", re);
+ re.rethrowFromSystemServer();
+ }
+ }
+ return Collections.emptyList();
+ }
+
+ /**
+ * Performs a global action. Such an action can be performed
+ * at any moment regardless of the current application or user
+ * location in that application. For example going back, going
+ * home, opening recents, etc.
+ *
+ * <p>
+ * Note: The global action ids themselves give no information about the current availability
+ * of their corresponding actions. To determine if a global action is available, use
+ * {@link #getSystemActions()}
+ *
+ * @param action The action to perform.
+ * @return Whether the action was successfully performed.
+ *
+ * Perform actions using ids like the id constants referenced below:
+ * @see #GLOBAL_ACTION_BACK
+ * @see #GLOBAL_ACTION_HOME
+ * @see #GLOBAL_ACTION_NOTIFICATIONS
+ * @see #GLOBAL_ACTION_RECENTS
+ * @see #GLOBAL_ACTION_DPAD_UP
+ * @see #GLOBAL_ACTION_DPAD_DOWN
+ * @see #GLOBAL_ACTION_DPAD_LEFT
+ * @see #GLOBAL_ACTION_DPAD_RIGHT
+ * @see #GLOBAL_ACTION_DPAD_CENTER
+ */
+ public final boolean performGlobalAction(int action) {
+ IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance(this).getConnection(mConnectionId);
+ if (connection != null) {
+ try {
+ return connection.performGlobalAction(action);
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Error while calling performGlobalAction", re);
+ re.rethrowFromSystemServer();
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Find the view that has the specified focus type. The search is performed
+ * across all windows.
+ * <p>
+ * <strong>Note:</strong> In order to access the windows your service has
+ * to declare the capability to retrieve window content by setting the
+ * {@link android.R.styleable#AccessibilityService_canRetrieveWindowContent}
+ * property in its meta-data. For details refer to {@link #SERVICE_META_DATA}.
+ * Also the service has to opt-in to retrieve the interactive windows by
+ * setting the {@link AccessibilityServiceInfo#FLAG_RETRIEVE_INTERACTIVE_WINDOWS}
+ * flag. Otherwise, the search will be performed only in the active window.
+ * </p>
+ * <p>
+ * <strong>Note:</strong> If the view with {@link AccessibilityNodeInfo#FOCUS_INPUT}
+ * is on an embedded view hierarchy which is embedded in a {@link android.view.SurfaceView} via
+ * {@link android.view.SurfaceView#setChildSurfacePackage}, there is a limitation that this API
+ * won't be able to find the node for the view. It's because views don't know about
+ * the embedded hierarchies. Instead, you could traverse all the nodes to find the
+ * focus.
+ * </p>
+ *
+ * @param focus The focus to find. One of {@link AccessibilityNodeInfo#FOCUS_INPUT} or
+ * {@link AccessibilityNodeInfo#FOCUS_ACCESSIBILITY}.
+ * @return The node info of the focused view or null.
+ *
+ * @see AccessibilityNodeInfo#FOCUS_INPUT
+ * @see AccessibilityNodeInfo#FOCUS_ACCESSIBILITY
+ */
+ public AccessibilityNodeInfo findFocus(int focus) {
+ return AccessibilityInteractionClient.getInstance(this).findFocus(mConnectionId,
+ AccessibilityWindowInfo.ANY_WINDOW_ID, AccessibilityNodeInfo.ROOT_NODE_ID, focus);
+ }
+
+ /**
+ * Gets the an {@link AccessibilityServiceInfo} describing this
+ * {@link AccessibilityService}. This method is useful if one wants
+ * to change some of the dynamically configurable properties at
+ * runtime.
+ *
+ * @return The accessibility service info.
+ *
+ * @see AccessibilityServiceInfo
+ */
+ public final AccessibilityServiceInfo getServiceInfo() {
+ IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance(this).getConnection(mConnectionId);
+ if (connection != null) {
+ try {
+ return connection.getServiceInfo();
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Error while getting AccessibilityServiceInfo", re);
+ re.rethrowFromSystemServer();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Sets the {@link AccessibilityServiceInfo} that describes this service.
+ * <p>
+ * Note: You can call this method any time but the info will be picked up after
+ * the system has bound to this service and when this method is called thereafter.
+ *
+ * @param info The info.
+ */
+ public final void setServiceInfo(AccessibilityServiceInfo info) {
+ mInfo = info;
+ updateInputMethod(info);
+ mMotionEventSources = info.getMotionEventSources();
+ sendServiceInfo();
+ }
+
+ /**
+ * Sets the {@link AccessibilityServiceInfo} for this service if the latter is
+ * properly set and there is an {@link IAccessibilityServiceConnection} to the
+ * AccessibilityManagerService.
+ */
+ private void sendServiceInfo() {
+ IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance(this).getConnection(mConnectionId);
+ if (mInfo != null && connection != null) {
+ try {
+ connection.setServiceInfo(mInfo);
+ mInfo = null;
+ AccessibilityInteractionClient.getInstance(this).clearCache(mConnectionId);
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Error while setting AccessibilityServiceInfo", re);
+ re.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ @Override
+ public Object getSystemService(@ServiceName @NonNull String name) {
+ if (getBaseContext() == null) {
+ throw new IllegalStateException(
+ "System services not available to Activities before onCreate()");
+ }
+
+ // Guarantee that we always return the same window manager instance.
+ if (WINDOW_SERVICE.equals(name)) {
+ if (mWindowManager == null) {
+ mWindowManager = (WindowManager) getBaseContext().getSystemService(name);
+ final WindowManagerImpl wm = (WindowManagerImpl) mWindowManager;
+ // Set e default token obtained from the connection to ensure client could use
+ // accessibility overlay.
+ wm.setDefaultToken(mWindowToken);
+ }
+ return mWindowManager;
+ }
+ return super.getSystemService(name);
+ }
+
+ /**
+ * Takes a screenshot of the specified display and returns it via an
+ * {@link AccessibilityService.ScreenshotResult}. You can use {@link Bitmap#wrapHardwareBuffer}
+ * to construct the bitmap from the ScreenshotResult's payload.
+ * <p>
+ * <strong>Note:</strong> In order to take screenshot your service has
+ * to declare the capability to take screenshot by setting the
+ * {@link android.R.styleable#AccessibilityService_canTakeScreenshot}
+ * property in its meta-data. For details refer to {@link #SERVICE_META_DATA}.
+ * </p>
+ *
+ * @param displayId The logic display id, must be {@link Display#DEFAULT_DISPLAY} for
+ * default display.
+ * @param executor Executor on which to run the callback.
+ * @param callback The callback invoked when taking screenshot has succeeded or failed.
+ * See {@link TakeScreenshotCallback} for details.
+ * @see #takeScreenshotOfWindow
+ */
+ public void takeScreenshot(int displayId, @NonNull @CallbackExecutor Executor executor,
+ @NonNull TakeScreenshotCallback callback) {
+ Preconditions.checkNotNull(executor, "executor cannot be null");
+ Preconditions.checkNotNull(callback, "callback cannot be null");
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance(this).getConnection(mConnectionId);
+ if (connection == null) {
+ sendScreenshotFailure(ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR, executor, callback);
+ return;
+ }
+ try {
+ connection.takeScreenshot(displayId, new RemoteCallback((result) -> {
+ final int status = result.getInt(KEY_ACCESSIBILITY_SCREENSHOT_STATUS);
+ if (status != TAKE_SCREENSHOT_SUCCESS) {
+ sendScreenshotFailure(status, executor, callback);
+ return;
+ }
+ final HardwareBuffer hardwareBuffer =
+ result.getParcelable(KEY_ACCESSIBILITY_SCREENSHOT_HARDWAREBUFFER, android.hardware.HardwareBuffer.class);
+ final ParcelableColorSpace colorSpace =
+ result.getParcelable(KEY_ACCESSIBILITY_SCREENSHOT_COLORSPACE,
+ android.graphics.ParcelableColorSpace.class);
+ final ScreenshotResult screenshot = new ScreenshotResult(hardwareBuffer,
+ colorSpace.getColorSpace(),
+ result.getLong(KEY_ACCESSIBILITY_SCREENSHOT_TIMESTAMP));
+ sendScreenshotSuccess(screenshot, executor, callback);
+ }));
+ } catch (RemoteException re) {
+ throw new RuntimeException(re);
+ }
+ }
+
+ /**
+ * Takes a screenshot of the specified window and returns it via an
+ * {@link AccessibilityService.ScreenshotResult}. You can use {@link Bitmap#wrapHardwareBuffer}
+ * to construct the bitmap from the ScreenshotResult's payload.
+ * <p>
+ * <strong>Note:</strong> In order to take screenshots your service has
+ * to declare the capability to take screenshot by setting the
+ * {@link android.R.styleable#AccessibilityService_canTakeScreenshot}
+ * property in its meta-data. For details refer to {@link #SERVICE_META_DATA}.
+ * </p>
+ * <p>
+ * Both this method and {@link #takeScreenshot} can be used for machine learning-based visual
+ * screen understanding. Use <code>takeScreenshotOfWindow</code> if your target window might be
+ * visually underneath an accessibility overlay (from your or another accessibility service) in
+ * order to capture the window contents without the screenshot being covered by the overlay
+ * contents drawn on the screen.
+ * </p>
+ *
+ * @param accessibilityWindowId The window id, from {@link AccessibilityWindowInfo#getId()}.
+ * @param executor Executor on which to run the callback.
+ * @param callback The callback invoked when taking screenshot has succeeded or failed.
+ * See {@link TakeScreenshotCallback} for details.
+ * @see #takeScreenshot
+ */
+ public void takeScreenshotOfWindow(int accessibilityWindowId,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull TakeScreenshotCallback callback) {
+ AccessibilityInteractionClient.getInstance(this).takeScreenshotOfWindow(
+ mConnectionId, accessibilityWindowId, executor, callback);
+ }
+
+ /**
+ * Sets the strokeWidth and color of the accessibility focus rectangle.
+ * <p>
+ * <strong>Note:</strong> This setting persists until this or another active
+ * AccessibilityService changes it or the device reboots.
+ * </p>
+ *
+ * @param strokeWidth The stroke width of the rectangle in pixels.
+ * Setting this value to zero results in no focus rectangle being drawn.
+ * @param color The color of the rectangle.
+ */
+ public void setAccessibilityFocusAppearance(int strokeWidth, @ColorInt int color) {
+ IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance(this).getConnection(mConnectionId);
+ if (connection != null) {
+ try {
+ connection.setFocusAppearance(strokeWidth, color);
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Error while setting the strokeWidth and color of the "
+ + "accessibility focus rectangle", re);
+ re.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Implement to return the implementation of the internal accessibility
+ * service interface.
+ */
+ @Override
+ public final IBinder onBind(Intent intent) {
+ return new IAccessibilityServiceClientWrapper(this, getMainExecutor(), new Callbacks() {
+ @Override
+ public void onServiceConnected() {
+ AccessibilityService.this.dispatchServiceConnected();
+ }
+
+ @Override
+ public void onInterrupt() {
+ AccessibilityService.this.onInterrupt();
+ }
+
+ @Override
+ public void onAccessibilityEvent(AccessibilityEvent event) {
+ AccessibilityService.this.onAccessibilityEvent(event);
+ }
+
+ @Override
+ public void init(int connectionId, IBinder windowToken) {
+ mConnectionId = connectionId;
+ mWindowToken = windowToken;
+
+ // The client may have already obtained the window manager, so
+ // update the default token on whatever manager we gave them.
+ if (mWindowManager != null) {
+ final WindowManagerImpl wm = (WindowManagerImpl) mWindowManager;
+ wm.setDefaultToken(mWindowToken);
+ }
+ }
+
+ @Override
+ public boolean onGesture(AccessibilityGestureEvent gestureEvent) {
+ return AccessibilityService.this.onGesture(gestureEvent);
+ }
+
+ @Override
+ public boolean onKeyEvent(KeyEvent event) {
+ return AccessibilityService.this.onKeyEvent(event);
+ }
+
+ @Override
+ public void onMagnificationChanged(int displayId, @NonNull Region region,
+ MagnificationConfig config) {
+ AccessibilityService.this.onMagnificationChanged(displayId, region, config);
+ }
+
+ @Override
+ public void onMotionEvent(MotionEvent event) {
+ AccessibilityService.this.sendMotionEventToCallback(event);
+ }
+
+ @Override
+ public void onTouchStateChanged(int displayId, int state) {
+ AccessibilityService.this.onTouchStateChanged(displayId, state);
+ }
+
+ @Override
+ public void onSoftKeyboardShowModeChanged(int showMode) {
+ AccessibilityService.this.onSoftKeyboardShowModeChanged(showMode);
+ }
+
+ @Override
+ public void onPerformGestureResult(int sequence, boolean completedSuccessfully) {
+ AccessibilityService.this.onPerformGestureResult(sequence, completedSuccessfully);
+ }
+
+ @Override
+ public void onFingerprintCapturingGesturesChanged(boolean active) {
+ AccessibilityService.this.onFingerprintCapturingGesturesChanged(active);
+ }
+
+ @Override
+ public void onFingerprintGesture(int gesture) {
+ AccessibilityService.this.onFingerprintGesture(gesture);
+ }
+
+ @Override
+ public void onAccessibilityButtonClicked(int displayId) {
+ AccessibilityService.this.onAccessibilityButtonClicked(displayId);
+ }
+
+ @Override
+ public void onAccessibilityButtonAvailabilityChanged(boolean available) {
+ AccessibilityService.this.onAccessibilityButtonAvailabilityChanged(available);
+ }
+
+ @Override
+ public void onSystemActionsChanged() {
+ AccessibilityService.this.onSystemActionsChanged();
+ }
+
+ @Override
+ public void createImeSession(IAccessibilityInputMethodSessionCallback callback) {
+ if (mInputMethod != null) {
+ mInputMethod.createImeSession(callback);
+ }
+ }
+
+ @Override
+ public void startInput(@Nullable RemoteAccessibilityInputConnection connection,
+ @NonNull EditorInfo editorInfo, boolean restarting) {
+ if (mInputMethod != null) {
+ if (restarting) {
+ mInputMethod.restartInput(connection, editorInfo);
+ } else {
+ mInputMethod.startInput(connection, editorInfo);
+ }
+ }
+ }
+ });
+ }
+
+ /**
+ * Implements the internal {@link IAccessibilityServiceClient} interface to convert
+ * incoming calls to it back to calls on an {@link AccessibilityService}.
+ *
+ * @hide
+ */
+ public static class IAccessibilityServiceClientWrapper extends
+ IAccessibilityServiceClient.Stub {
+
+ private final Callbacks mCallback;
+ private final Context mContext;
+ private final Executor mExecutor;
+
+ private int mConnectionId = AccessibilityInteractionClient.NO_ID;
+
+ /**
+ * This is not {@code null} only between {@link #bindInput()} and {@link #unbindInput()} so
+ * that {@link RemoteAccessibilityInputConnection} can query if {@link #unbindInput()} has
+ * already been called or not, mainly to avoid unnecessary blocking operations.
+ *
+ * <p>This field must be set and cleared only from the binder thread(s), where the system
+ * guarantees that {@link #bindInput()},
+ * {@link #startInput(IRemoteAccessibilityInputConnection, EditorInfo, boolean)},
+ * and {@link #unbindInput()} are called with the same order as the original calls
+ * in {@link com.android.server.inputmethod.InputMethodManagerService}.
+ * See {@link IBinder#FLAG_ONEWAY} for detailed semantics.</p>
+ */
+ @Nullable
+ CancellationGroup mCancellationGroup = null;
+
+ public IAccessibilityServiceClientWrapper(Context context, Executor executor,
+ Callbacks callback) {
+ mCallback = callback;
+ mContext = context;
+ mExecutor = executor;
+ }
+
+ public IAccessibilityServiceClientWrapper(Context context, Looper looper,
+ Callbacks callback) {
+ this(context, new HandlerExecutor(new Handler(looper)), callback);
+ }
+
+ public void init(IAccessibilityServiceConnection connection, int connectionId,
+ IBinder windowToken) {
+ mExecutor.execute(() -> {
+ mConnectionId = connectionId;
+ if (connection != null) {
+ AccessibilityInteractionClient.getInstance(mContext).addConnection(
+ mConnectionId, connection, /*initializeCache=*/true);
+ if (mContext != null) {
+ try {
+ connection.setAttributionTag(mContext.getAttributionTag());
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Error while setting attributionTag", re);
+ re.rethrowFromSystemServer();
+ }
+ }
+ mCallback.init(mConnectionId, windowToken);
+ mCallback.onServiceConnected();
+ } else {
+ AccessibilityInteractionClient.getInstance(mContext)
+ .clearCache(mConnectionId);
+ AccessibilityInteractionClient.getInstance(mContext).removeConnection(
+ mConnectionId);
+ mConnectionId = AccessibilityInteractionClient.NO_ID;
+ mCallback.init(AccessibilityInteractionClient.NO_ID, null);
+ }
+ return;
+ });
+ }
+
+ public void onInterrupt() {
+ mExecutor.execute(() -> {
+ if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
+ mCallback.onInterrupt();
+ }
+ });
+ }
+
+ public void onAccessibilityEvent(AccessibilityEvent event, boolean serviceWantsEvent) {
+ mExecutor.execute(() -> {
+ if (event != null) {
+ // Send the event to AccessibilityCache via AccessibilityInteractionClient
+ AccessibilityInteractionClient.getInstance(mContext).onAccessibilityEvent(
+ event, mConnectionId);
+ if (serviceWantsEvent
+ && (mConnectionId != AccessibilityInteractionClient.NO_ID)) {
+ // Send the event to AccessibilityService
+ mCallback.onAccessibilityEvent(event);
+ }
+ }
+ return;
+ });
+ }
+
+ @Override
+ public void onGesture(AccessibilityGestureEvent gestureInfo) {
+ mExecutor.execute(() -> {
+ if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
+ mCallback.onGesture(gestureInfo);
+ }
+ return;
+ });
+ }
+
+ public void clearAccessibilityCache() {
+ mExecutor.execute(() -> {
+ AccessibilityInteractionClient.getInstance(mContext).clearCache(mConnectionId);
+ return;
+ });
+ }
+
+ @Override
+ public void onKeyEvent(KeyEvent event, int sequence) {
+ mExecutor.execute(() -> {
+ try {
+ IAccessibilityServiceConnection connection = AccessibilityInteractionClient
+ .getInstance(mContext).getConnection(mConnectionId);
+ if (connection != null) {
+ final boolean result = mCallback.onKeyEvent(event);
+ try {
+ connection.setOnKeyEventResult(result, sequence);
+ } catch (RemoteException re) {
+ /* ignore */
+ }
+ }
+ } finally {
+ // Make sure the event is recycled.
+ try {
+ event.recycle();
+ } catch (IllegalStateException ise) {
+ /* ignore - best effort */
+ }
+ }
+ return;
+ });
+ }
+
+ /** Magnification changed callbacks for different displays */
+ public void onMagnificationChanged(int displayId, @NonNull Region region,
+ MagnificationConfig config) {
+ mExecutor.execute(() -> {
+ if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
+ mCallback.onMagnificationChanged(displayId, region, config);
+ }
+ return;
+ });
+ }
+
+ public void onSoftKeyboardShowModeChanged(int showMode) {
+ mExecutor.execute(() -> {
+ if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
+ mCallback.onSoftKeyboardShowModeChanged(showMode);
+ }
+ return;
+ });
+ }
+
+ public void onPerformGestureResult(int sequence, boolean successfully) {
+ mExecutor.execute(() -> {
+ if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
+ mCallback.onPerformGestureResult(sequence, successfully);
+ }
+ return;
+ });
+ }
+
+ public void onFingerprintCapturingGesturesChanged(boolean active) {
+ mExecutor.execute(() -> {
+ if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
+ mCallback.onFingerprintCapturingGesturesChanged(active);
+ }
+ return;
+ });
+ }
+
+ public void onFingerprintGesture(int gesture) {
+ mExecutor.execute(() -> {
+ if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
+ mCallback.onFingerprintGesture(gesture);
+ }
+ return;
+ });
+ }
+
+ /** Accessibility button clicked callbacks for different displays */
+ public void onAccessibilityButtonClicked(int displayId) {
+ mExecutor.execute(() -> {
+ if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
+ mCallback.onAccessibilityButtonClicked(displayId);
+ }
+ return;
+ });
+ }
+
+ public void onAccessibilityButtonAvailabilityChanged(boolean available) {
+ mExecutor.execute(() -> {
+ if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
+ mCallback.onAccessibilityButtonAvailabilityChanged(available);
+ }
+ return;
+ });
+ }
+
+ /** This is called when the system action list is changed. */
+ public void onSystemActionsChanged() {
+ mExecutor.execute(() -> {
+ if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
+ mCallback.onSystemActionsChanged();
+ }
+ return;
+ });
+ }
+
+ /** This is called when an app requests ime sessions or when the service is enabled. */
+ public void createImeSession(IAccessibilityInputMethodSessionCallback callback) {
+ mExecutor.execute(() -> {
+ if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
+ mCallback.createImeSession(callback);
+ }
+ });
+ }
+
+ /**
+ * This is called when InputMethodManagerService requests to set the session enabled or
+ * disabled
+ */
+ public void setImeSessionEnabled(IAccessibilityInputMethodSession session,
+ boolean enabled) {
+ try {
+ AccessibilityInputMethodSession ls =
+ ((AccessibilityInputMethodSessionWrapper) session).getSession();
+ if (ls == null) {
+ Log.w(LOG_TAG, "Session is already finished: " + session);
+ return;
+ }
+ mExecutor.execute(() -> {
+ if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
+ ls.setEnabled(enabled);
+ }
+ return;
+ });
+ } catch (ClassCastException e) {
+ Log.w(LOG_TAG, "Incoming session not of correct type: " + session, e);
+ }
+ }
+
+ /** This is called when an app binds input or when the service is enabled. */
+ public void bindInput() {
+ if (mCancellationGroup != null) {
+ Log.e(LOG_TAG, "bindInput must be paired with unbindInput.");
+ }
+ mCancellationGroup = new CancellationGroup();
+ }
+
+ /** This is called when an app unbinds input or when the service is disabled. */
+ public void unbindInput() {
+ if (mCancellationGroup != null) {
+ // Signal the flag then forget it.
+ mCancellationGroup.cancelAll();
+ mCancellationGroup = null;
+ } else {
+ Log.e(LOG_TAG, "unbindInput must be paired with bindInput.");
+ }
+ }
+
+ /** This is called when an app starts input or when the service is enabled. */
+ public void startInput(IRemoteAccessibilityInputConnection connection,
+ EditorInfo editorInfo, boolean restarting) {
+ if (mCancellationGroup == null) {
+ Log.e(LOG_TAG, "startInput must be called after bindInput.");
+ mCancellationGroup = new CancellationGroup();
+ }
+ mExecutor.execute(() -> {
+ if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
+ final RemoteAccessibilityInputConnection ic = connection == null ? null
+ : new RemoteAccessibilityInputConnection(
+ connection, mCancellationGroup);
+ editorInfo.makeCompatible(mContext.getApplicationInfo().targetSdkVersion);
+ mCallback.startInput(ic, editorInfo, restarting);
+ }
+ });
+ }
+
+ @Override
+ public void onMotionEvent(MotionEvent event) {
+ mExecutor.execute(() -> {
+ mCallback.onMotionEvent(event);
+ });
+ }
+
+ @Override
+ public void onTouchStateChanged(int displayId, int state) {
+ mExecutor.execute(() -> {
+ mCallback.onTouchStateChanged(displayId, state);
+ });
+ }
+ }
+
+ /**
+ * Class used to report status of dispatched gestures
+ */
+ public static abstract class GestureResultCallback {
+ /** Called when the gesture has completed successfully
+ *
+ * @param gestureDescription The description of the gesture that completed.
+ */
+ public void onCompleted(GestureDescription gestureDescription) {
+ }
+
+ /** Called when the gesture was cancelled
+ *
+ * @param gestureDescription The description of the gesture that was cancelled.
+ */
+ public void onCancelled(GestureDescription gestureDescription) {
+ }
+ }
+
+ /* Object to keep track of gesture result callbacks */
+ private static class GestureResultCallbackInfo {
+ GestureDescription gestureDescription;
+ GestureResultCallback callback;
+ Handler handler;
+
+ GestureResultCallbackInfo(GestureDescription gestureDescription,
+ GestureResultCallback callback, Handler handler) {
+ this.gestureDescription = gestureDescription;
+ this.callback = callback;
+ this.handler = handler;
+ }
+ }
+
+ private void sendScreenshotSuccess(ScreenshotResult screenshot, Executor executor,
+ TakeScreenshotCallback callback) {
+ executor.execute(() -> callback.onSuccess(screenshot));
+ }
+
+ private void sendScreenshotFailure(@ScreenshotErrorCode int errorCode, Executor executor,
+ TakeScreenshotCallback callback) {
+ executor.execute(() -> callback.onFailure(errorCode));
+ }
+
+ /**
+ * Interface used to report status of taking screenshot.
+ */
+ public interface TakeScreenshotCallback {
+ /** Called when taking screenshot has completed successfully.
+ *
+ * @param screenshot The content of screenshot.
+ */
+ void onSuccess(@NonNull ScreenshotResult screenshot);
+
+ /** Called when taking screenshot has failed. {@code errorCode} will identify the
+ * reason of failure.
+ *
+ * @param errorCode The error code of this operation.
+ */
+ void onFailure(@ScreenshotErrorCode int errorCode);
+ }
+
+ /**
+ * Can be used to construct a bitmap of the screenshot or any other operations for
+ * {@link AccessibilityService#takeScreenshot} API.
+ */
+ public static final class ScreenshotResult {
+ private final @NonNull HardwareBuffer mHardwareBuffer;
+ private final @NonNull ColorSpace mColorSpace;
+ private final long mTimestamp;
+
+ /** @hide */
+ public ScreenshotResult(@NonNull HardwareBuffer hardwareBuffer,
+ @NonNull ColorSpace colorSpace, long timestamp) {
+ Preconditions.checkNotNull(hardwareBuffer, "hardwareBuffer cannot be null");
+ Preconditions.checkNotNull(colorSpace, "colorSpace cannot be null");
+ mHardwareBuffer = hardwareBuffer;
+ mColorSpace = colorSpace;
+ mTimestamp = timestamp;
+ }
+
+ /**
+ * Gets the {@link ColorSpace} identifying a specific organization of colors of the
+ * screenshot.
+ *
+ * @return the color space
+ */
+ @NonNull
+ public ColorSpace getColorSpace() {
+ return mColorSpace;
+ }
+
+ /**
+ * Gets the {@link HardwareBuffer} representing a memory buffer of the screenshot.
+ * <p>
+ * <strong>Note:</strong> The application should call {@link HardwareBuffer#close()} when
+ * the buffer is no longer needed to free the underlying resources.
+ * </p>
+ *
+ * @return the hardware buffer
+ */
+ @NonNull
+ public HardwareBuffer getHardwareBuffer() {
+ return mHardwareBuffer;
+ }
+
+ /**
+ * Gets the timestamp of taking the screenshot.
+ *
+ * @return milliseconds of non-sleep uptime before screenshot since boot and it's from
+ * {@link SystemClock#uptimeMillis()}
+ */
+ public long getTimestamp() {
+ return mTimestamp;
+ };
+ }
+
+ /**
+ * When {@link AccessibilityServiceInfo#FLAG_REQUEST_TOUCH_EXPLORATION_MODE} is enabled, this
+ * function requests that touch interactions starting in the specified region of the screen
+ * bypass the gesture detector. There can only be one gesture detection passthrough region per
+ * display. Requesting a new gesture detection passthrough region clears the existing one. To
+ * disable this passthrough and return to the original behavior, pass in an empty region. When
+ * {@link AccessibilityServiceInfo#FLAG_REQUEST_TOUCH_EXPLORATION_MODE} is disabled this
+ * function has no effect.
+ *
+ * @param displayId The display on which to set this region.
+ * @param region the region of the screen.
+ */
+ public void setGestureDetectionPassthroughRegion(int displayId, @NonNull Region region) {
+ Preconditions.checkNotNull(region, "region cannot be null");
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance(this).getConnection(mConnectionId);
+ if (connection != null) {
+ try {
+ connection.setGestureDetectionPassthroughRegion(displayId, region);
+ } catch (RemoteException re) {
+ throw new RuntimeException(re);
+ }
+ }
+ }
+
+ /**
+ * When {@link AccessibilityServiceInfo#FLAG_REQUEST_TOUCH_EXPLORATION_MODE} is enabled, this
+ * function requests that touch interactions starting in the specified region of the screen
+ * bypass the touch explorer and go straight to the view hierarchy. There can only be one touch
+ * exploration passthrough region per display. Requesting a new touch explorationpassthrough
+ * region clears the existing one. To disable this passthrough and return to the original
+ * behavior, pass in an empty region. When {@link
+ * AccessibilityServiceInfo#FLAG_REQUEST_TOUCH_EXPLORATION_MODE} is disabled this function has
+ * no effect.
+ *
+ * @param displayId The display on which to set this region.
+ * @param region the region of the screen .
+ */
+ public void setTouchExplorationPassthroughRegion(int displayId, @NonNull Region region) {
+ Preconditions.checkNotNull(region, "region cannot be null");
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance(this).getConnection(mConnectionId);
+ if (connection != null) {
+ try {
+ connection.setTouchExplorationPassthroughRegion(displayId, region);
+ } catch (RemoteException re) {
+ throw new RuntimeException(re);
+ }
+ }
+ }
+
+ /**
+ * Sets the system settings values that control the scaling factor for animations. The scale
+ * controls the animation playback speed for animations that respect these settings. Animations
+ * that do not respect the settings values will not be affected by this function. A lower scale
+ * value results in a faster speed. A value of <code>0</code> disables animations entirely. When
+ * animations are disabled services receive window change events more quickly which can reduce
+ * the potential by confusion by reducing the time during which windows are in transition.
+ *
+ * @see AccessibilityEvent#TYPE_WINDOWS_CHANGED
+ * @see AccessibilityEvent#TYPE_WINDOW_STATE_CHANGED
+ * @see android.provider.Settings.Global#WINDOW_ANIMATION_SCALE
+ * @see android.provider.Settings.Global#TRANSITION_ANIMATION_SCALE
+ * @see android.provider.Settings.Global#ANIMATOR_DURATION_SCALE
+ * @param scale The scaling factor for all animations.
+ */
+ public void setAnimationScale(float scale) {
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance(this).getConnection(mConnectionId);
+ if (connection != null) {
+ try {
+ connection.setAnimationScale(scale);
+ } catch (RemoteException re) {
+ throw new RuntimeException(re);
+ }
+ }
+ }
+
+ private static class AccessibilityContext extends ContextWrapper {
+ private final int mConnectionId;
+
+ private AccessibilityContext(Context base, int connectionId) {
+ super(base);
+ mConnectionId = connectionId;
+ setDefaultTokenInternal(this, getDisplayId());
+ }
+
+ @NonNull
+ @Override
+ public Context createDisplayContext(Display display) {
+ return new AccessibilityContext(super.createDisplayContext(display), mConnectionId);
+ }
+
+ @NonNull
+ @Override
+ public Context createWindowContext(int type, @Nullable Bundle options) {
+ final Context context = super.createWindowContext(type, options);
+ if (type != TYPE_ACCESSIBILITY_OVERLAY) {
+ return context;
+ }
+ return new AccessibilityContext(context, mConnectionId);
+ }
+
+ @NonNull
+ @Override
+ public Context createWindowContext(@NonNull Display display, int type,
+ @Nullable Bundle options) {
+ final Context context = super.createWindowContext(display, type, options);
+ if (type != TYPE_ACCESSIBILITY_OVERLAY) {
+ return context;
+ }
+ return new AccessibilityContext(context, mConnectionId);
+ }
+
+ private void setDefaultTokenInternal(Context context, int displayId) {
+ final WindowManagerImpl wm = (WindowManagerImpl) context.getSystemService(
+ WINDOW_SERVICE);
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getConnection(mConnectionId);
+ IBinder token = null;
+ if (connection != null) {
+ try {
+ token = connection.getOverlayWindowToken(displayId);
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Failed to get window token", re);
+ re.rethrowFromSystemServer();
+ }
+ wm.setDefaultToken(token);
+ }
+ }
+ }
+
+ /**
+ * Returns the touch interaction controller for the specified logical display, which may be used
+ * to detect gestures and otherwise control touch interactions. If
+ * {@link AccessibilityServiceInfo#FLAG_REQUEST_TOUCH_EXPLORATION_MODE} is disabled the
+ * controller's methods will have no effect.
+ *
+ * @param displayId The logical display id, use {@link Display#DEFAULT_DISPLAY} for default
+ * display.
+ * @return the TouchExploration controller
+ */
+ @NonNull
+ public final TouchInteractionController getTouchInteractionController(int displayId) {
+ synchronized (mLock) {
+ TouchInteractionController controller = mTouchInteractionControllers.get(displayId);
+ if (controller == null) {
+ controller = new TouchInteractionController(this, mLock, displayId);
+ mTouchInteractionControllers.put(displayId, controller);
+ }
+ return controller;
+ }
+ }
+
+ void sendMotionEventToCallback(MotionEvent event) {
+ boolean sendingTouchEventToTouchInteractionController = false;
+ if (event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN)) {
+ TouchInteractionController controller;
+ synchronized (mLock) {
+ int displayId = event.getDisplayId();
+ controller = mTouchInteractionControllers.get(displayId);
+ }
+ if (controller != null) {
+ sendingTouchEventToTouchInteractionController = true;
+ controller.onMotionEvent(event);
+ }
+ }
+ final int eventSourceWithoutClass = event.getSource() & ~InputDevice.SOURCE_CLASS_MASK;
+ if ((mMotionEventSources & eventSourceWithoutClass) != 0
+ && !sendingTouchEventToTouchInteractionController) {
+ onMotionEvent(event);
+ }
+ }
+
+ void onTouchStateChanged(int displayId, int state) {
+ TouchInteractionController controller;
+ synchronized (mLock) {
+ controller = mTouchInteractionControllers.get(displayId);
+ }
+ if (controller != null) {
+ controller.onStateChanged(state);
+ }
+ }
+
+ /**
+ * Attaches a {@link android.view.SurfaceControl} containing an accessibility overlay to the
+ * specified display. This type of overlay should be used for content that does not need to
+ * track the location and size of Views in the currently active app e.g. service configuration
+ * or general service UI.
+ *
+ * <p>Generally speaking, an accessibility overlay will be a {@link android.view.View}. To embed
+ * the View into a {@link android.view.SurfaceControl}, create a {@link
+ * android.view.SurfaceControlViewHost} and attach the View using {@link
+ * android.view.SurfaceControlViewHost#setView}. Then obtain the SurfaceControl by calling
+ * <code> viewHost.getSurfacePackage().getSurfaceControl()</code>.
+ *
+ * <p>To remove this overlay and free the associated resources, use <code>
+ * new SurfaceControl.Transaction().reparent(sc, null).apply();</code>.
+ *
+ * <p>If the specified overlay has already been attached to the specified display this method
+ * does nothing. If the specified overlay has already been attached to a previous display this
+ * function will transfer the overlay to the new display. Services can attach multiple overlays.
+ * Use <code> new SurfaceControl.Transaction().setLayer(sc, layer).apply();</code>. to
+ * coordinate the order of the overlays on screen.
+ *
+ * @param displayId the display to which the SurfaceControl should be attached.
+ * @param sc the SurfaceControl containing the overlay content
+ *
+ */
+ public void attachAccessibilityOverlayToDisplay(int displayId, @NonNull SurfaceControl sc) {
+ Preconditions.checkNotNull(sc, "SurfaceControl cannot be null");
+ AccessibilityInteractionClient.getInstance(this)
+ .attachAccessibilityOverlayToDisplay(mConnectionId, displayId, sc, null, null);
+ }
+
+ /**
+ * Attaches a {@link android.view.SurfaceControl} containing an accessibility overlay to the
+ * specified display. This type of overlay should be used for content that does not need to
+ * track the location and size of Views in the currently active app e.g. service configuration
+ * or general service UI.
+ *
+ * <p>Generally speaking, an accessibility overlay will be a {@link android.view.View}. To embed
+ * the View into a {@link android.view.SurfaceControl}, create a {@link
+ * android.view.SurfaceControlViewHost} and attach the View using {@link
+ * android.view.SurfaceControlViewHost#setView}. Then obtain the SurfaceControl by calling
+ * <code> viewHost.getSurfacePackage().getSurfaceControl()</code>.
+ *
+ * <p>To remove this overlay and free the associated resources, use <code>
+ * new SurfaceControl.Transaction().reparent(sc, null).apply();</code>.
+ *
+ * <p>If the specified overlay has already been attached to the specified display this method
+ * does nothing. If the specified overlay has already been attached to a previous display this
+ * function will transfer the overlay to the new display. Services can attach multiple overlays.
+ * Use <code> new SurfaceControl.Transaction().setLayer(sc, layer).apply();</code>. to
+ * coordinate the order of the overlays on screen.
+ *
+ * @param displayId the display to which the SurfaceControl should be attached.
+ * @param sc the SurfaceControl containing the overlay content
+ * @param executor Executor on which to run the callback.
+ * @param callback The callback invoked when attaching the overlay has succeeded or failed. The
+ * callback is a {@link java.util.function.IntConsumer} of the result status code.
+ * @see #OVERLAY_RESULT_SUCCESS
+ * @see #OVERLAY_RESULT_INVALID
+ * @see #OVERLAY_RESULT_INTERNAL_ERROR
+ */
+ @FlaggedApi(android.view.accessibility.Flags.FLAG_A11Y_OVERLAY_CALLBACKS)
+ public final void attachAccessibilityOverlayToDisplay(
+ int displayId,
+ @NonNull SurfaceControl sc,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull IntConsumer callback) {
+ Preconditions.checkNotNull(sc, "SurfaceControl cannot be null");
+ AccessibilityInteractionClient.getInstance(this)
+ .attachAccessibilityOverlayToDisplay(
+ mConnectionId, displayId, sc, executor, callback);
+ }
+
+ /**
+ * Attaches an accessibility overlay {@link android.view.SurfaceControl} to the specified
+ * window. This method should be used when you want the overlay to move and resize as the parent
+ * window moves and resizes.
+ *
+ * <p>Generally speaking, an accessibility overlay will be a {@link android.view.View}. To embed
+ * the View into a {@link android.view.SurfaceControl}, create a {@link
+ * android.view.SurfaceControlViewHost} and attach the View using {@link
+ * android.view.SurfaceControlViewHost#setView}. Then obtain the SurfaceControl by calling
+ * <code> viewHost.getSurfacePackage().getSurfaceControl()</code>.
+ *
+ * <p>To remove this overlay and free the associated resources, use <code>
+ * new SurfaceControl.Transaction().reparent(sc, null).apply();</code>.
+ *
+ * <p>If the specified overlay has already been attached to the specified window this method
+ * does nothing. If the specified overlay has already been attached to a previous window this
+ * function will transfer the overlay to the new window. Services can attach multiple overlays.
+ * Use <code> new SurfaceControl.Transaction().setLayer(sc, layer).apply();</code>. to
+ * coordinate the order of the overlays on screen.
+ *
+ * @param accessibilityWindowId The window id, from {@link AccessibilityWindowInfo#getId()}.
+ * @param sc the SurfaceControl containing the overlay content
+ *
+ */
+ public void attachAccessibilityOverlayToWindow(
+ int accessibilityWindowId, @NonNull SurfaceControl sc) {
+ Preconditions.checkNotNull(sc, "SurfaceControl cannot be null");
+ AccessibilityInteractionClient.getInstance(this)
+ .attachAccessibilityOverlayToWindow(
+ mConnectionId, accessibilityWindowId, sc, null, null);
+ }
+
+ /**
+ * Attaches an accessibility overlay {@link android.view.SurfaceControl} to the specified
+ * window. This method should be used when you want the overlay to move and resize as the parent
+ * window moves and resizes.
+ *
+ * <p>Generally speaking, an accessibility overlay will be a {@link android.view.View}. To embed
+ * the View into a {@link android.view.SurfaceControl}, create a {@link
+ * android.view.SurfaceControlViewHost} and attach the View using {@link
+ * android.view.SurfaceControlViewHost#setView}. Then obtain the SurfaceControl by calling
+ * <code> viewHost.getSurfacePackage().getSurfaceControl()</code>.
+ *
+ * <p>To remove this overlay and free the associated resources, use <code>
+ * new SurfaceControl.Transaction().reparent(sc, null).apply();</code>.
+ *
+ * <p>If the specified overlay has already been attached to the specified window this method
+ * does nothing. If the specified overlay has already been attached to a previous window this
+ * function will transfer the overlay to the new window. Services can attach multiple overlays.
+ * Use <code> new SurfaceControl.Transaction().setLayer(sc, layer).apply();</code>. to
+ * coordinate the order of the overlays on screen.
+ *
+ * @param accessibilityWindowId The window id, from {@link AccessibilityWindowInfo#getId()}.
+ * @param sc the SurfaceControl containing the overlay content
+ * @param executor Executor on which to run the callback.
+ * @param callback The callback invoked when attaching the overlay has succeeded or failed. The
+ * callback is a {@link java.util.function.IntConsumer} of the result status code.
+ * @see #OVERLAY_RESULT_SUCCESS
+ * @see #OVERLAY_RESULT_INVALID
+ * @see #OVERLAY_RESULT_INTERNAL_ERROR
+ */
+ @FlaggedApi(android.view.accessibility.Flags.FLAG_A11Y_OVERLAY_CALLBACKS)
+ public final void attachAccessibilityOverlayToWindow(
+ int accessibilityWindowId,
+ @NonNull SurfaceControl sc,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull IntConsumer callback) {
+ Preconditions.checkNotNull(sc, "SurfaceControl cannot be null");
+ AccessibilityInteractionClient.getInstance(this)
+ .attachAccessibilityOverlayToWindow(
+ mConnectionId, accessibilityWindowId, sc, executor, callback);
+ }
+
+ /**
+ * Returns the {@link BrailleDisplayController} which may be used to communicate with
+ * refreshable Braille displays that provide USB or Bluetooth Braille display HID support.
+ */
+ @FlaggedApi(android.view.accessibility.Flags.FLAG_BRAILLE_DISPLAY_HID)
+ @NonNull
+ public final BrailleDisplayController getBrailleDisplayController() {
+ BrailleDisplayController.checkApiFlagIsEnabled();
+ synchronized (mLock) {
+ if (mBrailleDisplayController == null) {
+ mBrailleDisplayController = new BrailleDisplayControllerImpl(this, mLock);
+ }
+ return mBrailleDisplayController;
+ }
+ }
+}
diff --git a/android-35/android/accessibilityservice/AccessibilityServiceInfo.java b/android-35/android/accessibilityservice/AccessibilityServiceInfo.java
new file mode 100644
index 0000000..8bb2857
--- /dev/null
+++ b/android-35/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -0,0 +1,1790 @@
+/*
+ * Copyright (C) 2009 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.accessibilityservice;
+
+import static android.accessibilityservice.util.AccessibilityUtils.getFilteredHtmlText;
+import static android.accessibilityservice.util.AccessibilityUtils.loadSafeAnimatedImage;
+import static android.content.pm.PackageManager.FEATURE_FINGERPRINT;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.graphics.drawable.Drawable;
+import android.hardware.fingerprint.FingerprintManager;
+import android.os.Build;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.util.AttributeSet;
+import android.util.SparseArray;
+import android.util.TypedValue;
+import android.util.Xml;
+import android.view.InputDevice;
+import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.Flags;
+
+import com.android.internal.R;
+import com.android.internal.compat.IPlatformCompat;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * This class describes an {@link AccessibilityService}. The system notifies an
+ * {@link AccessibilityService} for {@link android.view.accessibility.AccessibilityEvent}s
+ * according to the information encapsulated in this class.
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For more information about creating AccessibilityServices, read the
+ * <a href="{@docRoot}guide/topics/ui/accessibility/index.html">Accessibility</a>
+ * developer guide.</p>
+ * </div>
+ *
+ * @attr ref android.R.styleable#AccessibilityService_accessibilityEventTypes
+ * @attr ref android.R.styleable#AccessibilityService_accessibilityFeedbackType
+ * @attr ref android.R.styleable#AccessibilityService_accessibilityFlags
+ * @attr ref android.R.styleable#AccessibilityService_canRequestFilterKeyEvents
+ * @attr ref android.R.styleable#AccessibilityService_canRequestTouchExplorationMode
+ * @attr ref android.R.styleable#AccessibilityService_canRetrieveWindowContent
+ * @attr ref android.R.styleable#AccessibilityService_intro
+ * @attr ref android.R.styleable#AccessibilityService_description
+ * @attr ref android.R.styleable#AccessibilityService_summary
+ * @attr ref android.R.styleable#AccessibilityService_notificationTimeout
+ * @attr ref android.R.styleable#AccessibilityService_packageNames
+ * @attr ref android.R.styleable#AccessibilityService_settingsActivity
+ * @attr ref android.R.styleable#AccessibilityService_tileService
+ * @attr ref android.R.styleable#AccessibilityService_nonInteractiveUiTimeout
+ * @attr ref android.R.styleable#AccessibilityService_interactiveUiTimeout
+ * @attr ref android.R.styleable#AccessibilityService_canTakeScreenshot
+ * @see AccessibilityService
+ * @see android.view.accessibility.AccessibilityEvent
+ * @see android.view.accessibility.AccessibilityManager
+ */
+public class AccessibilityServiceInfo implements Parcelable {
+
+ private static final String TAG_ACCESSIBILITY_SERVICE = "accessibility-service";
+
+ /**
+ * Capability: This accessibility service can retrieve the active window content.
+ * @see android.R.styleable#AccessibilityService_canRetrieveWindowContent
+ */
+ public static final int CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT = 1 /* << 0 */;
+
+ /**
+ * Capability: This accessibility service can request touch exploration mode in which
+ * touched items are spoken aloud and the UI can be explored via gestures.
+ * @see android.R.styleable#AccessibilityService_canRequestTouchExplorationMode
+ */
+ public static final int CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION = 1 << 1;
+
+ /**
+ * @deprecated No longer used
+ */
+ @Deprecated
+ public static final int CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY = 1 << 2;
+
+ /**
+ * Capability: This accessibility service can request to filter the key event stream.
+ * @see android.R.styleable#AccessibilityService_canRequestFilterKeyEvents
+ */
+ public static final int CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS = 1 << 3;
+
+ /**
+ * Capability: This accessibility service can control display magnification.
+ * @see android.R.styleable#AccessibilityService_canControlMagnification
+ */
+ public static final int CAPABILITY_CAN_CONTROL_MAGNIFICATION = 1 << 4;
+
+ /**
+ * Capability: This accessibility service can perform gestures.
+ * @see android.R.styleable#AccessibilityService_canPerformGestures
+ */
+ public static final int CAPABILITY_CAN_PERFORM_GESTURES = 1 << 5;
+
+ /**
+ * Capability: This accessibility service can capture gestures from the fingerprint sensor
+ * @see android.R.styleable#AccessibilityService_canRequestFingerprintGestures
+ */
+ public static final int CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES = 1 << 6;
+
+ /**
+ * Capability: This accessibility service can take screenshot.
+ * @see android.R.styleable#AccessibilityService_canTakeScreenshot
+ */
+ public static final int CAPABILITY_CAN_TAKE_SCREENSHOT = 1 << 7;
+
+ private static SparseArray<CapabilityInfo> sAvailableCapabilityInfos;
+
+ /**
+ * Denotes spoken feedback.
+ */
+ public static final int FEEDBACK_SPOKEN = 1 /* << 0 */;
+
+ /**
+ * Denotes haptic feedback.
+ */
+ public static final int FEEDBACK_HAPTIC = 1 << 1;
+
+ /**
+ * Denotes audible (not spoken) feedback.
+ */
+ public static final int FEEDBACK_AUDIBLE = 1 << 2;
+
+ /**
+ * Denotes visual feedback.
+ */
+ public static final int FEEDBACK_VISUAL = 1 << 3;
+
+ /**
+ * Denotes generic feedback.
+ */
+ public static final int FEEDBACK_GENERIC = 1 << 4;
+
+ /**
+ * Denotes braille feedback.
+ */
+ public static final int FEEDBACK_BRAILLE = 1 << 5;
+
+ /**
+ * Mask for all feedback types.
+ *
+ * @see #FEEDBACK_SPOKEN
+ * @see #FEEDBACK_HAPTIC
+ * @see #FEEDBACK_AUDIBLE
+ * @see #FEEDBACK_VISUAL
+ * @see #FEEDBACK_GENERIC
+ * @see #FEEDBACK_BRAILLE
+ */
+ public static final int FEEDBACK_ALL_MASK = 0xFFFFFFFF;
+
+ /**
+ * If an {@link AccessibilityService} is the default for a given type.
+ * Default service is invoked only if no package specific one exists. In case of
+ * more than one package specific service only the earlier registered is notified.
+ */
+ public static final int DEFAULT = 1 /* << 0 */;
+
+ /**
+ * If this flag is set the system will regard views that are not important
+ * for accessibility in addition to the ones that are important for accessibility.
+ * That is, views that are marked as not important for accessibility via
+ * {@link View#IMPORTANT_FOR_ACCESSIBILITY_NO} or
+ * {@link View#IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS} and views that are
+ * marked as potentially important for accessibility via
+ * {@link View#IMPORTANT_FOR_ACCESSIBILITY_AUTO} for which the system has determined
+ * that are not important for accessibility, are reported while querying the window
+ * content and also the accessibility service will receive accessibility events from
+ * them.
+ * <p>
+ * <strong>Note:</strong> For accessibility services targeting Android 4.1 (API level 16) or
+ * higher, this flag has to be explicitly set for the system to regard views that are not
+ * important for accessibility. For accessibility services targeting Android 4.0.4 (API level
+ * 15) or lower, this flag is ignored and all views are regarded for accessibility purposes.
+ * </p>
+ * <p>
+ * Usually views not important for accessibility are layout managers that do not
+ * react to user actions, do not draw any content, and do not have any special
+ * semantics in the context of the screen content. For example, a three by three
+ * grid can be implemented as three horizontal linear layouts and one vertical,
+ * or three vertical linear layouts and one horizontal, or one grid layout, etc.
+ * In this context, the actual layout managers used to achieve the grid configuration
+ * are not important; rather it is important that there are nine evenly distributed
+ * elements.
+ * </p>
+ */
+ public static final int FLAG_INCLUDE_NOT_IMPORTANT_VIEWS = 1 << 1;
+
+ /**
+ * This flag requests that the system gets into touch exploration mode.
+ * In this mode a single finger moving on the screen behaves as a mouse
+ * pointer hovering over the user interface. The system will also detect
+ * certain gestures performed on the touch screen and notify this service.
+ * The system will enable touch exploration mode if there is at least one
+ * accessibility service that has this flag set. Hence, clearing this
+ * flag does not guarantee that the device will not be in touch exploration
+ * mode since there may be another enabled service that requested it.
+ * <p>
+ * For accessibility services targeting Android 4.3 (API level 18) or higher
+ * that want to set this flag have to declare this capability in their
+ * meta-data by setting the attribute
+ * {@link android.R.attr#canRequestTouchExplorationMode
+ * canRequestTouchExplorationMode} to true. Otherwise, this flag will
+ * be ignored. For how to declare the meta-data of a service refer to
+ * {@value AccessibilityService#SERVICE_META_DATA}.
+ * </p>
+ * <p>
+ * Services targeting Android 4.2.2 (API level 17) or lower will work
+ * normally. In other words, the first time they are run, if this flag is
+ * specified, a dialog is shown to the user to confirm enabling explore by
+ * touch.
+ * </p>
+ * @see android.R.styleable#AccessibilityService_canRequestTouchExplorationMode
+ */
+ public static final int FLAG_REQUEST_TOUCH_EXPLORATION_MODE = 1 << 2;
+
+ /**
+ * @deprecated No longer used
+ */
+ @Deprecated
+ public static final int FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY = 1 << 3;
+
+ /**
+ * This flag requests that the {@link AccessibilityNodeInfo}s obtained
+ * by an {@link AccessibilityService} contain the id of the source view.
+ * The source view id will be a fully qualified resource name of the
+ * form "package:id/name", for example "foo.bar:id/my_list", and it is
+ * useful for UI test automation. This flag is not set by default.
+ */
+ public static final int FLAG_REPORT_VIEW_IDS = 1 << 4;
+
+ /**
+ * This flag requests from the system to filter key events. If this flag
+ * is set the accessibility service will receive the key events before
+ * applications allowing it implement global shortcuts.
+ * <p>
+ * Services that want to set this flag have to declare this capability
+ * in their meta-data by setting the attribute {@link android.R.attr
+ * #canRequestFilterKeyEvents canRequestFilterKeyEvents} to true,
+ * otherwise this flag will be ignored. For how to declare the meta-data
+ * of a service refer to {@value AccessibilityService#SERVICE_META_DATA}.
+ * </p>
+ * @see android.R.styleable#AccessibilityService_canRequestFilterKeyEvents
+ */
+ public static final int FLAG_REQUEST_FILTER_KEY_EVENTS = 1 << 5;
+
+ /**
+ * This flag indicates to the system that the accessibility service wants
+ * to access content of all interactive windows. An interactive window is a
+ * window that has input focus or can be touched by a sighted user when explore
+ * by touch is not enabled. If this flag is not set your service will not receive
+ * {@link android.view.accessibility.AccessibilityEvent#TYPE_WINDOWS_CHANGED}
+ * events, calling AccessibilityService{@link AccessibilityService#getWindows()
+ * AccessibilityService.getWindows()} will return an empty list, and {@link
+ * AccessibilityNodeInfo#getWindow() AccessibilityNodeInfo.getWindow()} will
+ * return null.
+ * <p>
+ * Services that want to set this flag have to declare the capability
+ * to retrieve window content in their meta-data by setting the attribute
+ * {@link android.R.attr#canRetrieveWindowContent canRetrieveWindowContent} to
+ * true, otherwise this flag will be ignored. For how to declare the meta-data
+ * of a service refer to {@value AccessibilityService#SERVICE_META_DATA}.
+ * </p>
+ * @see android.R.styleable#AccessibilityService_canRetrieveWindowContent
+ */
+ public static final int FLAG_RETRIEVE_INTERACTIVE_WINDOWS = 1 << 6;
+
+ /**
+ * This flag requests that all audio tracks system-wide with
+ * {@link android.media.AudioAttributes#USAGE_ASSISTANCE_ACCESSIBILITY} be controlled by the
+ * {@link android.media.AudioManager#STREAM_ACCESSIBILITY} volume.
+ */
+ public static final int FLAG_ENABLE_ACCESSIBILITY_VOLUME = 1 << 7;
+
+ /**
+ * This flag indicates to the system that the accessibility service requests that an
+ * accessibility button be shown within the system's navigation area, if available.
+ * <p>
+ * <strong>Note:</strong> For accessibility services targeting APIs greater than
+ * {@link Build.VERSION_CODES#Q API 29}, this flag must be specified in the
+ * accessibility service metadata file. Otherwise, it will be ignored.
+ * </p>
+ */
+ public static final int FLAG_REQUEST_ACCESSIBILITY_BUTTON = 1 << 8;
+
+ /**
+ * This flag requests that all fingerprint gestures be sent to the accessibility service.
+ * <p>
+ * Services that want to set this flag have to declare the capability
+ * to retrieve window content in their meta-data by setting the attribute
+ * {@link android.R.attr#canRequestFingerprintGestures} to
+ * true, otherwise this flag will be ignored. For how to declare the meta-data
+ * of a service refer to {@value AccessibilityService#SERVICE_META_DATA}.
+ * </p>
+ *
+ * @see android.R.styleable#AccessibilityService_canRequestFingerprintGestures
+ * @see AccessibilityService#getFingerprintGestureController()
+ */
+ public static final int FLAG_REQUEST_FINGERPRINT_GESTURES = 1 << 9;
+
+ /**
+ * This flag requests that accessibility shortcut warning dialog has spoken feedback when
+ * dialog is shown.
+ */
+ public static final int FLAG_REQUEST_SHORTCUT_WARNING_DIALOG_SPOKEN_FEEDBACK = 1 << 10;
+
+ /**
+ * This flag requests that when {@link #FLAG_REQUEST_TOUCH_EXPLORATION_MODE} is enabled,
+ * double tap and double tap and hold gestures are dispatched to the service rather than being
+ * handled by the framework. If {@link #FLAG_REQUEST_TOUCH_EXPLORATION_MODE} is disabled this
+ * flag has no effect.
+ *
+ * @see #FLAG_REQUEST_TOUCH_EXPLORATION_MODE
+ */
+ public static final int FLAG_SERVICE_HANDLES_DOUBLE_TAP = 1 << 11;
+
+ /**
+ * This flag requests that when when {@link #FLAG_REQUEST_TOUCH_EXPLORATION_MODE} is enabled,
+ * multi-finger gestures are also enabled. As a consequence, two-finger bypass gestures will be
+ * disabled. If {@link #FLAG_REQUEST_TOUCH_EXPLORATION_MODE} is disabled this flag has no
+ * effect.
+ *
+ * @see #FLAG_REQUEST_TOUCH_EXPLORATION_MODE
+ */
+ public static final int FLAG_REQUEST_MULTI_FINGER_GESTURES = 1 << 12;
+
+ /**
+ * This flag requests that when when {@link #FLAG_REQUEST_MULTI_FINGER_GESTURES} is enabled,
+ * two-finger passthrough gestures are re-enabled. Two-finger swipe gestures are not detected,
+ * but instead passed through as one-finger gestures. In addition, three-finger swipes from the
+ * bottom of the screen are not detected, and instead are passed through unchanged. If {@link
+ * #FLAG_REQUEST_MULTI_FINGER_GESTURES} is disabled this flag has no effect.
+ *
+ * @see #FLAG_REQUEST_TOUCH_EXPLORATION_MODE
+ */
+ public static final int FLAG_REQUEST_2_FINGER_PASSTHROUGH = 1 << 13;
+
+ /**
+ * This flag requests that when when {@link #FLAG_REQUEST_TOUCH_EXPLORATION_MODE} is enabled, a
+ * service will receive the motion events for each successfully-detected gesture. The service
+ * will also receive an AccessibilityGestureEvent of type GESTURE_INVALID for each cancelled
+ * gesture along with its motion events. A service will receive a gesture of type
+ * GESTURE_PASSTHROUGH and accompanying motion events for every passthrough gesture that does
+ * not start gesture detection. This information can be used to collect instances of improper
+ * gesture detection behavior and relay that information to framework developers. If {@link
+ * #FLAG_REQUEST_TOUCH_EXPLORATION_MODE} is disabled this flag has no effect.
+ *
+ * @see #FLAG_REQUEST_TOUCH_EXPLORATION_MODE
+ */
+ public static final int FLAG_SEND_MOTION_EVENTS = 1 << 14;
+
+ /**
+ * This flag makes the AccessibilityService an input method editor with a subset of input
+ * method editor capabilities: get the {@link android.view.inputmethod.InputConnection} and get
+ * text selection change notifications.
+ *
+ * @see AccessibilityService#getInputMethod()
+ */
+ public static final int FLAG_INPUT_METHOD_EDITOR = 1 << 15;
+
+ /** {@hide} */
+ public static final int FLAG_FORCE_DIRECT_BOOT_AWARE = 1 << 16;
+
+ /**
+ * The event types an {@link AccessibilityService} is interested in.
+ * <p>
+ * <strong>Can be dynamically set at runtime.</strong>
+ * </p>
+ * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_CLICKED
+ * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_LONG_CLICKED
+ * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_FOCUSED
+ * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_SELECTED
+ * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_TEXT_CHANGED
+ * @see android.view.accessibility.AccessibilityEvent#TYPE_WINDOW_STATE_CHANGED
+ * @see android.view.accessibility.AccessibilityEvent#TYPE_NOTIFICATION_STATE_CHANGED
+ * @see android.view.accessibility.AccessibilityEvent#TYPE_TOUCH_EXPLORATION_GESTURE_START
+ * @see android.view.accessibility.AccessibilityEvent#TYPE_TOUCH_EXPLORATION_GESTURE_END
+ * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_HOVER_ENTER
+ * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_HOVER_EXIT
+ * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_SCROLLED
+ * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_TEXT_SELECTION_CHANGED
+ * @see android.view.accessibility.AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED
+ * @see android.view.accessibility.AccessibilityEvent#TYPE_TOUCH_INTERACTION_START
+ * @see android.view.accessibility.AccessibilityEvent#TYPE_TOUCH_INTERACTION_END
+ * @see android.view.accessibility.AccessibilityEvent#TYPE_ANNOUNCEMENT
+ * @see android.view.accessibility.AccessibilityEvent#TYPE_GESTURE_DETECTION_START
+ * @see android.view.accessibility.AccessibilityEvent#TYPE_GESTURE_DETECTION_END
+ * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_ACCESSIBILITY_FOCUSED
+ * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED
+ * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+ * @see android.view.accessibility.AccessibilityEvent#TYPE_WINDOWS_CHANGED
+ */
+ public int eventTypes;
+
+ /**
+ * The package names an {@link AccessibilityService} is interested in. Setting
+ * to <code>null</code> is equivalent to all packages.
+ * <p>
+ * <strong>Can be dynamically set at runtime.</strong>
+ * </p>
+ */
+ public String[] packageNames;
+
+
+ /** @hide */
+ @IntDef(flag = true, prefix = { "FEEDBACK_" }, value = {
+ FEEDBACK_AUDIBLE,
+ FEEDBACK_GENERIC,
+ FEEDBACK_HAPTIC,
+ FEEDBACK_SPOKEN,
+ FEEDBACK_VISUAL,
+ FEEDBACK_BRAILLE
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface FeedbackType {}
+
+ /**
+ * The feedback type an {@link AccessibilityService} provides.
+ * <p>
+ * <strong>Can be dynamically set at runtime.</strong>
+ * </p>
+ * @see #FEEDBACK_AUDIBLE
+ * @see #FEEDBACK_GENERIC
+ * @see #FEEDBACK_HAPTIC
+ * @see #FEEDBACK_SPOKEN
+ * @see #FEEDBACK_VISUAL
+ * @see #FEEDBACK_BRAILLE
+ */
+ @FeedbackType
+ public int feedbackType;
+
+ /**
+ * The timeout, in milliseconds, after the most recent event of a given type before an
+ * {@link AccessibilityService} is notified.
+ * <p>
+ * <strong>Can be dynamically set at runtime.</strong>
+ * </p>
+ * <p>
+ * <strong>Note:</strong> The event notification timeout is useful to avoid propagating
+ * events to the client too frequently since this is accomplished via an expensive
+ * interprocess call. One can think of the timeout as a criteria to determine when
+ * event generation has settled down.
+ */
+ public long notificationTimeout;
+
+ /**
+ * This field represents a set of flags used for configuring an
+ * {@link AccessibilityService}.
+ * <p>
+ * <strong>Can be dynamically set at runtime.</strong>
+ * </p>
+ * <p>
+ * <strong>Note:</strong> Accessibility services with targetSdkVersion greater than
+ * {@link Build.VERSION_CODES#Q API 29} cannot dynamically set the
+ * {@link #FLAG_REQUEST_ACCESSIBILITY_BUTTON} at runtime. It must be specified in the
+ * accessibility service metadata file.
+ * </p>
+ * @see #DEFAULT
+ * @see #FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
+ * @see #FLAG_REQUEST_TOUCH_EXPLORATION_MODE
+ * @see #FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY
+ * @see #FLAG_REQUEST_FILTER_KEY_EVENTS
+ * @see #FLAG_REPORT_VIEW_IDS
+ * @see #FLAG_RETRIEVE_INTERACTIVE_WINDOWS
+ * @see #FLAG_ENABLE_ACCESSIBILITY_VOLUME
+ * @see #FLAG_REQUEST_ACCESSIBILITY_BUTTON
+ * @see #FLAG_REQUEST_SHORTCUT_WARNING_DIALOG_SPOKEN_FEEDBACK
+ * @see #FLAG_INPUT_METHOD_EDITOR
+ */
+ public int flags;
+
+ /**
+ * Whether or not the service has crashed and is awaiting restart. Only valid from {@link
+ * android.view.accessibility.AccessibilityManager#getInstalledAccessibilityServiceList()},
+ * because that is populated from the internal list of running services.
+ *
+ * @hide
+ */
+ public boolean crashed;
+
+ /**
+ * A recommended timeout in milliseconds for non-interactive controls.
+ */
+ private int mNonInteractiveUiTimeout;
+
+ /**
+ * A recommended timeout in milliseconds for interactive controls.
+ */
+ private int mInteractiveUiTimeout;
+
+ /**
+ * The component name the accessibility service.
+ */
+ @NonNull
+ private ComponentName mComponentName;
+
+ /**
+ * The Service that implements this accessibility service component.
+ */
+ private ResolveInfo mResolveInfo;
+
+ /**
+ * The accessibility service setting activity's name, used by the system
+ * settings to launch the setting activity of this accessibility service.
+ */
+ private String mSettingsActivityName;
+
+ /**
+ * The name of {@link android.service.quicksettings.TileService} is associated with this
+ * accessibility service for one to one mapping. It is used by system settings to remind users
+ * this accessibility service has a {@link android.service.quicksettings.TileService}.
+ */
+ private String mTileServiceName;
+
+ /**
+ * Bit mask with capabilities of this service.
+ */
+ private int mCapabilities;
+
+ /**
+ * Resource id of the summary of the accessibility service.
+ */
+ private int mSummaryResId;
+
+ /**
+ * Non-localized summary of the accessibility service.
+ */
+ private String mNonLocalizedSummary;
+
+ /**
+ * Resource id of the intro of the accessibility service.
+ */
+ private int mIntroResId;
+
+ /**
+ * Resource id of the description of the accessibility service.
+ */
+ private int mDescriptionResId;
+
+ /**
+ * Non localized description of the accessibility service.
+ */
+ private String mNonLocalizedDescription;
+
+ /**
+ * For accessibility services targeting APIs greater than {@link Build.VERSION_CODES#Q API 29},
+ * {@link #FLAG_REQUEST_ACCESSIBILITY_BUTTON} must be specified in the accessibility service
+ * metadata file. Otherwise, it will be ignored.
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = android.os.Build.VERSION_CODES.Q)
+ private static final long REQUEST_ACCESSIBILITY_BUTTON_CHANGE = 136293963L;
+
+ /**
+ * Resource id of the animated image of the accessibility service.
+ */
+ private int mAnimatedImageRes;
+
+ /**
+ * Resource id of the html description of the accessibility service.
+ */
+ private int mHtmlDescriptionRes;
+
+ /**
+ * Whether the service is for accessibility.
+ *
+ * @hide
+ */
+ private boolean mIsAccessibilityTool = false;
+
+ /**
+ * {@link InputDevice} sources which may send {@link android.view.MotionEvent}s.
+ * @see #setMotionEventSources(int)
+ * @hide
+ */
+ @IntDef(flag = true, prefix = { "SOURCE_" }, value = {
+ InputDevice.SOURCE_MOUSE,
+ InputDevice.SOURCE_STYLUS,
+ InputDevice.SOURCE_BLUETOOTH_STYLUS,
+ InputDevice.SOURCE_TRACKBALL,
+ InputDevice.SOURCE_MOUSE_RELATIVE,
+ InputDevice.SOURCE_TOUCHPAD,
+ InputDevice.SOURCE_TOUCH_NAVIGATION,
+ InputDevice.SOURCE_ROTARY_ENCODER,
+ InputDevice.SOURCE_JOYSTICK,
+ InputDevice.SOURCE_SENSOR,
+ InputDevice.SOURCE_TOUCHSCREEN
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface MotionEventSources {}
+
+ /**
+ * The bit mask of {@link android.view.InputDevice} sources that the accessibility
+ * service wants to listen to for generic {@link android.view.MotionEvent}s.
+ */
+ @MotionEventSources
+ private int mMotionEventSources = 0;
+
+ private int mObservedMotionEventSources = 0;
+
+ // Default values for each dynamic property
+ // LINT.IfChange(dynamic_property_defaults)
+ private final DynamicPropertyDefaults mDynamicPropertyDefaults;
+
+ private static class DynamicPropertyDefaults {
+ private final int mEventTypesDefault;
+ private final List<String> mPackageNamesDefault;
+ private final int mFeedbackTypeDefault;
+ private final long mNotificationTimeoutDefault;
+ private final int mFlagsDefault;
+ private final int mNonInteractiveUiTimeoutDefault;
+ private final int mInteractiveUiTimeoutDefault;
+ private final int mMotionEventSourcesDefault;
+ private final int mObservedMotionEventSourcesDefault;
+
+ DynamicPropertyDefaults(AccessibilityServiceInfo info) {
+ mEventTypesDefault = info.eventTypes;
+ if (info.packageNames != null) {
+ mPackageNamesDefault = List.of(info.packageNames);
+ } else {
+ mPackageNamesDefault = null;
+ }
+ mFeedbackTypeDefault = info.feedbackType;
+ mNotificationTimeoutDefault = info.notificationTimeout;
+ mNonInteractiveUiTimeoutDefault = info.mNonInteractiveUiTimeout;
+ mInteractiveUiTimeoutDefault = info.mInteractiveUiTimeout;
+ mFlagsDefault = info.flags;
+ mMotionEventSourcesDefault = info.mMotionEventSources;
+ mObservedMotionEventSourcesDefault = info.mObservedMotionEventSources;
+ }
+ }
+ // LINT.ThenChange(:dynamic_property_reset)
+
+ /**
+ * Creates a new instance.
+ */
+ public AccessibilityServiceInfo() {
+ mDynamicPropertyDefaults = new DynamicPropertyDefaults(this);
+ }
+
+ /**
+ * Creates a new instance.
+ *
+ * @param resolveInfo The service resolve info.
+ * @param context Context for accessing resources.
+ * @throws XmlPullParserException If a XML parsing error occurs.
+ * @throws IOException If a XML parsing error occurs.
+ *
+ * @hide
+ */
+ public AccessibilityServiceInfo(ResolveInfo resolveInfo, Context context)
+ throws XmlPullParserException, IOException {
+ ServiceInfo serviceInfo = resolveInfo.serviceInfo;
+ mComponentName = new ComponentName(serviceInfo.packageName, serviceInfo.name);
+ mResolveInfo = resolveInfo;
+
+ XmlResourceParser parser = null;
+
+ try {
+ PackageManager packageManager = context.getPackageManager();
+ parser = serviceInfo.loadXmlMetaData(packageManager,
+ AccessibilityService.SERVICE_META_DATA);
+ if (parser == null) {
+ return;
+ }
+
+ int type = 0;
+ while (type != XmlPullParser.END_DOCUMENT && type != XmlPullParser.START_TAG) {
+ type = parser.next();
+ }
+
+ String nodeName = parser.getName();
+ if (!TAG_ACCESSIBILITY_SERVICE.equals(nodeName)) {
+ throw new XmlPullParserException( "Meta-data does not start with"
+ + TAG_ACCESSIBILITY_SERVICE + " tag");
+ }
+
+ AttributeSet allAttributes = Xml.asAttributeSet(parser);
+ Resources resources = packageManager.getResourcesForApplication(
+ serviceInfo.applicationInfo);
+ TypedArray asAttributes = resources.obtainAttributes(allAttributes,
+ com.android.internal.R.styleable.AccessibilityService);
+ eventTypes = asAttributes.getInt(
+ com.android.internal.R.styleable.AccessibilityService_accessibilityEventTypes,
+ 0);
+ String packageNamez = asAttributes.getString(
+ com.android.internal.R.styleable.AccessibilityService_packageNames);
+ if (packageNamez != null) {
+ packageNames = packageNamez.split("(\\s)*,(\\s)*");
+ }
+ feedbackType = asAttributes.getInt(
+ com.android.internal.R.styleable.AccessibilityService_accessibilityFeedbackType,
+ 0);
+ notificationTimeout = asAttributes.getInt(
+ com.android.internal.R.styleable.AccessibilityService_notificationTimeout,
+ 0);
+ mNonInteractiveUiTimeout = asAttributes.getInt(
+ com.android.internal.R.styleable.AccessibilityService_nonInteractiveUiTimeout,
+ 0);
+ mInteractiveUiTimeout = asAttributes.getInt(
+ com.android.internal.R.styleable.AccessibilityService_interactiveUiTimeout,
+ 0);
+ flags = asAttributes.getInt(
+ com.android.internal.R.styleable.AccessibilityService_accessibilityFlags, 0);
+ mSettingsActivityName = asAttributes.getString(
+ com.android.internal.R.styleable.AccessibilityService_settingsActivity);
+ if (asAttributes.getBoolean(com.android.internal.R.styleable
+ .AccessibilityService_canRetrieveWindowContent, false)) {
+ mCapabilities |= CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT;
+ }
+ if (asAttributes.getBoolean(com.android.internal.R.styleable
+ .AccessibilityService_canRequestTouchExplorationMode, false)) {
+ mCapabilities |= CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION;
+ }
+ if (asAttributes.getBoolean(com.android.internal.R.styleable
+ .AccessibilityService_canRequestFilterKeyEvents, false)) {
+ mCapabilities |= CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS;
+ }
+ if (asAttributes.getBoolean(com.android.internal.R.styleable
+ .AccessibilityService_canControlMagnification, false)) {
+ mCapabilities |= CAPABILITY_CAN_CONTROL_MAGNIFICATION;
+ }
+ if (asAttributes.getBoolean(com.android.internal.R.styleable
+ .AccessibilityService_canPerformGestures, false)) {
+ mCapabilities |= CAPABILITY_CAN_PERFORM_GESTURES;
+ }
+ if (asAttributes.getBoolean(com.android.internal.R.styleable
+ .AccessibilityService_canRequestFingerprintGestures, false)) {
+ mCapabilities |= CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES;
+ }
+ if (asAttributes.getBoolean(com.android.internal.R.styleable
+ .AccessibilityService_canTakeScreenshot, false)) {
+ mCapabilities |= CAPABILITY_CAN_TAKE_SCREENSHOT;
+ }
+ TypedValue peekedValue = asAttributes.peekValue(
+ com.android.internal.R.styleable.AccessibilityService_description);
+ if (peekedValue != null) {
+ mDescriptionResId = peekedValue.resourceId;
+ CharSequence nonLocalizedDescription = peekedValue.coerceToString();
+ if (nonLocalizedDescription != null) {
+ mNonLocalizedDescription = nonLocalizedDescription.toString().trim();
+ }
+ }
+ peekedValue = asAttributes.peekValue(
+ com.android.internal.R.styleable.AccessibilityService_summary);
+ if (peekedValue != null) {
+ mSummaryResId = peekedValue.resourceId;
+ CharSequence nonLocalizedSummary = peekedValue.coerceToString();
+ if (nonLocalizedSummary != null) {
+ mNonLocalizedSummary = nonLocalizedSummary.toString().trim();
+ }
+ }
+ peekedValue = asAttributes.peekValue(
+ com.android.internal.R.styleable.AccessibilityService_animatedImageDrawable);
+ if (peekedValue != null) {
+ mAnimatedImageRes = peekedValue.resourceId;
+ }
+ peekedValue = asAttributes.peekValue(
+ com.android.internal.R.styleable.AccessibilityService_htmlDescription);
+ if (peekedValue != null) {
+ mHtmlDescriptionRes = peekedValue.resourceId;
+ }
+ mIsAccessibilityTool = asAttributes.getBoolean(
+ R.styleable.AccessibilityService_isAccessibilityTool, false);
+ mTileServiceName = asAttributes.getString(
+ com.android.internal.R.styleable.AccessibilityService_tileService);
+ peekedValue = asAttributes.peekValue(
+ com.android.internal.R.styleable.AccessibilityService_intro);
+ if (peekedValue != null) {
+ mIntroResId = peekedValue.resourceId;
+ }
+ asAttributes.recycle();
+ } catch (NameNotFoundException e) {
+ throw new XmlPullParserException( "Unable to create context for: "
+ + serviceInfo.packageName);
+ } finally {
+ if (parser != null) {
+ parser.close();
+ }
+
+ mDynamicPropertyDefaults = new DynamicPropertyDefaults(this);
+ }
+ }
+
+ /**
+ * Resets all dynamically configurable properties to their default values.
+ *
+ * @hide
+ */
+ // LINT.IfChange(dynamic_property_reset)
+ public void resetDynamicallyConfigurableProperties() {
+ eventTypes = mDynamicPropertyDefaults.mEventTypesDefault;
+ if (mDynamicPropertyDefaults.mPackageNamesDefault == null) {
+ packageNames = null;
+ } else {
+ packageNames = mDynamicPropertyDefaults.mPackageNamesDefault.toArray(new String[0]);
+ }
+ feedbackType = mDynamicPropertyDefaults.mFeedbackTypeDefault;
+ notificationTimeout = mDynamicPropertyDefaults.mNotificationTimeoutDefault;
+ mNonInteractiveUiTimeout = mDynamicPropertyDefaults.mNonInteractiveUiTimeoutDefault;
+ mInteractiveUiTimeout = mDynamicPropertyDefaults.mInteractiveUiTimeoutDefault;
+ flags = mDynamicPropertyDefaults.mFlagsDefault;
+ mMotionEventSources = mDynamicPropertyDefaults.mMotionEventSourcesDefault;
+ if (Flags.motionEventObserving()) {
+ mObservedMotionEventSources = mDynamicPropertyDefaults
+ .mObservedMotionEventSourcesDefault;
+ }
+ }
+ // LINT.ThenChange(:dynamic_property_update)
+
+ /**
+ * Updates the properties that an AccessibilityService can change dynamically.
+ * <p>
+ * Note: A11y services targeting APIs > Q, it cannot update flagRequestAccessibilityButton
+ * dynamically.
+ * </p>
+ *
+ * @param platformCompat The platform compat service to check the compatibility change.
+ * @param other The info from which to update the properties.
+ *
+ * @hide
+ */
+ // LINT.IfChange(dynamic_property_update)
+ public void updateDynamicallyConfigurableProperties(IPlatformCompat platformCompat,
+ AccessibilityServiceInfo other) {
+ if (isRequestAccessibilityButtonChangeEnabled(platformCompat)) {
+ other.flags &= ~FLAG_REQUEST_ACCESSIBILITY_BUTTON;
+ other.flags |= (flags & FLAG_REQUEST_ACCESSIBILITY_BUTTON);
+ }
+ eventTypes = other.eventTypes;
+ packageNames = other.packageNames;
+ feedbackType = other.feedbackType;
+ notificationTimeout = other.notificationTimeout;
+ mNonInteractiveUiTimeout = other.mNonInteractiveUiTimeout;
+ mInteractiveUiTimeout = other.mInteractiveUiTimeout;
+ flags = other.flags;
+ mMotionEventSources = other.mMotionEventSources;
+ if (Flags.motionEventObserving()) {
+ setObservedMotionEventSources(other.mObservedMotionEventSources);
+ }
+ // NOTE: Ensure that only properties that are safe to be modified by the service itself
+ // are included here (regardless of hidden setters, etc.).
+ }
+ // LINT.ThenChange(:dynamic_property_defaults)
+
+ private boolean isRequestAccessibilityButtonChangeEnabled(IPlatformCompat platformCompat) {
+ if (mResolveInfo == null) {
+ return true;
+ }
+ try {
+ if (platformCompat != null) {
+ return platformCompat.isChangeEnabled(REQUEST_ACCESSIBILITY_BUTTON_CHANGE,
+ mResolveInfo.serviceInfo.applicationInfo);
+ }
+ } catch (RemoteException ignore) {
+ }
+ return mResolveInfo.serviceInfo.applicationInfo.targetSdkVersion > Build.VERSION_CODES.Q;
+ }
+
+ /**
+ * @hide
+ */
+ public void setComponentName(@NonNull ComponentName component) {
+ mComponentName = component;
+ }
+
+ /**
+ * @hide
+ */
+ public void setResolveInfo(@NonNull ResolveInfo resolveInfo) {
+ mResolveInfo = resolveInfo;
+ }
+
+ /**
+ * @hide
+ */
+ @TestApi
+ @NonNull
+ public ComponentName getComponentName() {
+ return mComponentName;
+ }
+
+ /**
+ * The accessibility service id.
+ * <p>
+ * <strong>Generated by the system.</strong>
+ * </p>
+ * @return The id (or {@code null} if the component is not set yet).
+ */
+ public String getId() {
+ return mComponentName == null ? null : mComponentName.flattenToShortString();
+ }
+
+ /**
+ * The service {@link ResolveInfo}.
+ * <p>
+ * <strong>Generated by the system.</strong>
+ * </p>
+ * @return The info.
+ */
+ public ResolveInfo getResolveInfo() {
+ return mResolveInfo;
+ }
+
+ /**
+ * The settings activity name.
+ * <p>
+ * <strong>Statically set from
+ * {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong>
+ * </p>
+ * @return The settings activity name.
+ */
+ public String getSettingsActivityName() {
+ return mSettingsActivityName;
+ }
+
+ /**
+ * Gets the name of {@link android.service.quicksettings.TileService} is associated with
+ * this accessibility service.
+ *
+ * @return The name of {@link android.service.quicksettings.TileService}.
+ */
+ @Nullable
+ public String getTileServiceName() {
+ return mTileServiceName;
+ }
+
+ /**
+ * Gets the animated image resource id.
+ *
+ * @return The animated image resource id.
+ *
+ * @hide
+ */
+ public int getAnimatedImageRes() {
+ return mAnimatedImageRes;
+ }
+
+ /**
+ * The animated image drawable.
+ * <p>
+ * Image can not exceed the screen size.
+ * <strong>Statically set from
+ * {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong>
+ * </p>
+ * @return The animated image drawable, or null if the resource is invalid or the image
+ * exceed the screen size.
+ *
+ * @hide
+ */
+ @Nullable
+ public Drawable loadAnimatedImage(@NonNull Context context) {
+ if (mAnimatedImageRes == /* invalid */ 0) {
+ return null;
+ }
+
+ return loadSafeAnimatedImage(context, mResolveInfo.serviceInfo.applicationInfo,
+ mAnimatedImageRes);
+ }
+
+ /**
+ * Whether this service can retrieve the current window's content.
+ * <p>
+ * <strong>Statically set from
+ * {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong>
+ * </p>
+ * @return True if window content can be retrieved.
+ *
+ * @deprecated Use {@link #getCapabilities()}.
+ */
+ public boolean getCanRetrieveWindowContent() {
+ return (mCapabilities & CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT) != 0;
+ }
+
+ /**
+ * Returns the bit mask of capabilities this accessibility service has such as
+ * being able to retrieve the active window content, etc.
+ *
+ * @return The capability bit mask.
+ *
+ * @see #CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT
+ * @see #CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION
+ * @see #CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS
+ * @see #CAPABILITY_CAN_CONTROL_MAGNIFICATION
+ * @see #CAPABILITY_CAN_PERFORM_GESTURES
+ * @see #CAPABILITY_CAN_TAKE_SCREENSHOT
+ */
+ public int getCapabilities() {
+ return mCapabilities;
+ }
+
+ /**
+ * Sets the bit mask of capabilities this accessibility service has such as
+ * being able to retrieve the active window content, etc.
+ *
+ * @param capabilities The capability bit mask.
+ *
+ * @see #CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT
+ * @see #CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION
+ * @see #CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS
+ * @see #CAPABILITY_CAN_CONTROL_MAGNIFICATION
+ * @see #CAPABILITY_CAN_PERFORM_GESTURES
+ * @see #CAPABILITY_CAN_TAKE_SCREENSHOT
+ *
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public void setCapabilities(int capabilities) {
+ mCapabilities = capabilities;
+ }
+
+ /**
+ * Returns the bit mask of {@link android.view.InputDevice} sources that the accessibility
+ * service wants to listen to for generic {@link android.view.MotionEvent}s.
+ */
+ @MotionEventSources
+ public int getMotionEventSources() {
+ return mMotionEventSources;
+ }
+
+ /**
+ * Sets the bit mask of {@link android.view.InputDevice} sources that the accessibility
+ * service wants to listen to for generic {@link android.view.MotionEvent}s.
+ *
+ * <p>
+ * Including an {@link android.view.InputDevice} source that does not send
+ * {@link android.view.MotionEvent}s is effectively a no-op for that source, since you will
+ * not receive any events from that source.
+ * </p>
+ *
+ * <p>
+ * See {@link android.view.InputDevice} for complete source definitions.
+ * Many input devices send {@link android.view.InputEvent}s from more than one type of source so
+ * you may need to include multiple {@link android.view.MotionEvent} sources here, in addition
+ * to using {@link AccessibilityService#onKeyEvent} to listen to {@link android.view.KeyEvent}s.
+ * </p>
+ *
+ * <p>
+ * <strong>Note:</strong> {@link android.view.InputDevice} sources contain source class bits
+ * that complicate bitwise flag removal operations. To remove a specific source you should
+ * rebuild the entire value using bitwise OR operations on the individual source constants.
+ * </p>
+ *
+ * @param motionEventSources A bit mask of {@link android.view.InputDevice} sources.
+ * @see AccessibilityService#onMotionEvent
+ */
+ public void setMotionEventSources(@MotionEventSources int motionEventSources) {
+ mMotionEventSources = motionEventSources;
+ mObservedMotionEventSources = 0;
+ }
+
+ /**
+ * Sets the bit mask of {@link android.view.InputDevice} sources that the accessibility service
+ * wants to observe generic {@link android.view.MotionEvent}s from if it has already requested
+ * to listen to them using {@link #setMotionEventSources(int)}. Events from these sources will
+ * be sent to the rest of the input pipeline without being consumed by accessibility services.
+ * This service will still be able to see them.
+ *
+ * <p><strong>Note:</strong> you will need to call this function every time you call {@link
+ * #setMotionEventSources(int)}. Calling {@link #setMotionEventSources(int)} clears the list of
+ * observed motion event sources for this service.
+ *
+ * <p><strong>Note:</strong> {@link android.view.InputDevice} sources contain source class bits
+ * that complicate bitwise flag removal operations. To remove a specific source you should
+ * rebuild the entire value using bitwise OR operations on the individual source constants.
+ *
+ * <p>Including an {@link android.view.InputDevice} source that does not send {@link
+ * android.view.MotionEvent}s is effectively a no-op for that source, since you will not receive
+ * any events from that source.
+ *
+ * <p><strong>Note:</strong> Calling this function with a source that has not been listened to
+ * using {@link #setMotionEventSources(int)} will throw an exception.
+ *
+ * @see AccessibilityService#onMotionEvent
+ * @see #MotionEventSources
+ * @see #setMotionEventSources(int)
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_MOTION_EVENT_OBSERVING)
+ @TestApi
+ public void setObservedMotionEventSources(int observedMotionEventSources) {
+ // Confirm that any sources requested here have already been requested for listening.
+ if ((observedMotionEventSources & ~mMotionEventSources) != 0) {
+ String message =
+ String.format(
+ "Requested motion event sources for listening = 0x%x but requested"
+ + " motion event sources for observing = 0x%x.",
+ mMotionEventSources, observedMotionEventSources);
+ throw new IllegalArgumentException(message);
+ }
+ mObservedMotionEventSources = observedMotionEventSources;
+ }
+
+ /**
+ * Returns the bit mask of {@link android.view.InputDevice} sources that the accessibility
+ * service wants to observe generic {@link android.view.MotionEvent}s from if it has already
+ * requested to listen to them using {@link #setMotionEventSources(int)}. Events from these
+ * sources will be sent to the rest of the input pipeline without being consumed by
+ * accessibility services. This service will still be able to see them.
+ *
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_MOTION_EVENT_OBSERVING)
+ @MotionEventSources
+ @TestApi
+ public int getObservedMotionEventSources() {
+ return mObservedMotionEventSources;
+ }
+
+ /**
+ * The localized summary of the accessibility service.
+ *
+ * <p><strong>Statically set from {@link AccessibilityService#SERVICE_META_DATA
+ * meta-data}.</strong>
+ *
+ * @return The localized summary if available, and {@code null} if a summary has not been
+ * provided.
+ */
+ public CharSequence loadSummary(PackageManager packageManager) {
+ if (mSummaryResId == 0) {
+ return mNonLocalizedSummary;
+ }
+ ServiceInfo serviceInfo = mResolveInfo.serviceInfo;
+ CharSequence summary = packageManager.getText(serviceInfo.packageName,
+ mSummaryResId, serviceInfo.applicationInfo);
+ if (summary != null) {
+ return summary.toString().trim();
+ }
+ return null;
+ }
+
+ /**
+ * The localized intro of the accessibility service.
+ * <p>
+ * <strong>Statically set from
+ * {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong>
+ * </p>
+ * @return The localized intro if available, and {@code null} if a intro
+ * has not been provided.
+ */
+ @Nullable
+ public CharSequence loadIntro(@NonNull PackageManager packageManager) {
+ if (mIntroResId == /* invalid */ 0) {
+ return null;
+ }
+ ServiceInfo serviceInfo = mResolveInfo.serviceInfo;
+ CharSequence intro = packageManager.getText(serviceInfo.packageName,
+ mIntroResId, serviceInfo.applicationInfo);
+ if (intro != null) {
+ return intro.toString().trim();
+ }
+ return null;
+ }
+
+ /**
+ * Gets the non-localized description of the accessibility service.
+ * <p>
+ * <strong>Statically set from
+ * {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong>
+ * </p>
+ * @return The description.
+ *
+ * @deprecated Use {@link #loadDescription(PackageManager)}.
+ */
+ public String getDescription() {
+ return mNonLocalizedDescription;
+ }
+
+ /**
+ * The localized description of the accessibility service.
+ * <p>
+ * <strong>Statically set from
+ * {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong>
+ * </p>
+ * @return The localized description.
+ */
+ public String loadDescription(PackageManager packageManager) {
+ if (mDescriptionResId == 0) {
+ return mNonLocalizedDescription;
+ }
+ ServiceInfo serviceInfo = mResolveInfo.serviceInfo;
+ CharSequence description = packageManager.getText(serviceInfo.packageName,
+ mDescriptionResId, serviceInfo.applicationInfo);
+ if (description != null) {
+ return description.toString().trim();
+ }
+ return null;
+ }
+
+ /**
+ * The localized and restricted html description of the accessibility service.
+ * <p>
+ * Filters the <img> tag which do not meet the custom specification and the <a> tag.
+ * <strong>Statically set from
+ * {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong>
+ * </p>
+ * @return The localized and restricted html description.
+ *
+ * @hide
+ */
+ @Nullable
+ public String loadHtmlDescription(@NonNull PackageManager packageManager) {
+ if (mHtmlDescriptionRes == /* invalid */ 0) {
+ return null;
+ }
+
+ final ServiceInfo serviceInfo = mResolveInfo.serviceInfo;
+ final CharSequence htmlDescription = packageManager.getText(serviceInfo.packageName,
+ mHtmlDescriptionRes, serviceInfo.applicationInfo);
+ if (htmlDescription != null) {
+ return getFilteredHtmlText(htmlDescription.toString().trim());
+ }
+ return null;
+ }
+
+ /**
+ * Set the recommended time that non-interactive controls need to remain on the screen to
+ * support the user.
+ * <p>
+ * <strong>This value can be dynamically set at runtime by
+ * {@link AccessibilityService#setServiceInfo(AccessibilityServiceInfo)}.</strong>
+ * </p>
+ *
+ * @param timeout The timeout in milliseconds.
+ *
+ * @see android.R.styleable#AccessibilityService_nonInteractiveUiTimeout
+ */
+ public void setNonInteractiveUiTimeoutMillis(@IntRange(from = 0) int timeout) {
+ mNonInteractiveUiTimeout = timeout;
+ }
+
+ /**
+ * Get the recommended timeout for non-interactive controls.
+ *
+ * @return The timeout in milliseconds.
+ *
+ * @see #setNonInteractiveUiTimeoutMillis(int)
+ */
+ public int getNonInteractiveUiTimeoutMillis() {
+ return mNonInteractiveUiTimeout;
+ }
+
+ /**
+ * Set the recommended time that interactive controls need to remain on the screen to
+ * support the user.
+ * <p>
+ * <strong>This value can be dynamically set at runtime by
+ * {@link AccessibilityService#setServiceInfo(AccessibilityServiceInfo)}.</strong>
+ * </p>
+ *
+ * @param timeout The timeout in milliseconds.
+ *
+ * @see android.R.styleable#AccessibilityService_interactiveUiTimeout
+ */
+ public void setInteractiveUiTimeoutMillis(@IntRange(from = 0) int timeout) {
+ mInteractiveUiTimeout = timeout;
+ }
+
+ /**
+ * Get the recommended timeout for interactive controls.
+ *
+ * @return The timeout in milliseconds.
+ *
+ * @see #setInteractiveUiTimeoutMillis(int)
+ */
+ public int getInteractiveUiTimeoutMillis() {
+ return mInteractiveUiTimeout;
+ }
+
+ /** {@hide} */
+ public boolean isDirectBootAware() {
+ return ((flags & FLAG_FORCE_DIRECT_BOOT_AWARE) != 0)
+ || mResolveInfo.serviceInfo.directBootAware;
+ }
+
+ /**
+ * Sets whether the service is used to assist users with disabilities.
+ *
+ * <p>
+ * This property is normally provided in the service's {@link #mResolveInfo ResolveInfo}.
+ * </p>
+ *
+ * <p>
+ * This method is helpful for unit testing. However, this property is not dynamically
+ * configurable by a standard {@link AccessibilityService} so it's not possible to update the
+ * copy held by the system with this method.
+ * </p>
+ *
+ * @hide
+ */
+ @SystemApi
+ public void setAccessibilityTool(boolean isAccessibilityTool) {
+ mIsAccessibilityTool = isAccessibilityTool;
+ }
+
+ /**
+ * Indicates if the service is used to assist users with disabilities.
+ *
+ * @return {@code true} if the property is set to true.
+ */
+ public boolean isAccessibilityTool() {
+ return mIsAccessibilityTool;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public int describeContents() {
+ return 0;
+ }
+
+ /** @hide */
+ public final boolean isWithinParcelableSize() {
+ final Parcel parcel = Parcel.obtain();
+ writeToParcel(parcel, 0);
+ final boolean result = parcel.dataSize() <= IBinder.MAX_IPC_SIZE;
+ parcel.recycle();
+ return result;
+ }
+
+ public void writeToParcel(Parcel parcel, int flagz) {
+ parcel.writeInt(eventTypes);
+ parcel.writeStringArray(packageNames);
+ parcel.writeInt(feedbackType);
+ parcel.writeLong(notificationTimeout);
+ parcel.writeInt(mNonInteractiveUiTimeout);
+ parcel.writeInt(mInteractiveUiTimeout);
+ parcel.writeInt(flags);
+ parcel.writeInt(crashed ? 1 : 0);
+ parcel.writeParcelable(mComponentName, flagz);
+ parcel.writeParcelable(mResolveInfo, 0);
+ parcel.writeString(mSettingsActivityName);
+ parcel.writeInt(mCapabilities);
+ parcel.writeInt(mSummaryResId);
+ parcel.writeString(mNonLocalizedSummary);
+ parcel.writeInt(mDescriptionResId);
+ parcel.writeInt(mAnimatedImageRes);
+ parcel.writeInt(mHtmlDescriptionRes);
+ parcel.writeString(mNonLocalizedDescription);
+ parcel.writeBoolean(mIsAccessibilityTool);
+ parcel.writeString(mTileServiceName);
+ parcel.writeInt(mIntroResId);
+ parcel.writeInt(mMotionEventSources);
+ parcel.writeInt(mObservedMotionEventSources);
+ }
+
+ private void initFromParcel(Parcel parcel) {
+ eventTypes = parcel.readInt();
+ packageNames = parcel.readStringArray();
+ feedbackType = parcel.readInt();
+ notificationTimeout = parcel.readLong();
+ mNonInteractiveUiTimeout = parcel.readInt();
+ mInteractiveUiTimeout = parcel.readInt();
+ flags = parcel.readInt();
+ crashed = parcel.readInt() != 0;
+ mComponentName = parcel.readParcelable(this.getClass().getClassLoader(), android.content.ComponentName.class);
+ mResolveInfo = parcel.readParcelable(null, android.content.pm.ResolveInfo.class);
+ mSettingsActivityName = parcel.readString();
+ mCapabilities = parcel.readInt();
+ mSummaryResId = parcel.readInt();
+ mNonLocalizedSummary = parcel.readString();
+ mDescriptionResId = parcel.readInt();
+ mAnimatedImageRes = parcel.readInt();
+ mHtmlDescriptionRes = parcel.readInt();
+ mNonLocalizedDescription = parcel.readString();
+ mIsAccessibilityTool = parcel.readBoolean();
+ mTileServiceName = parcel.readString();
+ mIntroResId = parcel.readInt();
+ mMotionEventSources = parcel.readInt();
+ // use the setter here because it throws an exception for invalid values.
+ setObservedMotionEventSources(parcel.readInt());
+ }
+
+ @Override
+ public int hashCode() {
+ return 31 * 1 + ((mComponentName == null) ? 0 : mComponentName.hashCode());
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ AccessibilityServiceInfo other = (AccessibilityServiceInfo) obj;
+ if (mComponentName == null) {
+ if (other.mComponentName != null) {
+ return false;
+ }
+ } else if (!mComponentName.equals(other.mComponentName)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder stringBuilder = new StringBuilder();
+ appendEventTypes(stringBuilder, eventTypes);
+ stringBuilder.append(", ");
+ appendPackageNames(stringBuilder, packageNames);
+ stringBuilder.append(", ");
+ appendFeedbackTypes(stringBuilder, feedbackType);
+ stringBuilder.append(", ");
+ stringBuilder.append("notificationTimeout: ").append(notificationTimeout);
+ stringBuilder.append(", ");
+ stringBuilder.append("nonInteractiveUiTimeout: ").append(mNonInteractiveUiTimeout);
+ stringBuilder.append(", ");
+ stringBuilder.append("interactiveUiTimeout: ").append(mInteractiveUiTimeout);
+ stringBuilder.append(", ");
+ appendFlags(stringBuilder, flags);
+ stringBuilder.append(", ");
+ stringBuilder.append("id: ").append(getId());
+ stringBuilder.append(", ");
+ stringBuilder.append("resolveInfo: ").append(mResolveInfo);
+ stringBuilder.append(", ");
+ stringBuilder.append("settingsActivityName: ").append(mSettingsActivityName);
+ stringBuilder.append(", ");
+ stringBuilder.append("tileServiceName: ").append(mTileServiceName);
+ stringBuilder.append(", ");
+ stringBuilder.append("summary: ").append(mNonLocalizedSummary);
+ stringBuilder.append(", ");
+ stringBuilder.append("isAccessibilityTool: ").append(mIsAccessibilityTool);
+ stringBuilder.append(", ");
+ appendCapabilities(stringBuilder, mCapabilities);
+ return stringBuilder.toString();
+ }
+
+ private static void appendFeedbackTypes(StringBuilder stringBuilder,
+ @FeedbackType int feedbackTypes) {
+ stringBuilder.append("feedbackTypes:");
+ stringBuilder.append("[");
+ while (feedbackTypes != 0) {
+ final int feedbackTypeBit = (1 << Integer.numberOfTrailingZeros(feedbackTypes));
+ stringBuilder.append(feedbackTypeToString(feedbackTypeBit));
+ feedbackTypes &= ~feedbackTypeBit;
+ if (feedbackTypes != 0) {
+ stringBuilder.append(", ");
+ }
+ }
+ stringBuilder.append("]");
+ }
+
+ private static void appendPackageNames(StringBuilder stringBuilder, String[] packageNames) {
+ stringBuilder.append("packageNames:");
+ stringBuilder.append("[");
+ if (packageNames != null) {
+ final int packageNameCount = packageNames.length;
+ for (int i = 0; i < packageNameCount; i++) {
+ stringBuilder.append(packageNames[i]);
+ if (i < packageNameCount - 1) {
+ stringBuilder.append(", ");
+ }
+ }
+ }
+ stringBuilder.append("]");
+ }
+
+ private static void appendEventTypes(StringBuilder stringBuilder, int eventTypes) {
+ stringBuilder.append("eventTypes:");
+ stringBuilder.append("[");
+ while (eventTypes != 0) {
+ final int eventTypeBit = (1 << Integer.numberOfTrailingZeros(eventTypes));
+ stringBuilder.append(AccessibilityEvent.eventTypeToString(eventTypeBit));
+ eventTypes &= ~eventTypeBit;
+ if (eventTypes != 0) {
+ stringBuilder.append(", ");
+ }
+ }
+ stringBuilder.append("]");
+ }
+
+ private static void appendFlags(StringBuilder stringBuilder, int flags) {
+ stringBuilder.append("flags:");
+ stringBuilder.append("[");
+ while (flags != 0) {
+ final int flagBit = (1 << Integer.numberOfTrailingZeros(flags));
+ stringBuilder.append(flagToString(flagBit));
+ flags &= ~flagBit;
+ if (flags != 0) {
+ stringBuilder.append(", ");
+ }
+ }
+ stringBuilder.append("]");
+ }
+
+ private static void appendCapabilities(StringBuilder stringBuilder, int capabilities) {
+ stringBuilder.append("capabilities:");
+ stringBuilder.append("[");
+ while (capabilities != 0) {
+ final int capabilityBit = (1 << Integer.numberOfTrailingZeros(capabilities));
+ stringBuilder.append(capabilityToString(capabilityBit));
+ capabilities &= ~capabilityBit;
+ if (capabilities != 0) {
+ stringBuilder.append(", ");
+ }
+ }
+ stringBuilder.append("]");
+ }
+
+ /**
+ * Returns the string representation of a feedback type. For example,
+ * {@link #FEEDBACK_SPOKEN} is represented by the string FEEDBACK_SPOKEN.
+ *
+ * @param feedbackType The feedback type.
+ * @return The string representation.
+ */
+ public static String feedbackTypeToString(int feedbackType) {
+ StringBuilder builder = new StringBuilder();
+ builder.append("[");
+ while (feedbackType != 0) {
+ final int feedbackTypeFlag = 1 << Integer.numberOfTrailingZeros(feedbackType);
+ feedbackType &= ~feedbackTypeFlag;
+ switch (feedbackTypeFlag) {
+ case FEEDBACK_AUDIBLE:
+ if (builder.length() > 1) {
+ builder.append(", ");
+ }
+ builder.append("FEEDBACK_AUDIBLE");
+ break;
+ case FEEDBACK_HAPTIC:
+ if (builder.length() > 1) {
+ builder.append(", ");
+ }
+ builder.append("FEEDBACK_HAPTIC");
+ break;
+ case FEEDBACK_GENERIC:
+ if (builder.length() > 1) {
+ builder.append(", ");
+ }
+ builder.append("FEEDBACK_GENERIC");
+ break;
+ case FEEDBACK_SPOKEN:
+ if (builder.length() > 1) {
+ builder.append(", ");
+ }
+ builder.append("FEEDBACK_SPOKEN");
+ break;
+ case FEEDBACK_VISUAL:
+ if (builder.length() > 1) {
+ builder.append(", ");
+ }
+ builder.append("FEEDBACK_VISUAL");
+ break;
+ case FEEDBACK_BRAILLE:
+ if (builder.length() > 1) {
+ builder.append(", ");
+ }
+ builder.append("FEEDBACK_BRAILLE");
+ break;
+ }
+ }
+ builder.append("]");
+ return builder.toString();
+ }
+
+ /**
+ * Returns the string representation of a flag. For example,
+ * {@link #DEFAULT} is represented by the string DEFAULT.
+ *
+ * @param flag The flag.
+ * @return The string representation.
+ */
+ public static String flagToString(int flag) {
+ switch (flag) {
+ case DEFAULT:
+ return "DEFAULT";
+ case FLAG_INCLUDE_NOT_IMPORTANT_VIEWS:
+ return "FLAG_INCLUDE_NOT_IMPORTANT_VIEWS";
+ case FLAG_REQUEST_TOUCH_EXPLORATION_MODE:
+ return "FLAG_REQUEST_TOUCH_EXPLORATION_MODE";
+ case FLAG_SERVICE_HANDLES_DOUBLE_TAP:
+ return "FLAG_SERVICE_HANDLES_DOUBLE_TAP";
+ case FLAG_REQUEST_MULTI_FINGER_GESTURES:
+ return "FLAG_REQUEST_MULTI_FINGER_GESTURES";
+ case FLAG_REQUEST_2_FINGER_PASSTHROUGH:
+ return "FLAG_REQUEST_2_FINGER_PASSTHROUGH";
+ case FLAG_SEND_MOTION_EVENTS:
+ return "FLAG_SEND_MOTION_EVENTS";
+ case FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY:
+ return "FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY";
+ case FLAG_REPORT_VIEW_IDS:
+ return "FLAG_REPORT_VIEW_IDS";
+ case FLAG_REQUEST_FILTER_KEY_EVENTS:
+ return "FLAG_REQUEST_FILTER_KEY_EVENTS";
+ case FLAG_RETRIEVE_INTERACTIVE_WINDOWS:
+ return "FLAG_RETRIEVE_INTERACTIVE_WINDOWS";
+ case FLAG_ENABLE_ACCESSIBILITY_VOLUME:
+ return "FLAG_ENABLE_ACCESSIBILITY_VOLUME";
+ case FLAG_REQUEST_ACCESSIBILITY_BUTTON:
+ return "FLAG_REQUEST_ACCESSIBILITY_BUTTON";
+ case FLAG_REQUEST_FINGERPRINT_GESTURES:
+ return "FLAG_REQUEST_FINGERPRINT_GESTURES";
+ case FLAG_REQUEST_SHORTCUT_WARNING_DIALOG_SPOKEN_FEEDBACK:
+ return "FLAG_REQUEST_SHORTCUT_WARNING_DIALOG_SPOKEN_FEEDBACK";
+ case FLAG_INPUT_METHOD_EDITOR:
+ return "FLAG_INPUT_METHOD_EDITOR";
+ default:
+ return null;
+ }
+ }
+
+ /**
+ * Returns the string representation of a capability. For example,
+ * {@link #CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT} is represented
+ * by the string CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT.
+ *
+ * @param capability The capability.
+ * @return The string representation.
+ */
+ public static String capabilityToString(int capability) {
+ switch (capability) {
+ case CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT:
+ return "CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT";
+ case CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION:
+ return "CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION";
+ case CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS:
+ return "CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS";
+ case CAPABILITY_CAN_CONTROL_MAGNIFICATION:
+ return "CAPABILITY_CAN_CONTROL_MAGNIFICATION";
+ case CAPABILITY_CAN_PERFORM_GESTURES:
+ return "CAPABILITY_CAN_PERFORM_GESTURES";
+ case CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES:
+ return "CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES";
+ case CAPABILITY_CAN_TAKE_SCREENSHOT:
+ return "CAPABILITY_CAN_TAKE_SCREENSHOT";
+ default:
+ return "UNKNOWN";
+ }
+ }
+
+ /**
+ * @hide
+ * @return The list of {@link CapabilityInfo} objects.
+ * @deprecated The version that takes a context works better.
+ */
+ public List<CapabilityInfo> getCapabilityInfos() {
+ return getCapabilityInfos(null);
+ }
+
+ /**
+ * @hide
+ * @param context A valid context
+ * @return The list of {@link CapabilityInfo} objects.
+ */
+ public List<CapabilityInfo> getCapabilityInfos(Context context) {
+ if (mCapabilities == 0) {
+ return Collections.emptyList();
+ }
+ int capabilities = mCapabilities;
+ List<CapabilityInfo> capabilityInfos = new ArrayList<CapabilityInfo>();
+ SparseArray<CapabilityInfo> capabilityInfoSparseArray =
+ getCapabilityInfoSparseArray(context);
+ while (capabilities != 0) {
+ final int capabilityBit = 1 << Integer.numberOfTrailingZeros(capabilities);
+ capabilities &= ~capabilityBit;
+ CapabilityInfo capabilityInfo = capabilityInfoSparseArray.get(capabilityBit);
+ if (capabilityInfo != null) {
+ capabilityInfos.add(capabilityInfo);
+ }
+ }
+ return capabilityInfos;
+ }
+
+ private static SparseArray<CapabilityInfo> getCapabilityInfoSparseArray(Context context) {
+ if (sAvailableCapabilityInfos == null) {
+ sAvailableCapabilityInfos = new SparseArray<CapabilityInfo>();
+ sAvailableCapabilityInfos.put(CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT,
+ new CapabilityInfo(CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT,
+ R.string.capability_title_canRetrieveWindowContent,
+ R.string.capability_desc_canRetrieveWindowContent));
+ sAvailableCapabilityInfos.put(CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION,
+ new CapabilityInfo(CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION,
+ R.string.capability_title_canRequestTouchExploration,
+ R.string.capability_desc_canRequestTouchExploration));
+ sAvailableCapabilityInfos.put(CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS,
+ new CapabilityInfo(CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS,
+ R.string.capability_title_canRequestFilterKeyEvents,
+ R.string.capability_desc_canRequestFilterKeyEvents));
+ sAvailableCapabilityInfos.put(CAPABILITY_CAN_CONTROL_MAGNIFICATION,
+ new CapabilityInfo(CAPABILITY_CAN_CONTROL_MAGNIFICATION,
+ R.string.capability_title_canControlMagnification,
+ R.string.capability_desc_canControlMagnification));
+ sAvailableCapabilityInfos.put(CAPABILITY_CAN_PERFORM_GESTURES,
+ new CapabilityInfo(CAPABILITY_CAN_PERFORM_GESTURES,
+ R.string.capability_title_canPerformGestures,
+ R.string.capability_desc_canPerformGestures));
+ sAvailableCapabilityInfos.put(CAPABILITY_CAN_TAKE_SCREENSHOT,
+ new CapabilityInfo(CAPABILITY_CAN_TAKE_SCREENSHOT,
+ R.string.capability_title_canTakeScreenshot,
+ R.string.capability_desc_canTakeScreenshot));
+ if ((context == null) || fingerprintAvailable(context)) {
+ sAvailableCapabilityInfos.put(CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES,
+ new CapabilityInfo(CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES,
+ R.string.capability_title_canCaptureFingerprintGestures,
+ R.string.capability_desc_canCaptureFingerprintGestures));
+ }
+ }
+ return sAvailableCapabilityInfos;
+ }
+
+ private static boolean fingerprintAvailable(Context context) {
+ return context.getPackageManager().hasSystemFeature(FEATURE_FINGERPRINT)
+ && context.getSystemService(FingerprintManager.class).isHardwareDetected();
+ }
+ /**
+ * @hide
+ */
+ public static final class CapabilityInfo {
+ public final int capability;
+ public final int titleResId;
+ public final int descResId;
+
+ public CapabilityInfo(int capability, int titleResId, int descResId) {
+ this.capability = capability;
+ this.titleResId = titleResId;
+ this.descResId = descResId;
+ }
+ }
+
+ /**
+ * @see Parcelable.Creator
+ */
+ public static final @android.annotation.NonNull Parcelable.Creator<AccessibilityServiceInfo> CREATOR =
+ new Parcelable.Creator<AccessibilityServiceInfo>() {
+ public AccessibilityServiceInfo createFromParcel(Parcel parcel) {
+ AccessibilityServiceInfo info = new AccessibilityServiceInfo();
+ info.initFromParcel(parcel);
+ return info;
+ }
+
+ public AccessibilityServiceInfo[] newArray(int size) {
+ return new AccessibilityServiceInfo[size];
+ }
+ };
+}
diff --git a/android-35/android/accessibilityservice/AccessibilityShortcutInfo.java b/android-35/android/accessibilityservice/AccessibilityShortcutInfo.java
new file mode 100644
index 0000000..f53cfe4
--- /dev/null
+++ b/android-35/android/accessibilityservice/AccessibilityShortcutInfo.java
@@ -0,0 +1,357 @@
+/*
+ * Copyright (C) 2019 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.accessibilityservice;
+
+import static android.accessibilityservice.util.AccessibilityUtils.getFilteredHtmlText;
+import static android.accessibilityservice.util.AccessibilityUtils.loadSafeAnimatedImage;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.util.Xml;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+/**
+ * Activities of interest to users with accessibility needs may request to be targets of the
+ * accessibility shortcut. These activities must handle the
+ * {@link Intent#ACTION_MAIN} intent with category
+ * {@link Intent#CATEGORY_ACCESSIBILITY_SHORTCUT_TARGET}, which will be dispatched by the system
+ * when the user activates the shortcut when it is configured to point at this target.
+ *
+ * @see Intent#CATEGORY_ACCESSIBILITY_SHORTCUT_TARGET
+ *
+ * @hide
+ */
+public final class AccessibilityShortcutInfo {
+ private static final String TAG_ACCESSIBILITY_SHORTCUT = "accessibility-shortcut-target";
+
+ /**
+ * Name under which an activity component of the accessibility shortcut publishes information
+ * about itself. This meta-data must reference an XML resource containing an
+ * <code><accessibility-shortcut-target></code> tag.
+ */
+ public static final String META_DATA = "android.accessibilityshortcut.target";
+
+ /**
+ * The component name of the accessibility shortcut target.
+ */
+ private final ComponentName mComponentName;
+
+ /**
+ * The activity info of the accessibility shortcut target.
+ */
+ private final ActivityInfo mActivityInfo;
+
+ /**
+ * Resource id of the intro of the accessibility shortcut target.
+ */
+ private final int mIntroResId;
+
+ /**
+ * Resource id of the summary of the accessibility shortcut target.
+ */
+ private final int mSummaryResId;
+
+ /**
+ * Resource id of the description of the accessibility shortcut target.
+ */
+ private final int mDescriptionResId;
+
+ /**
+ * Resource id of the animated image of the accessibility shortcut target.
+ */
+ private final int mAnimatedImageRes;
+
+ /**
+ * Resource id of the html description of the accessibility shortcut target.
+ */
+ private final int mHtmlDescriptionRes;
+
+ /**
+ * The accessibility shortcut target setting activity's name, used by the system
+ * settings to launch the setting activity of this accessibility shortcut target.
+ */
+ private String mSettingsActivityName;
+
+ /**
+ * The name of {@link android.service.quicksettings.TileService} is associated with this
+ * accessibility shortcut target for one to one mapping. It is used by system settings to remind
+ * users this accessibility service has a {@link android.service.quicksettings.TileService}.
+ */
+ private String mTileServiceName;
+
+ /**
+ * Creates a new instance.
+ *
+ * @param context Context for accessing resources.
+ * @param activityInfo The activity info.
+ * @throws XmlPullParserException If a XML parsing error occurs.
+ * @throws IOException If a XML parsing error occurs.
+ */
+ public AccessibilityShortcutInfo(@NonNull Context context, @NonNull ActivityInfo activityInfo)
+ throws XmlPullParserException, IOException {
+ final PackageManager packageManager = context.getPackageManager();
+ mComponentName = activityInfo.getComponentName();
+ mActivityInfo = activityInfo;
+
+ try (XmlResourceParser parser = mActivityInfo.loadXmlMetaData(
+ packageManager, META_DATA)) {
+ if (parser == null) {
+ throw new XmlPullParserException("Meta-data "
+ + TAG_ACCESSIBILITY_SHORTCUT + " does not exist");
+ }
+
+ int type = 0;
+ while (type != XmlPullParser.END_DOCUMENT && type != XmlPullParser.START_TAG) {
+ type = parser.next();
+ }
+
+ final String nodeName = parser.getName();
+ if (!TAG_ACCESSIBILITY_SHORTCUT.equals(nodeName)) {
+ throw new XmlPullParserException("Meta-data does not start with"
+ + TAG_ACCESSIBILITY_SHORTCUT + " tag");
+ }
+
+ final AttributeSet allAttributes = Xml.asAttributeSet(parser);
+ final Resources resources = packageManager.getResourcesForApplication(
+ mActivityInfo.applicationInfo);
+ final TypedArray asAttributes = resources.obtainAttributes(allAttributes,
+ com.android.internal.R.styleable.AccessibilityShortcutTarget);
+
+ // Gets description
+ mDescriptionResId = asAttributes.getResourceId(
+ com.android.internal.R.styleable.AccessibilityShortcutTarget_description, 0);
+ // Gets summary
+ mSummaryResId = asAttributes.getResourceId(
+ com.android.internal.R.styleable.AccessibilityShortcutTarget_summary, 0);
+ // Gets animated image
+ mAnimatedImageRes = asAttributes.getResourceId(
+ com.android.internal.R.styleable
+ .AccessibilityShortcutTarget_animatedImageDrawable, /* defValue= */ 0);
+ // Gets html description
+ mHtmlDescriptionRes = asAttributes.getResourceId(
+ com.android.internal.R.styleable.AccessibilityShortcutTarget_htmlDescription,
+ 0);
+ // Get settings activity name
+ mSettingsActivityName = asAttributes.getString(
+ com.android.internal.R.styleable.AccessibilityShortcutTarget_settingsActivity);
+ // Get tile service class name
+ mTileServiceName = asAttributes.getString(
+ com.android.internal.R.styleable.AccessibilityShortcutTarget_tileService);
+ // Gets intro
+ mIntroResId = asAttributes.getResourceId(
+ com.android.internal.R.styleable.AccessibilityShortcutTarget_intro, 0);
+ asAttributes.recycle();
+ } catch (PackageManager.NameNotFoundException e) {
+ throw new XmlPullParserException("Unable to create context for: "
+ + mActivityInfo.packageName);
+ }
+ }
+
+ /**
+ * The {@link ActivityInfo} of accessibility shortcut target.
+ *
+ * @return The activity info.
+ */
+ @NonNull
+ public ActivityInfo getActivityInfo() {
+ return mActivityInfo;
+ }
+
+ /**
+ * The {@link ComponentName} of the accessibility shortcut target.
+ *
+ * @return The component name
+ */
+ @NonNull
+ public ComponentName getComponentName() {
+ return mComponentName;
+ }
+
+ /**
+ * The localized summary of the accessibility shortcut target.
+ *
+ * @return The localized summary if available, and {@code null} if a summary
+ * has not been provided.
+ */
+ @Nullable
+ public String loadSummary(@NonNull PackageManager packageManager) {
+ return loadResourceString(packageManager, mActivityInfo, mSummaryResId);
+ }
+
+ /**
+ * The localized intro of the accessibility shortcut target.
+ *
+ * @return The localized intro.
+ */
+ @Nullable
+ public String loadIntro(@NonNull PackageManager packageManager) {
+ return loadResourceString(packageManager, mActivityInfo, mIntroResId);
+ }
+
+ /**
+ * The localized description of the accessibility shortcut target.
+ *
+ * @return The localized description.
+ */
+ @Nullable
+ public String loadDescription(@NonNull PackageManager packageManager) {
+ return loadResourceString(packageManager, mActivityInfo, mDescriptionResId);
+ }
+
+ /**
+ * Gets the animated image resource id.
+ *
+ * @return The animated image resource id.
+ *
+ * @hide
+ */
+ public int getAnimatedImageRes() {
+ return mAnimatedImageRes;
+ }
+
+ /**
+ * The animated image drawable of the accessibility shortcut target.
+ *
+ * @return The animated image drawable, or null if the resource is invalid or the image
+ * exceed the screen size.
+ *
+ * @hide
+ */
+ @Nullable
+ public Drawable loadAnimatedImage(@NonNull Context context) {
+ if (mAnimatedImageRes == /* invalid */ 0) {
+ return null;
+ }
+
+ return loadSafeAnimatedImage(context, mActivityInfo.applicationInfo, mAnimatedImageRes);
+ }
+
+ /**
+ * The localized and restricted html description of the accessibility shortcut target.
+ * It filters the <img> tag which do not meet the custom specification and the <a> tag.
+ *
+ * @return The localized and restricted html description.
+ *
+ * @hide
+ */
+ @Nullable
+ public String loadHtmlDescription(@NonNull PackageManager packageManager) {
+ final String htmlDescription = loadResourceString(packageManager, mActivityInfo,
+ mHtmlDescriptionRes);
+ if (htmlDescription != null) {
+ return getFilteredHtmlText(htmlDescription);
+ }
+ return null;
+ }
+
+ /**
+ * The settings activity name.
+ *
+ * @return The settings activity name.
+ */
+ @Nullable
+ public String getSettingsActivityName() {
+ return mSettingsActivityName;
+ }
+
+ /**
+ * Gets the name of {@link android.service.quicksettings.TileService} is associated with
+ * this accessibility shortcut target.
+ *
+ * @return The class name of {@link android.service.quicksettings.TileService}.
+ */
+ @Nullable
+ public String getTileServiceName() {
+ return mTileServiceName;
+ }
+
+ /**
+ * Gets string resource by the given activity and resource id.
+ */
+ @Nullable
+ private String loadResourceString(@NonNull PackageManager packageManager,
+ @NonNull ActivityInfo activityInfo, int resId) {
+ if (resId == 0) {
+ return null;
+ }
+ final CharSequence text = packageManager.getText(activityInfo.packageName,
+ resId, activityInfo.applicationInfo);
+ if (text != null) {
+ return text.toString().trim();
+ }
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int hashCode() {
+ return 31 * 1 + ((mComponentName == null) ? 0 : mComponentName.hashCode());
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ final AccessibilityShortcutInfo other = (AccessibilityShortcutInfo) obj;
+ if (mComponentName == null) {
+ if (other.mComponentName != null) {
+ return false;
+ }
+ } else if (!mComponentName.equals(other.mComponentName)) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString() {
+ StringBuilder stringBuilder = new StringBuilder();
+ stringBuilder.append("AccessibilityShortcutInfo[");
+ stringBuilder.append("activityInfo: ").append(mActivityInfo);
+ stringBuilder.append("]");
+ return stringBuilder.toString();
+ }
+}
diff --git a/android-35/android/accessibilityservice/AccessibilityTrace.java b/android-35/android/accessibilityservice/AccessibilityTrace.java
new file mode 100644
index 0000000..87304c8
--- /dev/null
+++ b/android-35/android/accessibilityservice/AccessibilityTrace.java
@@ -0,0 +1,216 @@
+/**
+ * Copyright (C) 2021 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.accessibilityservice;
+
+import java.util.AbstractMap;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Interface to log accessibility trace.
+ *
+ * @hide
+ */
+public interface AccessibilityTrace {
+ String NAME_ACCESSIBILITY_SERVICE_CONNECTION = "IAccessibilityServiceConnection";
+ String NAME_ACCESSIBILITY_SERVICE_CLIENT = "IAccessibilityServiceClient";
+ String NAME_ACCESSIBILITY_MANAGER = "IAccessibilityManager";
+ String NAME_ACCESSIBILITY_MANAGER_CLIENT = "IAccessibilityManagerClient";
+ String NAME_ACCESSIBILITY_INTERACTION_CONNECTION = "IAccessibilityInteractionConnection";
+ String NAME_ACCESSIBILITY_INTERACTION_CONNECTION_CALLBACK =
+ "IAccessibilityInteractionConnectionCallback";
+ String NAME_REMOTE_MAGNIFICATION_ANIMATION_CALLBACK = "IRemoteMagnificationAnimationCallback";
+ String NAME_MAGNIFICATION_CONNECTION = "IMagnificationConnection";
+ String NAME_MAGNIFICATION_CONNECTION_CALLBACK = "IMagnificationConnectionCallback";
+ String NAME_WINDOW_MANAGER_INTERNAL = "WindowManagerInternal";
+ String NAME_WINDOWS_FOR_ACCESSIBILITY_CALLBACK = "WindowsForAccessibilityCallback";
+ String NAME_MAGNIFICATION_CALLBACK = "MagnificationCallbacks";
+ String NAME_INPUT_FILTER = "InputFilter";
+ String NAME_GESTURE = "Gesture";
+ String NAME_ACCESSIBILITY_SERVICE = "AccessibilityService";
+ String NAME_PACKAGE_BROADCAST_RECEIVER = "PMBroadcastReceiver";
+ String NAME_USER_BROADCAST_RECEIVER = "UserBroadcastReceiver";
+ String NAME_FINGERPRINT = "FingerprintGesture";
+ String NAME_ACCESSIBILITY_INTERACTION_CLIENT = "AccessibilityInteractionClient";
+
+ String NAME_ALL_LOGGINGS = "AllLoggings";
+ String NAME_NONE = "None";
+
+ long FLAGS_ACCESSIBILITY_SERVICE_CONNECTION = 0x0000000000000001L;
+ long FLAGS_ACCESSIBILITY_SERVICE_CLIENT = 0x0000000000000002L;
+ long FLAGS_ACCESSIBILITY_MANAGER = 0x0000000000000004L;
+ long FLAGS_ACCESSIBILITY_MANAGER_CLIENT = 0x0000000000000008L;
+ long FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION = 0x0000000000000010L;
+ long FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION_CALLBACK = 0x0000000000000020L;
+ long FLAGS_REMOTE_MAGNIFICATION_ANIMATION_CALLBACK = 0x0000000000000040L;
+ long FLAGS_MAGNIFICATION_CONNECTION = 0x0000000000000080L;
+ long FLAGS_MAGNIFICATION_CONNECTION_CALLBACK = 0x0000000000000100L;
+ long FLAGS_WINDOW_MANAGER_INTERNAL = 0x0000000000000200L;
+ long FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK = 0x0000000000000400L;
+ long FLAGS_MAGNIFICATION_CALLBACK = 0x0000000000000800L;
+ long FLAGS_INPUT_FILTER = 0x0000000000001000L;
+ long FLAGS_GESTURE = 0x0000000000002000L;
+ long FLAGS_ACCESSIBILITY_SERVICE = 0x0000000000004000L;
+ long FLAGS_PACKAGE_BROADCAST_RECEIVER = 0x0000000000008000L;
+ long FLAGS_USER_BROADCAST_RECEIVER = 0x0000000000010000L;
+ long FLAGS_FINGERPRINT = 0x0000000000020000L;
+ long FLAGS_ACCESSIBILITY_INTERACTION_CLIENT = 0x0000000000040000L;
+
+ long FLAGS_LOGGING_NONE = 0x0000000000000000L;
+ long FLAGS_LOGGING_ALL = 0xFFFFFFFFFFFFFFFFL;
+
+ long FLAGS_ACCESSIBILITY_MANAGER_CLIENT_STATES = FLAGS_ACCESSIBILITY_INTERACTION_CLIENT
+ | FLAGS_ACCESSIBILITY_SERVICE
+ | FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION
+ | FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION_CALLBACK;
+
+ Map<String, Long> sNamesToFlags = Map.ofEntries(
+ new AbstractMap.SimpleEntry<String, Long>(
+ NAME_ACCESSIBILITY_SERVICE_CONNECTION, FLAGS_ACCESSIBILITY_SERVICE_CONNECTION),
+ new AbstractMap.SimpleEntry<String, Long>(
+ NAME_ACCESSIBILITY_SERVICE_CLIENT, FLAGS_ACCESSIBILITY_SERVICE_CLIENT),
+ new AbstractMap.SimpleEntry<String, Long>(
+ NAME_ACCESSIBILITY_MANAGER, FLAGS_ACCESSIBILITY_MANAGER),
+ new AbstractMap.SimpleEntry<String, Long>(
+ NAME_ACCESSIBILITY_MANAGER_CLIENT, FLAGS_ACCESSIBILITY_MANAGER_CLIENT),
+ new AbstractMap.SimpleEntry<String, Long>(
+ NAME_ACCESSIBILITY_INTERACTION_CONNECTION,
+ FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION),
+ new AbstractMap.SimpleEntry<String, Long>(
+ NAME_ACCESSIBILITY_INTERACTION_CONNECTION_CALLBACK,
+ FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION_CALLBACK),
+ new AbstractMap.SimpleEntry<String, Long>(
+ NAME_REMOTE_MAGNIFICATION_ANIMATION_CALLBACK,
+ FLAGS_REMOTE_MAGNIFICATION_ANIMATION_CALLBACK),
+ new AbstractMap.SimpleEntry<String, Long>(
+ NAME_MAGNIFICATION_CONNECTION, FLAGS_MAGNIFICATION_CONNECTION),
+ new AbstractMap.SimpleEntry<String, Long>(
+ NAME_MAGNIFICATION_CONNECTION_CALLBACK,
+ FLAGS_MAGNIFICATION_CONNECTION_CALLBACK),
+ new AbstractMap.SimpleEntry<String, Long>(
+ NAME_WINDOW_MANAGER_INTERNAL, FLAGS_WINDOW_MANAGER_INTERNAL),
+ new AbstractMap.SimpleEntry<String, Long>(
+ NAME_WINDOWS_FOR_ACCESSIBILITY_CALLBACK,
+ FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK),
+ new AbstractMap.SimpleEntry<String, Long>(
+ NAME_MAGNIFICATION_CALLBACK, FLAGS_MAGNIFICATION_CALLBACK),
+ new AbstractMap.SimpleEntry<String, Long>(NAME_INPUT_FILTER, FLAGS_INPUT_FILTER),
+ new AbstractMap.SimpleEntry<String, Long>(NAME_GESTURE, FLAGS_GESTURE),
+ new AbstractMap.SimpleEntry<String, Long>(
+ NAME_ACCESSIBILITY_SERVICE, FLAGS_ACCESSIBILITY_SERVICE),
+ new AbstractMap.SimpleEntry<String, Long>(
+ NAME_PACKAGE_BROADCAST_RECEIVER, FLAGS_PACKAGE_BROADCAST_RECEIVER),
+ new AbstractMap.SimpleEntry<String, Long>(
+ NAME_USER_BROADCAST_RECEIVER, FLAGS_USER_BROADCAST_RECEIVER),
+ new AbstractMap.SimpleEntry<String, Long>(NAME_FINGERPRINT, FLAGS_FINGERPRINT),
+ new AbstractMap.SimpleEntry<String, Long>(
+ NAME_ACCESSIBILITY_INTERACTION_CLIENT, FLAGS_ACCESSIBILITY_INTERACTION_CLIENT),
+ new AbstractMap.SimpleEntry<String, Long>(NAME_NONE, FLAGS_LOGGING_NONE),
+ new AbstractMap.SimpleEntry<String, Long>(NAME_ALL_LOGGINGS, FLAGS_LOGGING_ALL));
+
+ /**
+ * Get the flags of the logging types by the given names.
+ * The names list contains logging type names in lower case.
+ */
+ static long getLoggingFlagsFromNames(List<String> names) {
+ long types = FLAGS_LOGGING_NONE;
+ for (String name : names) {
+ long flag = sNamesToFlags.get(name);
+ types |= flag;
+ }
+ return types;
+ }
+
+ /**
+ * Get the list of the names of logging types by the given flags.
+ */
+ static List<String> getNamesOfLoggingTypes(long flags) {
+ List<String> list = new ArrayList<String>();
+
+ for (Map.Entry<String, Long> entry : sNamesToFlags.entrySet()) {
+ if ((entry.getValue() & flags) != FLAGS_LOGGING_NONE) {
+ list.add(entry.getKey());
+ }
+ }
+
+ return list;
+ }
+
+ /**
+ * Whether the trace is enabled for any logging type.
+ */
+ boolean isA11yTracingEnabled();
+
+ /**
+ * Whether the trace is enabled for any of the given logging type.
+ */
+ boolean isA11yTracingEnabledForTypes(long typeIdFlags);
+
+ /**
+ * Get trace state to be sent to AccessibilityManager.
+ */
+ int getTraceStateForAccessibilityManagerClientState();
+
+ /**
+ * Start tracing for the given logging types.
+ */
+ void startTrace(long flagss);
+
+ /**
+ * Stop tracing.
+ */
+ void stopTrace();
+
+ /**
+ * Log one trace entry.
+ * @param where A string to identify this log entry, which can be used to search through the
+ * tracing file.
+ * @param loggingFlags Flags to identify which logging types this entry belongs to. This
+ * can be used to filter the log entries when generating tracing file.
+ */
+ void logTrace(String where, long loggingFlags);
+
+ /**
+ * Log one trace entry.
+ * @param where A string to identify this log entry, which can be used to filter/search
+ * through the tracing file.
+ * @param loggingFlags Flags to identify which logging types this entry belongs to. This
+ * can be used to filter the log entries when generating tracing file.
+ * @param callingParams The parameters for the method to be logged.
+ */
+ void logTrace(String where, long loggingFlags, String callingParams);
+
+ /**
+ * Log one trace entry. Accessibility services using AccessibilityInteractionClient to
+ * make screen content related requests use this API to log entry when receive callback.
+ * @param timestamp The timestamp when a callback is received.
+ * @param where A string to identify this log entry, which can be used to filter/search
+ * through the tracing file.
+ * @param loggingFlags Flags to identify which logging types this entry belongs to. This
+ * can be used to filter the log entries when generating tracing file.
+ * @param callingParams The parameters for the callback.
+ * @param processId The process id of the calling component.
+ * @param threadId The threadId of the calling component.
+ * @param callingUid The calling uid of the callback.
+ * @param callStack The call stack of the callback.
+ * @param ignoreStackElements ignore these call stack element
+ */
+ void logTrace(long timestamp, String where, long loggingFlags, String callingParams,
+ int processId, long threadId, int callingUid, StackTraceElement[] callStack,
+ Set<String> ignoreStackElements);
+}
diff --git a/android-35/android/accessibilityservice/BrailleDisplayController.java b/android-35/android/accessibilityservice/BrailleDisplayController.java
new file mode 100644
index 0000000..7334676
--- /dev/null
+++ b/android-35/android/accessibilityservice/BrailleDisplayController.java
@@ -0,0 +1,310 @@
+/*
+ * 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.accessibilityservice;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
+import android.annotation.TestApi;
+import android.bluetooth.BluetoothDevice;
+import android.hardware.usb.UsbDevice;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.view.accessibility.AccessibilityInteractionClient;
+import android.view.accessibility.Flags;
+
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+/**
+ * Used to communicate with a Braille display that supports the Braille display HID standard
+ * (usage page 0x41).
+ *
+ * <p>Only one Braille display may be connected at a time.
+ */
+// This interface doesn't actually own resources. Its I/O connections are owned, monitored,
+// and automatically closed by the system after the accessibility service is disconnected.
+@SuppressLint("NotCloseable")
+@FlaggedApi(Flags.FLAG_BRAILLE_DISPLAY_HID)
+public interface BrailleDisplayController {
+
+ /**
+ * Throw {@link IllegalStateException} if this feature's aconfig flag is disabled.
+ *
+ * @hide
+ */
+ static void checkApiFlagIsEnabled() {
+ if (!Flags.brailleDisplayHid()) {
+ throw new IllegalStateException("Flag BRAILLE_DISPLAY_HID not enabled");
+ }
+ }
+
+ /**
+ * Interface provided to {@link BrailleDisplayController} connection methods to
+ * receive callbacks from the system.
+ */
+ @FlaggedApi(Flags.FLAG_BRAILLE_DISPLAY_HID)
+ interface BrailleDisplayCallback {
+ /**
+ * The system cannot access connected HID devices.
+ */
+ @FlaggedApi(Flags.FLAG_BRAILLE_DISPLAY_HID)
+ int FLAG_ERROR_CANNOT_ACCESS = 1 << 0;
+ /**
+ * A unique Braille display matching the requested properties could not be identified.
+ */
+ @FlaggedApi(Flags.FLAG_BRAILLE_DISPLAY_HID)
+ int FLAG_ERROR_BRAILLE_DISPLAY_NOT_FOUND = 1 << 1;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, prefix = "FLAG_ERROR_", value = {
+ FLAG_ERROR_CANNOT_ACCESS,
+ FLAG_ERROR_BRAILLE_DISPLAY_NOT_FOUND,
+ })
+ @interface ErrorCode {
+ }
+
+ /**
+ * Callback to observe a successful Braille display connection.
+ *
+ * <p>The provided HID report descriptor should be used to understand the input bytes
+ * received from the Braille display via {@link #onInput} and to prepare
+ * the output sent to the Braille display via {@link #write}.
+ *
+ * @param hidDescriptor The HID report descriptor for this Braille display.
+ * @see #connect(BluetoothDevice, BrailleDisplayCallback)
+ * @see #connect(UsbDevice, BrailleDisplayCallback)
+ */
+ @FlaggedApi(Flags.FLAG_BRAILLE_DISPLAY_HID)
+ void onConnected(@NonNull byte[] hidDescriptor);
+
+ /**
+ * Callback to observe a failed Braille display connection.
+ *
+ * @param errorFlags A bitmask of error codes for the connection failure.
+ * @see #connect(BluetoothDevice, BrailleDisplayCallback)
+ * @see #connect(UsbDevice, BrailleDisplayCallback)
+ */
+ @FlaggedApi(Flags.FLAG_BRAILLE_DISPLAY_HID)
+ void onConnectionFailed(@ErrorCode int errorFlags);
+
+ /**
+ * Callback to observe input bytes from the currently connected Braille display.
+ *
+ * @param input The input bytes from the Braille display, formatted according to the HID
+ * report descriptor and the HIDRAW kernel driver.
+ */
+ @FlaggedApi(Flags.FLAG_BRAILLE_DISPLAY_HID)
+ void onInput(@NonNull byte[] input);
+
+ /**
+ * Callback to observe when the currently connected Braille display is disconnected by the
+ * system.
+ */
+ @FlaggedApi(Flags.FLAG_BRAILLE_DISPLAY_HID)
+ void onDisconnected();
+ }
+
+ /**
+ * Connects to the requested bluetooth Braille display using the Braille
+ * display HID standard (usage page 0x41).
+ *
+ * <p>If successful then the HID report descriptor will be provided to
+ * {@link BrailleDisplayCallback#onConnected}
+ * and the Braille display will start sending incoming input bytes to
+ * {@link BrailleDisplayCallback#onInput}. If there is an error in reading input
+ * then the system will disconnect the Braille display.
+ *
+ * <p>Note that the callbacks will be executed on the main thread using
+ * {@link AccessibilityService#getMainExecutor()}. To specify the execution thread, use
+ * {@link #connect(BluetoothDevice, Executor, BrailleDisplayCallback)}.
+ *
+ * @param bluetoothDevice The Braille display device.
+ * @param callback Callbacks used to provide connection results.
+ * @see BrailleDisplayCallback#onConnected
+ * @see BrailleDisplayCallback#onConnectionFailed
+ * @throws IllegalStateException if a Braille display is already connected to this controller.
+ */
+ @FlaggedApi(Flags.FLAG_BRAILLE_DISPLAY_HID)
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+ void connect(@NonNull BluetoothDevice bluetoothDevice,
+ @NonNull BrailleDisplayCallback callback);
+
+ /**
+ * Connects to the requested bluetooth Braille display using the Braille
+ * display HID standard (usage page 0x41).
+ *
+ * <p>If successful then the HID report descriptor will be provided to
+ * {@link BrailleDisplayCallback#onConnected}
+ * and the Braille display will start sending incoming input bytes to
+ * {@link BrailleDisplayCallback#onInput}. If there is an error in reading input
+ * then the system will disconnect the Braille display.
+ *
+ * @param bluetoothDevice The Braille display device.
+ * @param callbackExecutor Executor for executing the provided callbacks.
+ * @param callback Callbacks used to provide connection results.
+ * @see BrailleDisplayCallback#onConnected
+ * @see BrailleDisplayCallback#onConnectionFailed
+ * @throws IllegalStateException if a Braille display is already connected to this controller.
+ */
+ @FlaggedApi(Flags.FLAG_BRAILLE_DISPLAY_HID)
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+ void connect(@NonNull BluetoothDevice bluetoothDevice,
+ @NonNull @CallbackExecutor Executor callbackExecutor,
+ @NonNull BrailleDisplayCallback callback);
+
+ /**
+ * Connects to the requested USB Braille display using the Braille
+ * display HID standard (usage page 0x41).
+ *
+ * <p>If successful then the HID report descriptor will be provided to
+ * {@link BrailleDisplayCallback#onConnected}
+ * and the Braille display will start sending incoming input bytes to
+ * {@link BrailleDisplayCallback#onInput}. If there is an error in reading input
+ * then the system will disconnect the Braille display.
+ *
+ * <p>The accessibility service app must already have approval to access the USB device
+ * from the standard {@link android.hardware.usb.UsbManager} access approval process.
+ *
+ * <p>Note that the callbacks will be executed on the main thread using
+ * {@link AccessibilityService#getMainExecutor()}. To specify the execution thread, use
+ * {@link #connect(UsbDevice, Executor, BrailleDisplayCallback)}.
+ *
+ * @param usbDevice The Braille display device.
+ * @param callback Callbacks used to provide connection results.
+ * @see BrailleDisplayCallback#onConnected
+ * @see BrailleDisplayCallback#onConnectionFailed
+ * @throws SecurityException if the caller does not have USB device approval.
+ * @throws IllegalStateException if a Braille display is already connected to this controller.
+ */
+ @FlaggedApi(Flags.FLAG_BRAILLE_DISPLAY_HID)
+ void connect(@NonNull UsbDevice usbDevice,
+ @NonNull BrailleDisplayCallback callback);
+
+ /**
+ * Connects to the requested USB Braille display using the Braille
+ * display HID standard (usage page 0x41).
+ *
+ * <p>If successful then the HID report descriptor will be provided to
+ * {@link BrailleDisplayCallback#onConnected}
+ * and the Braille display will start sending incoming input bytes to
+ * {@link BrailleDisplayCallback#onInput}. If there is an error in reading input
+ * then the system will disconnect the Braille display.
+ *
+ * <p>The accessibility service app must already have approval to access the USB device
+ * from the standard {@link android.hardware.usb.UsbManager} access approval process.
+ *
+ * @param usbDevice The Braille display device.
+ * @param callbackExecutor Executor for executing the provided callbacks.
+ * @param callback Callbacks used to provide connection results.
+ * @see BrailleDisplayCallback#onConnected
+ * @see BrailleDisplayCallback#onConnectionFailed
+ * @throws SecurityException if the caller does not have USB device approval.
+ * @throws IllegalStateException if a Braille display is already connected to this controller.
+ */
+ @FlaggedApi(Flags.FLAG_BRAILLE_DISPLAY_HID)
+ void connect(@NonNull UsbDevice usbDevice,
+ @NonNull @CallbackExecutor Executor callbackExecutor,
+ @NonNull BrailleDisplayCallback callback);
+
+ /**
+ * Returns true if a Braille display is currently connected, otherwise false.
+ *
+ * @see #connect
+ */
+ @FlaggedApi(Flags.FLAG_BRAILLE_DISPLAY_HID)
+ boolean isConnected();
+
+ /**
+ * Writes a HID report to the currently connected Braille display.
+ *
+ * <p>This method returns immediately after dispatching the write request to the system.
+ * If the system experiences an error in writing output (e.g. the Braille display is unplugged
+ * after the system receives the write request but before writing the bytes to the Braille
+ * display) then the system will disconnect the Braille display, which calls
+ * {@link BrailleDisplayCallback#onDisconnected()}.
+ *
+ * @param buffer The bytes to write to the Braille display. These bytes should be formatted
+ * according to the HID report descriptor and the HIDRAW kernel driver.
+ * @throws IOException if there is no currently connected Braille display.
+ * @throws IllegalArgumentException if the buffer exceeds the maximum safe payload size for
+ * binder transactions of
+ * {@link IBinder#getSuggestedMaxIpcSizeBytes()}
+ */
+ @FlaggedApi(Flags.FLAG_BRAILLE_DISPLAY_HID)
+ void write(@NonNull byte[] buffer) throws IOException;
+
+ /**
+ * Disconnects from the currently connected Braille display.
+ *
+ * @see #isConnected()
+ */
+ @FlaggedApi(Flags.FLAG_BRAILLE_DISPLAY_HID)
+ void disconnect();
+
+ /**
+ * Provides test Braille display data to be used for automated CTS tests.
+ *
+ * <p>See {@code TEST_BRAILLE_DISPLAY_*} bundle keys.
+ *
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_BRAILLE_DISPLAY_HID)
+ @RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY)
+ @TestApi
+ static void setTestBrailleDisplayData(
+ @NonNull AccessibilityService service,
+ @NonNull List<Bundle> brailleDisplays) {
+ checkApiFlagIsEnabled();
+ final IAccessibilityServiceConnection serviceConnection =
+ AccessibilityInteractionClient.getConnection(service.getConnectionId());
+ if (serviceConnection != null) {
+ try {
+ serviceConnection.setTestBrailleDisplayData(brailleDisplays);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /** @hide */
+ @FlaggedApi(Flags.FLAG_BRAILLE_DISPLAY_HID)
+ @TestApi
+ String TEST_BRAILLE_DISPLAY_HIDRAW_PATH = "HIDRAW_PATH";
+ /** @hide */
+ @FlaggedApi(Flags.FLAG_BRAILLE_DISPLAY_HID)
+ @TestApi
+ String TEST_BRAILLE_DISPLAY_DESCRIPTOR = "DESCRIPTOR";
+ /** @hide */
+ @FlaggedApi(Flags.FLAG_BRAILLE_DISPLAY_HID)
+ @TestApi
+ String TEST_BRAILLE_DISPLAY_BUS_BLUETOOTH = "BUS_BLUETOOTH";
+ /** @hide */
+ @FlaggedApi(Flags.FLAG_BRAILLE_DISPLAY_HID)
+ @TestApi
+ String TEST_BRAILLE_DISPLAY_UNIQUE_ID = "UNIQUE_ID";
+ /** @hide */
+ String TEST_BRAILLE_DISPLAY_NAME = "NAME";
+}
diff --git a/android-35/android/accessibilityservice/BrailleDisplayControllerImpl.java b/android-35/android/accessibilityservice/BrailleDisplayControllerImpl.java
new file mode 100644
index 0000000..f1df336
--- /dev/null
+++ b/android-35/android/accessibilityservice/BrailleDisplayControllerImpl.java
@@ -0,0 +1,296 @@
+/*
+ * 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.accessibilityservice;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.bluetooth.BluetoothDevice;
+import android.hardware.usb.UsbDevice;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.SystemProperties;
+import android.view.accessibility.AccessibilityInteractionClient;
+import android.view.accessibility.Flags;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FunctionalUtils;
+
+import java.io.IOException;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/**
+ * Default implementation of {@link BrailleDisplayController}.
+ *
+ * @hide
+ */
+// BrailleDisplayControllerImpl is not an API, but it implements BrailleDisplayController APIs.
+// This @FlaggedApi annotation tells the linter that this method delegates API checks to its
+// callers.
+@FlaggedApi(Flags.FLAG_BRAILLE_DISPLAY_HID)
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+public final class BrailleDisplayControllerImpl implements BrailleDisplayController {
+
+ private final AccessibilityService mAccessibilityService;
+ private final Object mLock;
+ private final boolean mIsHidrawSupported;
+
+ private IBrailleDisplayConnection mBrailleDisplayConnection;
+ private Executor mCallbackExecutor;
+ private BrailleDisplayCallback mCallback;
+
+ /**
+ * Read-only property that returns whether HIDRAW access is supported on this device.
+ *
+ * <p>Defaults to true.
+ *
+ * <p>Device manufacturers without HIDRAW kernel support can set this to false in
+ * the device's product makefile.
+ */
+ private static final boolean IS_HIDRAW_SUPPORTED = SystemProperties.getBoolean(
+ "ro.accessibility.support_hidraw", true);
+
+ BrailleDisplayControllerImpl(AccessibilityService accessibilityService,
+ Object lock) {
+ this(accessibilityService, lock, IS_HIDRAW_SUPPORTED);
+ }
+
+ @VisibleForTesting
+ public BrailleDisplayControllerImpl(AccessibilityService accessibilityService,
+ Object lock, boolean isHidrawSupported) {
+ mAccessibilityService = accessibilityService;
+ mLock = lock;
+ mIsHidrawSupported = isHidrawSupported;
+ }
+
+ @Override
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+ public void connect(@NonNull BluetoothDevice bluetoothDevice,
+ @NonNull BrailleDisplayCallback callback) {
+ connect(bluetoothDevice, mAccessibilityService.getMainExecutor(), callback);
+ }
+
+ @Override
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+ public void connect(@NonNull BluetoothDevice bluetoothDevice,
+ @NonNull @CallbackExecutor Executor callbackExecutor,
+ @NonNull BrailleDisplayCallback callback) {
+ Objects.requireNonNull(bluetoothDevice);
+ Objects.requireNonNull(callbackExecutor);
+ Objects.requireNonNull(callback);
+ connect(serviceConnection -> serviceConnection.connectBluetoothBrailleDisplay(
+ bluetoothDevice.getAddress(), new IBrailleDisplayControllerWrapper()),
+ callbackExecutor, callback);
+ }
+
+ @Override
+ public void connect(@NonNull UsbDevice usbDevice,
+ @NonNull BrailleDisplayCallback callback) {
+ connect(usbDevice, mAccessibilityService.getMainExecutor(), callback);
+ }
+
+ @Override
+ public void connect(@NonNull UsbDevice usbDevice,
+ @NonNull @CallbackExecutor Executor callbackExecutor,
+ @NonNull BrailleDisplayCallback callback) {
+ Objects.requireNonNull(usbDevice);
+ Objects.requireNonNull(callbackExecutor);
+ Objects.requireNonNull(callback);
+ connect(serviceConnection -> serviceConnection.connectUsbBrailleDisplay(
+ usbDevice, new IBrailleDisplayControllerWrapper()),
+ callbackExecutor, callback);
+ }
+
+ /**
+ * Shared implementation for the {@code connect()} API methods.
+ *
+ * <p>Performs a blocking call to system_server to create the connection. Success is
+ * returned through {@link BrailleDisplayCallback#onConnected} while normal connection
+ * errors are returned through {@link BrailleDisplayCallback#onConnectionFailed}. This
+ * connection is implemented using cached data from the HIDRAW driver so it returns
+ * quickly without needing to perform any I/O with the Braille display.
+ *
+ * <p>The AIDL call to system_server is blocking (not posted to a handler thread) so
+ * that runtime exceptions signaling abnormal connection errors from API misuse
+ * (e.g. lacking permissions, providing an invalid BluetoothDevice, calling connect
+ * while already connected) are propagated to the API caller.
+ */
+ private void connect(
+ FunctionalUtils.RemoteExceptionIgnoringConsumer<IAccessibilityServiceConnection>
+ createConnection,
+ @NonNull Executor callbackExecutor, @NonNull BrailleDisplayCallback callback) {
+ BrailleDisplayController.checkApiFlagIsEnabled();
+ if (!mIsHidrawSupported) {
+ callbackExecutor.execute(() -> callback.onConnectionFailed(
+ BrailleDisplayCallback.FLAG_ERROR_CANNOT_ACCESS));
+ return;
+ }
+ if (isConnected()) {
+ throw new IllegalStateException(
+ "This service already has a connected Braille display");
+ }
+ final IAccessibilityServiceConnection serviceConnection =
+ AccessibilityInteractionClient.getConnection(
+ mAccessibilityService.getConnectionId());
+ if (serviceConnection == null) {
+ throw new IllegalStateException("Accessibility service is not connected");
+ }
+ synchronized (mLock) {
+ mCallbackExecutor = callbackExecutor;
+ mCallback = callback;
+ }
+ try {
+ createConnection.acceptOrThrow(serviceConnection);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @Override
+ public boolean isConnected() {
+ BrailleDisplayController.checkApiFlagIsEnabled();
+ return mBrailleDisplayConnection != null;
+ }
+
+ @Override
+ public void write(@NonNull byte[] buffer) throws IOException {
+ BrailleDisplayController.checkApiFlagIsEnabled();
+ Objects.requireNonNull(buffer);
+ if (buffer.length > IBinder.getSuggestedMaxIpcSizeBytes()) {
+ // This same check must be performed in the system to prevent reflection misuse,
+ // but perform it here too to prevent unnecessary IPCs from non-reflection callers.
+ throw new IllegalArgumentException("Invalid write buffer size " + buffer.length);
+ }
+ synchronized (mLock) {
+ if (mBrailleDisplayConnection == null) {
+ throw new IOException("Braille display is not connected");
+ }
+ try {
+ mBrailleDisplayConnection.write(buffer);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ @Override
+ public void disconnect() {
+ BrailleDisplayController.checkApiFlagIsEnabled();
+ synchronized (mLock) {
+ try {
+ if (mBrailleDisplayConnection != null) {
+ mBrailleDisplayConnection.disconnect();
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } finally {
+ clearConnectionLocked();
+ }
+ }
+ }
+
+ /**
+ * Implementation of the {@code IBrailleDisplayController} AIDL interface provided to
+ * system_server, which system_server uses to pass messages back to this
+ * {@code BrailleDisplayController}.
+ *
+ * <p>Messages from system_server are routed to the {@link BrailleDisplayCallback} callbacks
+ * implemented by the accessibility service.
+ *
+ * <p>Note: Per API Guidelines 7.5 the Binder identity must be cleared before invoking the
+ * callback executor so that Binder identity checks in the callbacks are performed using the
+ * app's identity.
+ */
+ private final class IBrailleDisplayControllerWrapper extends IBrailleDisplayController.Stub {
+ /**
+ * Called when the system successfully connects to a Braille display.
+ */
+ @Override
+ public void onConnected(IBrailleDisplayConnection connection, byte[] hidDescriptor) {
+ BrailleDisplayController.checkApiFlagIsEnabled();
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ mBrailleDisplayConnection = connection;
+ mCallbackExecutor.execute(() -> mCallback.onConnected(hidDescriptor));
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ /**
+ * Called when the system is unable to connect to a Braille display.
+ */
+ @Override
+ public void onConnectionFailed(@BrailleDisplayCallback.ErrorCode int errorCode) {
+ BrailleDisplayController.checkApiFlagIsEnabled();
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ mCallbackExecutor.execute(() -> mCallback.onConnectionFailed(errorCode));
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ /**
+ * Called when input is received from the currently connected Braille display.
+ */
+ @Override
+ public void onInput(byte[] input) {
+ BrailleDisplayController.checkApiFlagIsEnabled();
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ // Ignore input that arrives after disconnection.
+ if (mBrailleDisplayConnection != null) {
+ mCallbackExecutor.execute(() -> mCallback.onInput(input));
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ /**
+ * Called when the currently connected Braille display is disconnected.
+ */
+ @Override
+ public void onDisconnected() {
+ BrailleDisplayController.checkApiFlagIsEnabled();
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ mCallbackExecutor.execute(mCallback::onDisconnected);
+ clearConnectionLocked();
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
+
+ private void clearConnectionLocked() {
+ mBrailleDisplayConnection = null;
+ }
+
+}
diff --git a/android-35/android/accessibilityservice/FingerprintGestureController.java b/android-35/android/accessibilityservice/FingerprintGestureController.java
new file mode 100644
index 0000000..c30030d
--- /dev/null
+++ b/android-35/android/accessibilityservice/FingerprintGestureController.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2017 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.accessibilityservice;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.Log;
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * An {@link AccessibilityService} can capture gestures performed on a device's fingerprint
+ * sensor, as long as the device has a sensor capable of detecting gestures.
+ * <p>
+ * This capability must be declared by the service as
+ * {@link AccessibilityServiceInfo#CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES}. It also requires
+ * the permission {@link android.Manifest.permission#USE_FINGERPRINT}.
+ * <p>
+ * Because capturing fingerprint gestures may have side effects, services with the capability only
+ * capture gestures when {@link AccessibilityServiceInfo#FLAG_REQUEST_FINGERPRINT_GESTURES} is set.
+ * <p>
+ * <strong>Note: </strong>The fingerprint sensor is used for authentication in critical use cases,
+ * so services must carefully design their user's experience when performing gestures on the sensor.
+ * When the sensor is in use by an app, for example, when authenticating or enrolling a user,
+ * the sensor will not detect gestures. Services need to ensure that users understand when the
+ * sensor is in-use for authentication to prevent users from authenticating unintentionally when
+ * trying to interact with the service. They can use
+ * {@link FingerprintGestureCallback#onGestureDetectionAvailabilityChanged(boolean)} to learn when
+ * gesture detection becomes unavailable.
+ * <p>
+ * Multiple accessibility services may listen for fingerprint gestures simultaneously, so services
+ * should provide a way for the user to disable the use of this feature so multiple services don't
+ * conflict with each other.
+ * <p>
+ * {@see android.hardware.fingerprint.FingerprintManager#isHardwareDetected}
+ */
+public final class FingerprintGestureController {
+ /** Identifier for a swipe right on the fingerprint sensor */
+ public static final int FINGERPRINT_GESTURE_SWIPE_RIGHT = 0x00000001;
+
+ /** Identifier for a swipe left on the fingerprint sensor */
+ public static final int FINGERPRINT_GESTURE_SWIPE_LEFT = 0x00000002;
+
+ /** Identifier for a swipe up on the fingerprint sensor */
+ public static final int FINGERPRINT_GESTURE_SWIPE_UP = 0x00000004;
+
+ /** Identifier for a swipe down on the fingerprint sensor */
+ public static final int FINGERPRINT_GESTURE_SWIPE_DOWN = 0x00000008;
+
+ private static final String LOG_TAG = "FingerprintGestureController";
+ private final Object mLock = new Object();
+ private final IAccessibilityServiceConnection mAccessibilityServiceConnection;
+
+ private final ArrayMap<FingerprintGestureCallback, Handler> mCallbackHandlerMap =
+ new ArrayMap<>(1);
+
+ /**
+ * @param connection The connection to use for system interactions
+ * @hide
+ */
+ @VisibleForTesting
+ public FingerprintGestureController(IAccessibilityServiceConnection connection) {
+ mAccessibilityServiceConnection = connection;
+ }
+
+ /**
+ * Gets if the fingerprint sensor's gesture detection is available.
+ *
+ * @return {@code true} if the sensor's gesture detection is available. {@code false} if it is
+ * not currently detecting gestures (for example, if it is enrolling a finger).
+ */
+ public boolean isGestureDetectionAvailable() {
+ try {
+ return mAccessibilityServiceConnection.isFingerprintGestureDetectionAvailable();
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Failed to check if fingerprint gestures are active", re);
+ re.rethrowFromSystemServer();
+ return false;
+ }
+ }
+
+ /**
+ * Register a callback to be informed of fingerprint sensor gesture events.
+ *
+ * @param callback The listener to be added.
+ * @param handler The handler to use for the callback. If {@code null}, callbacks will happen
+ * on the service's main thread.
+ */
+ public void registerFingerprintGestureCallback(
+ @NonNull FingerprintGestureCallback callback, @Nullable Handler handler) {
+ synchronized (mLock) {
+ mCallbackHandlerMap.put(callback, handler);
+ }
+ }
+
+ /**
+ * Unregister a listener added with {@link #registerFingerprintGestureCallback}.
+ *
+ * @param callback The callback to remove. Removing a callback that was never added has no
+ * effect.
+ */
+ public void unregisterFingerprintGestureCallback(FingerprintGestureCallback callback) {
+ synchronized (mLock) {
+ mCallbackHandlerMap.remove(callback);
+ }
+ }
+
+ /**
+ * Called when gesture detection becomes active or inactive
+ * @hide
+ */
+ public void onGestureDetectionActiveChanged(boolean active) {
+ final ArrayMap<FingerprintGestureCallback, Handler> handlerMap;
+ synchronized (mLock) {
+ handlerMap = new ArrayMap<>(mCallbackHandlerMap);
+ }
+ int numListeners = handlerMap.size();
+ for (int i = 0; i < numListeners; i++) {
+ FingerprintGestureCallback callback = handlerMap.keyAt(i);
+ Handler handler = handlerMap.valueAt(i);
+ if (handler != null) {
+ handler.post(() -> callback.onGestureDetectionAvailabilityChanged(active));
+ } else {
+ callback.onGestureDetectionAvailabilityChanged(active);
+ }
+ }
+ }
+
+ /**
+ * Called when gesture is detected.
+ * @hide
+ */
+ public void onGesture(int gesture) {
+ final ArrayMap<FingerprintGestureCallback, Handler> handlerMap;
+ synchronized (mLock) {
+ handlerMap = new ArrayMap<>(mCallbackHandlerMap);
+ }
+ int numListeners = handlerMap.size();
+ for (int i = 0; i < numListeners; i++) {
+ FingerprintGestureCallback callback = handlerMap.keyAt(i);
+ Handler handler = handlerMap.valueAt(i);
+ if (handler != null) {
+ handler.post(() -> callback.onGestureDetected(gesture));
+ } else {
+ callback.onGestureDetected(gesture);
+ }
+ }
+ }
+
+ /**
+ * Class that is called back when fingerprint gestures are being used for accessibility.
+ */
+ public abstract static class FingerprintGestureCallback {
+ /**
+ * Called when the fingerprint sensor's gesture detection becomes available or unavailable.
+ *
+ * @param available Whether or not the sensor's gesture detection is now available.
+ */
+ public void onGestureDetectionAvailabilityChanged(boolean available) {}
+
+ /**
+ * Called when the fingerprint sensor detects gestures.
+ *
+ * @param gesture The id of the gesture that was detected. For example,
+ * {@link #FINGERPRINT_GESTURE_SWIPE_RIGHT}.
+ */
+ public void onGestureDetected(int gesture) {}
+ }
+}
diff --git a/android-35/android/accessibilityservice/GestureDescription.java b/android-35/android/accessibilityservice/GestureDescription.java
new file mode 100644
index 0000000..857c541
--- /dev/null
+++ b/android-35/android/accessibilityservice/GestureDescription.java
@@ -0,0 +1,603 @@
+/*
+ * Copyright (C) 2015 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.accessibilityservice;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.graphics.Path;
+import android.graphics.PathMeasure;
+import android.graphics.RectF;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.Display;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Accessibility services with the
+ * {@link android.R.styleable#AccessibilityService_canPerformGestures} property can dispatch
+ * gestures. This class describes those gestures. Gestures are made up of one or more strokes.
+ * Gestures are immutable once built and will be dispatched to the specified display.
+ * <p>
+ * Spatial dimensions throughout are in screen pixels. Time is measured in milliseconds.
+ */
+public final class GestureDescription {
+ /** Gestures may contain no more than this many strokes */
+ private static final int MAX_STROKE_COUNT = 20;
+
+ /**
+ * Upper bound on total gesture duration. Nearly all gestures will be much shorter.
+ */
+ private static final long MAX_GESTURE_DURATION_MS = 60 * 1000;
+
+ private final List<StrokeDescription> mStrokes = new ArrayList<>();
+ private final float[] mTempPos = new float[2];
+ private final int mDisplayId;
+
+ /**
+ * Get the upper limit for the number of strokes a gesture may contain.
+ *
+ * @return The maximum number of strokes.
+ */
+ public static int getMaxStrokeCount() {
+ return MAX_STROKE_COUNT;
+ }
+
+ /**
+ * Get the upper limit on a gesture's duration.
+ *
+ * @return The maximum duration in milliseconds.
+ */
+ public static long getMaxGestureDuration() {
+ return MAX_GESTURE_DURATION_MS;
+ }
+
+ private GestureDescription() {
+ this(new ArrayList<>());
+ }
+
+ private GestureDescription(List<StrokeDescription> strokes) {
+ this(strokes, Display.DEFAULT_DISPLAY);
+ }
+
+ private GestureDescription(List<StrokeDescription> strokes, int displayId) {
+ mStrokes.addAll(strokes);
+ mDisplayId = displayId;
+ }
+
+ /**
+ * Get the number of stroke in the gesture.
+ *
+ * @return the number of strokes in this gesture
+ */
+ public int getStrokeCount() {
+ return mStrokes.size();
+ }
+
+ /**
+ * Read a stroke from the gesture
+ *
+ * @param index the index of the stroke
+ *
+ * @return A description of the stroke.
+ */
+ public StrokeDescription getStroke(@IntRange(from = 0) int index) {
+ return mStrokes.get(index);
+ }
+
+ /**
+ * Returns the ID of the display this gesture is sent on, for use with
+ * {@link android.hardware.display.DisplayManager#getDisplay(int)}.
+ *
+ * @return The logical display id.
+ */
+ public int getDisplayId() {
+ return mDisplayId;
+ }
+
+ /**
+ * Return the smallest key point (where a path starts or ends) that is at least a specified
+ * offset
+ * @param offset the minimum start time
+ * @return The next key time that is at least the offset or -1 if one can't be found
+ */
+ private long getNextKeyPointAtLeast(long offset) {
+ long nextKeyPoint = Long.MAX_VALUE;
+ for (int i = 0; i < mStrokes.size(); i++) {
+ long thisStartTime = mStrokes.get(i).mStartTime;
+ if ((thisStartTime < nextKeyPoint) && (thisStartTime >= offset)) {
+ nextKeyPoint = thisStartTime;
+ }
+ long thisEndTime = mStrokes.get(i).mEndTime;
+ if ((thisEndTime < nextKeyPoint) && (thisEndTime >= offset)) {
+ nextKeyPoint = thisEndTime;
+ }
+ }
+ return (nextKeyPoint == Long.MAX_VALUE) ? -1L : nextKeyPoint;
+ }
+
+ /**
+ * Get the points that correspond to a particular moment in time.
+ * @param time The time of interest
+ * @param touchPoints An array to hold the current touch points. Must be preallocated to at
+ * least the number of paths in the gesture to prevent going out of bounds
+ * @return The number of points found, and thus the number of elements set in each array
+ */
+ private int getPointsForTime(long time, TouchPoint[] touchPoints) {
+ int numPointsFound = 0;
+ for (int i = 0; i < mStrokes.size(); i++) {
+ StrokeDescription strokeDescription = mStrokes.get(i);
+ if (strokeDescription.hasPointForTime(time)) {
+ touchPoints[numPointsFound].mStrokeId = strokeDescription.getId();
+ touchPoints[numPointsFound].mContinuedStrokeId =
+ strokeDescription.getContinuedStrokeId();
+ touchPoints[numPointsFound].mIsStartOfPath =
+ (strokeDescription.getContinuedStrokeId() < 0)
+ && (time == strokeDescription.mStartTime);
+ touchPoints[numPointsFound].mIsEndOfPath = !strokeDescription.willContinue()
+ && (time == strokeDescription.mEndTime);
+ strokeDescription.getPosForTime(time, mTempPos);
+ touchPoints[numPointsFound].mX = Math.round(mTempPos[0]);
+ touchPoints[numPointsFound].mY = Math.round(mTempPos[1]);
+ numPointsFound++;
+ }
+ }
+ return numPointsFound;
+ }
+
+ // Total duration assumes that the gesture starts at 0; waiting around to start a gesture
+ // counts against total duration
+ private static long getTotalDuration(List<StrokeDescription> paths) {
+ long latestEnd = Long.MIN_VALUE;
+ for (int i = 0; i < paths.size(); i++) {
+ StrokeDescription path = paths.get(i);
+ latestEnd = Math.max(latestEnd, path.mEndTime);
+ }
+ return Math.max(latestEnd, 0);
+ }
+
+ /**
+ * Builder for a {@code GestureDescription}
+ */
+ public static class Builder {
+
+ private final List<StrokeDescription> mStrokes = new ArrayList<>();
+ private int mDisplayId = Display.DEFAULT_DISPLAY;
+
+ /**
+ * Adds a stroke to the gesture description. Up to
+ * {@link GestureDescription#getMaxStrokeCount()} paths may be
+ * added to a gesture, and the total gesture duration (earliest path start time to latest
+ * path end time) may not exceed {@link GestureDescription#getMaxGestureDuration()}.
+ *
+ * @param strokeDescription the stroke to add.
+ *
+ * @return this
+ */
+ public Builder addStroke(@NonNull StrokeDescription strokeDescription) {
+ if (mStrokes.size() >= MAX_STROKE_COUNT) {
+ throw new IllegalStateException(
+ "Attempting to add too many strokes to a gesture. Maximum is "
+ + MAX_STROKE_COUNT
+ + ", got "
+ + mStrokes.size());
+ }
+
+ mStrokes.add(strokeDescription);
+
+ if (getTotalDuration(mStrokes) > MAX_GESTURE_DURATION_MS) {
+ mStrokes.remove(strokeDescription);
+ throw new IllegalStateException(
+ "Gesture would exceed maximum duration with new stroke");
+ }
+ return this;
+ }
+
+ /**
+ * Sets the id of the display to dispatch gestures.
+ *
+ * @param displayId The logical display id
+ *
+ * @return this
+ */
+ public @NonNull Builder setDisplayId(int displayId) {
+ mDisplayId = displayId;
+ return this;
+ }
+
+ public GestureDescription build() {
+ if (mStrokes.size() == 0) {
+ throw new IllegalStateException("Gestures must have at least one stroke");
+ }
+ return new GestureDescription(mStrokes, mDisplayId);
+ }
+ }
+
+ /**
+ * Immutable description of stroke that can be part of a gesture.
+ */
+ public static class StrokeDescription {
+ private static final int INVALID_STROKE_ID = -1;
+
+ static int sIdCounter;
+
+ Path mPath;
+ long mStartTime;
+ long mEndTime;
+ private float mTimeToLengthConversion;
+ private PathMeasure mPathMeasure;
+ // The tap location is only set for zero-length paths
+ float[] mTapLocation;
+ int mId;
+ boolean mContinued;
+ int mContinuedStrokeId = INVALID_STROKE_ID;
+
+ /**
+ * @param path The path to follow. Must have exactly one contour. The bounds of the path
+ * must not be negative. The path must not be empty. If the path has zero length
+ * (for example, a single {@code moveTo()}), the stroke is a touch that doesn't move.
+ * @param startTime The time, in milliseconds, from the time the gesture starts to the
+ * time the stroke should start. Must not be negative.
+ * @param duration The duration, in milliseconds, the stroke takes to traverse the path.
+ * Must be positive.
+ */
+ public StrokeDescription(@NonNull Path path,
+ @IntRange(from = 0) long startTime,
+ @IntRange(from = 0) long duration) {
+ this(path, startTime, duration, false);
+ }
+
+ /**
+ * @param path The path to follow. Must have exactly one contour. The bounds of the path
+ * must not be negative. The path must not be empty. If the path has zero length
+ * (for example, a single {@code moveTo()}), the stroke is a touch that doesn't move.
+ * @param startTime The time, in milliseconds, from the time the gesture starts to the
+ * time the stroke should start. Must not be negative.
+ * @param duration The duration, in milliseconds, the stroke takes to traverse the path.
+ * Must be positive.
+ * @param willContinue {@code true} if this stroke will be continued by one in the
+ * next gesture {@code false} otherwise. Continued strokes keep their pointers down when
+ * the gesture completes.
+ */
+ public StrokeDescription(@NonNull Path path,
+ @IntRange(from = 0) long startTime,
+ @IntRange(from = 0) long duration,
+ boolean willContinue) {
+ mContinued = willContinue;
+ Preconditions.checkArgument(duration > 0, "Duration must be positive");
+ Preconditions.checkArgument(startTime >= 0, "Start time must not be negative");
+ Preconditions.checkArgument(!path.isEmpty(), "Path is empty");
+ RectF bounds = new RectF();
+ path.computeBounds(bounds, false /* unused */);
+ Preconditions.checkArgument((bounds.bottom >= 0) && (bounds.top >= 0)
+ && (bounds.right >= 0) && (bounds.left >= 0),
+ "Path bounds must not be negative");
+ mPath = new Path(path);
+ mPathMeasure = new PathMeasure(path, false);
+ if (mPathMeasure.getLength() == 0) {
+ // Treat zero-length paths as taps
+ Path tempPath = new Path(path);
+ tempPath.lineTo(-1, -1);
+ mTapLocation = new float[2];
+ PathMeasure pathMeasure = new PathMeasure(tempPath, false);
+ pathMeasure.getPosTan(0, mTapLocation, null);
+ }
+ if (mPathMeasure.nextContour()) {
+ throw new IllegalArgumentException("Path has more than one contour");
+ }
+ /*
+ * Calling nextContour has moved mPathMeasure off the first contour, which is the only
+ * one we care about. Set the path again to go back to the first contour.
+ */
+ mPathMeasure.setPath(mPath, false);
+ mStartTime = startTime;
+ mEndTime = startTime + duration;
+ mTimeToLengthConversion = getLength() / duration;
+ mId = sIdCounter++;
+ }
+
+ /**
+ * Retrieve a copy of the path for this stroke
+ *
+ * @return A copy of the path
+ */
+ public Path getPath() {
+ return new Path(mPath);
+ }
+
+ /**
+ * Get the stroke's start time
+ *
+ * @return the start time for this stroke.
+ */
+ public long getStartTime() {
+ return mStartTime;
+ }
+
+ /**
+ * Get the stroke's duration
+ *
+ * @return the duration for this stroke
+ */
+ public long getDuration() {
+ return mEndTime - mStartTime;
+ }
+
+ /**
+ * Get the stroke's ID. The ID is used when a stroke is to be continued by another
+ * stroke in a future gesture.
+ *
+ * @return the ID of this stroke
+ * @hide
+ */
+ public int getId() {
+ return mId;
+ }
+
+ /**
+ * Create a new stroke that will continue this one. This is only possible if this stroke
+ * will continue.
+ *
+ * @param path The path for the stroke that continues this one. The starting point of
+ * this path must match the ending point of the stroke it continues.
+ * @param startTime The time, in milliseconds, from the time the gesture starts to the
+ * time this stroke should start. Must not be negative. This time is from
+ * the start of the new gesture, not the one being continued.
+ * @param duration The duration for the new stroke. Must not be negative.
+ * @param willContinue {@code true} if this stroke will be continued by one in the
+ * next gesture {@code false} otherwise.
+ * @return
+ */
+ public StrokeDescription continueStroke(Path path, long startTime, long duration,
+ boolean willContinue) {
+ if (!mContinued) {
+ throw new IllegalStateException(
+ "Only strokes marked willContinue can be continued");
+ }
+ StrokeDescription strokeDescription =
+ new StrokeDescription(path, startTime, duration, willContinue);
+ strokeDescription.mContinuedStrokeId = mId;
+ return strokeDescription;
+ }
+
+ /**
+ * Check if this stroke is marked to continue in the next gesture.
+ *
+ * @return {@code true} if the stroke is to be continued.
+ */
+ public boolean willContinue() {
+ return mContinued;
+ }
+
+ /**
+ * Get the ID of the stroke that this one will continue.
+ *
+ * @return The ID of the stroke that this stroke continues, or 0 if no such stroke exists.
+ * @hide
+ */
+ public int getContinuedStrokeId() {
+ return mContinuedStrokeId;
+ }
+
+ float getLength() {
+ return mPathMeasure.getLength();
+ }
+
+ /* Assumes hasPointForTime returns true */
+ boolean getPosForTime(long time, float[] pos) {
+ if (mTapLocation != null) {
+ pos[0] = mTapLocation[0];
+ pos[1] = mTapLocation[1];
+ return true;
+ }
+ if (time == mEndTime) {
+ // Close to the end time, roundoff can be a problem
+ return mPathMeasure.getPosTan(getLength(), pos, null);
+ }
+ float length = mTimeToLengthConversion * ((float) (time - mStartTime));
+ return mPathMeasure.getPosTan(length, pos, null);
+ }
+
+ boolean hasPointForTime(long time) {
+ return ((time >= mStartTime) && (time <= mEndTime));
+ }
+ }
+
+ /**
+ * The location of a finger for gesture dispatch
+ *
+ * @hide
+ */
+ public static class TouchPoint implements Parcelable {
+ private static final int FLAG_IS_START_OF_PATH = 0x01;
+ private static final int FLAG_IS_END_OF_PATH = 0x02;
+
+ public int mStrokeId;
+ public int mContinuedStrokeId;
+ public boolean mIsStartOfPath;
+ public boolean mIsEndOfPath;
+ public float mX;
+ public float mY;
+
+ public TouchPoint() {
+ }
+
+ public TouchPoint(TouchPoint pointToCopy) {
+ copyFrom(pointToCopy);
+ }
+
+ public TouchPoint(Parcel parcel) {
+ mStrokeId = parcel.readInt();
+ mContinuedStrokeId = parcel.readInt();
+ int startEnd = parcel.readInt();
+ mIsStartOfPath = (startEnd & FLAG_IS_START_OF_PATH) != 0;
+ mIsEndOfPath = (startEnd & FLAG_IS_END_OF_PATH) != 0;
+ mX = parcel.readFloat();
+ mY = parcel.readFloat();
+ }
+
+ public void copyFrom(TouchPoint other) {
+ mStrokeId = other.mStrokeId;
+ mContinuedStrokeId = other.mContinuedStrokeId;
+ mIsStartOfPath = other.mIsStartOfPath;
+ mIsEndOfPath = other.mIsEndOfPath;
+ mX = other.mX;
+ mY = other.mY;
+ }
+
+ @Override
+ public String toString() {
+ return "TouchPoint{"
+ + "mStrokeId=" + mStrokeId
+ + ", mContinuedStrokeId=" + mContinuedStrokeId
+ + ", mIsStartOfPath=" + mIsStartOfPath
+ + ", mIsEndOfPath=" + mIsEndOfPath
+ + ", mX=" + mX
+ + ", mY=" + mY
+ + '}';
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mStrokeId);
+ dest.writeInt(mContinuedStrokeId);
+ int startEnd = mIsStartOfPath ? FLAG_IS_START_OF_PATH : 0;
+ startEnd |= mIsEndOfPath ? FLAG_IS_END_OF_PATH : 0;
+ dest.writeInt(startEnd);
+ dest.writeFloat(mX);
+ dest.writeFloat(mY);
+ }
+
+ public static final @android.annotation.NonNull Parcelable.Creator<TouchPoint> CREATOR
+ = new Parcelable.Creator<TouchPoint>() {
+ public TouchPoint createFromParcel(Parcel in) {
+ return new TouchPoint(in);
+ }
+
+ public TouchPoint[] newArray(int size) {
+ return new TouchPoint[size];
+ }
+ };
+ }
+
+ /**
+ * A step along a gesture. Contains all of the touch points at a particular time
+ *
+ * @hide
+ */
+ public static class GestureStep implements Parcelable {
+ public long timeSinceGestureStart;
+ public int numTouchPoints;
+ public TouchPoint[] touchPoints;
+
+ public GestureStep(long timeSinceGestureStart, int numTouchPoints,
+ TouchPoint[] touchPointsToCopy) {
+ this.timeSinceGestureStart = timeSinceGestureStart;
+ this.numTouchPoints = numTouchPoints;
+ this.touchPoints = new TouchPoint[numTouchPoints];
+ for (int i = 0; i < numTouchPoints; i++) {
+ this.touchPoints[i] = new TouchPoint(touchPointsToCopy[i]);
+ }
+ }
+
+ public GestureStep(Parcel parcel) {
+ timeSinceGestureStart = parcel.readLong();
+ Parcelable[] parcelables =
+ parcel.readParcelableArray(TouchPoint.class.getClassLoader(), TouchPoint.class);
+ numTouchPoints = (parcelables == null) ? 0 : parcelables.length;
+ touchPoints = new TouchPoint[numTouchPoints];
+ for (int i = 0; i < numTouchPoints; i++) {
+ touchPoints[i] = (TouchPoint) parcelables[i];
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeLong(timeSinceGestureStart);
+ dest.writeParcelableArray(touchPoints, flags);
+ }
+
+ public static final @android.annotation.NonNull Parcelable.Creator<GestureStep> CREATOR
+ = new Parcelable.Creator<GestureStep>() {
+ public GestureStep createFromParcel(Parcel in) {
+ return new GestureStep(in);
+ }
+
+ public GestureStep[] newArray(int size) {
+ return new GestureStep[size];
+ }
+ };
+ }
+
+ /**
+ * Class to convert a GestureDescription to a series of GestureSteps.
+ *
+ * @hide
+ */
+ public static class MotionEventGenerator {
+ /* Lazily-created scratch memory for processing touches */
+ private static TouchPoint[] sCurrentTouchPoints;
+
+ public static List<GestureStep> getGestureStepsFromGestureDescription(
+ GestureDescription description, int sampleTimeMs) {
+ final List<GestureStep> gestureSteps = new ArrayList<>();
+
+ // Point data at each time we generate an event for
+ final TouchPoint[] currentTouchPoints =
+ getCurrentTouchPoints(description.getStrokeCount());
+ int currentTouchPointSize = 0;
+ /* Loop through each time slice where there are touch points */
+ long timeSinceGestureStart = 0;
+ long nextKeyPointTime = description.getNextKeyPointAtLeast(timeSinceGestureStart);
+ while (nextKeyPointTime >= 0) {
+ timeSinceGestureStart = (currentTouchPointSize == 0) ? nextKeyPointTime
+ : Math.min(nextKeyPointTime, timeSinceGestureStart + sampleTimeMs);
+ currentTouchPointSize = description.getPointsForTime(timeSinceGestureStart,
+ currentTouchPoints);
+ gestureSteps.add(new GestureStep(timeSinceGestureStart, currentTouchPointSize,
+ currentTouchPoints));
+
+ /* Move to next time slice */
+ nextKeyPointTime = description.getNextKeyPointAtLeast(timeSinceGestureStart + 1);
+ }
+ return gestureSteps;
+ }
+
+ private static TouchPoint[] getCurrentTouchPoints(int requiredCapacity) {
+ if ((sCurrentTouchPoints == null) || (sCurrentTouchPoints.length < requiredCapacity)) {
+ sCurrentTouchPoints = new TouchPoint[requiredCapacity];
+ for (int i = 0; i < requiredCapacity; i++) {
+ sCurrentTouchPoints[i] = new TouchPoint();
+ }
+ }
+ return sCurrentTouchPoints;
+ }
+ }
+}
diff --git a/android-35/android/accessibilityservice/InputMethod.java b/android-35/android/accessibilityservice/InputMethod.java
new file mode 100644
index 0000000..93888ef
--- /dev/null
+++ b/android-35/android/accessibilityservice/InputMethod.java
@@ -0,0 +1,530 @@
+/*
+ * 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.accessibilityservice;
+
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.RemoteException;
+import android.os.Trace;
+import android.util.Log;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.SurroundingText;
+import android.view.inputmethod.TextAttribute;
+
+import com.android.internal.inputmethod.IAccessibilityInputMethodSessionCallback;
+import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection;
+import com.android.internal.inputmethod.RemoteAccessibilityInputConnection;
+
+/**
+ * This class provides input method APIs. Some public methods such as
+ * @link #onUpdateSelection(int, int, int, int, int, int)} do nothing by default and service
+ * developers should override them as needed. Developers should also override
+ * {@link AccessibilityService#onCreateInputMethod()} to return
+ * their custom InputMethod implementation. Accessibility services also need to set the
+ * {@link AccessibilityServiceInfo#FLAG_INPUT_METHOD_EDITOR} flag to use input method APIs.
+ */
+public class InputMethod {
+ private static final String LOG_TAG = "A11yInputMethod";
+
+ private final AccessibilityService mService;
+ private boolean mInputStarted;
+ private RemoteAccessibilityInputConnection mStartedInputConnection;
+ private EditorInfo mInputEditorInfo;
+
+ /**
+ * Creates a new InputMethod instance for the given <code>service</code>, so that the
+ * accessibility service can control editing.
+ */
+ public InputMethod(@NonNull AccessibilityService service) {
+ mService = service;
+ }
+
+ /**
+ * Retrieve the currently active InputConnection that is bound to
+ * the input method, or null if there is none.
+ */
+ @Nullable
+ public final AccessibilityInputConnection getCurrentInputConnection() {
+ if (mStartedInputConnection != null) {
+ return new AccessibilityInputConnection(mStartedInputConnection);
+ }
+ return null;
+ }
+
+ /**
+ * Whether the input has started.
+ */
+ public final boolean getCurrentInputStarted() {
+ return mInputStarted;
+ }
+
+ /**
+ * Get the EditorInfo which describes several attributes of a text editing object
+ * that an accessibility service is communicating with (typically an EditText).
+ */
+ @Nullable
+ public final EditorInfo getCurrentInputEditorInfo() {
+ return mInputEditorInfo;
+ }
+
+ /**
+ * Called to inform the accessibility service that text input has started in an
+ * editor. You should use this callback to initialize the state of your
+ * input to match the state of the editor given to it.
+ *
+ * @param attribute The attributes of the editor that input is starting
+ * in.
+ * @param restarting Set to true if input is restarting in the same
+ * editor such as because the application has changed the text in
+ * the editor. Otherwise will be false, indicating this is a new
+ * session with the editor.
+ */
+ public void onStartInput(@NonNull EditorInfo attribute, boolean restarting) {
+ // Intentionally empty
+ }
+
+ /**
+ * Called to inform the accessibility service that text input has finished in
+ * the last editor. At this point there may be a call to
+ * {@link #onStartInput(EditorInfo, boolean)} to perform input in a
+ * new editor, or the accessibility service may be left idle. This method is
+ * <em>not</em> called when input restarts in the same editor.
+ *
+ * <p>The default
+ * implementation uses the InputConnection to clear any active composing
+ * text; you can override this (not calling the base class implementation)
+ * to perform whatever behavior you would like.
+ */
+ public void onFinishInput() {
+ // Intentionally empty
+ }
+
+ /**
+ * Called when the application has reported a new selection region of
+ * the text. This is called whether or not the accessibility service has requested
+ * extracted text updates, although if so it will not receive this call
+ * if the extracted text has changed as well.
+ *
+ * <p>Be careful about changing the text in reaction to this call with
+ * methods such as setComposingText, commitText or
+ * deleteSurroundingText. If the cursor moves as a result, this method
+ * will be called again, which may result in an infinite loop.
+ */
+ public void onUpdateSelection(int oldSelStart, int oldSelEnd, int newSelStart,
+ int newSelEnd, int candidatesStart, int candidatesEnd) {
+ // Intentionally empty
+ }
+
+ final void createImeSession(IAccessibilityInputMethodSessionCallback callback) {
+ final AccessibilityInputMethodSessionWrapper wrapper =
+ new AccessibilityInputMethodSessionWrapper(mService.getMainLooper(),
+ new SessionImpl());
+ try {
+ callback.sessionCreated(wrapper, mService.getConnectionId());
+ } catch (RemoteException ignored) {
+ }
+ }
+
+ final void startInput(@Nullable RemoteAccessibilityInputConnection ic,
+ @NonNull EditorInfo attribute) {
+ Log.v(LOG_TAG, "startInput(): editor=" + attribute);
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "AccessibilityService.startInput");
+ doStartInput(ic, attribute, false /* restarting */);
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+ }
+
+ final void restartInput(@Nullable RemoteAccessibilityInputConnection ic,
+ @NonNull EditorInfo attribute) {
+ Log.v(LOG_TAG, "restartInput(): editor=" + attribute);
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "AccessibilityService.restartInput");
+ doStartInput(ic, attribute, true /* restarting */);
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+ }
+
+
+ final void doStartInput(RemoteAccessibilityInputConnection ic, EditorInfo attribute,
+ boolean restarting) {
+ if ((ic == null || !restarting) && mInputStarted) {
+ doFinishInput();
+ if (ic == null) {
+ // Unlike InputMethodService, A11y IME should not observe fallback InputConnection.
+ return;
+ }
+ }
+ mInputStarted = true;
+ mStartedInputConnection = ic;
+ mInputEditorInfo = attribute;
+ Log.v(LOG_TAG, "CALL: onStartInput");
+ onStartInput(attribute, restarting);
+ }
+
+ final void doFinishInput() {
+ Log.v(LOG_TAG, "CALL: doFinishInput");
+ if (mInputStarted) {
+ Log.v(LOG_TAG, "CALL: onFinishInput");
+ onFinishInput();
+ }
+ mInputStarted = false;
+ mStartedInputConnection = null;
+ mInputEditorInfo = null;
+ }
+
+ /**
+ * This class provides the allowed list of {@link InputConnection} APIs for
+ * accessibility services.
+ */
+ public final class AccessibilityInputConnection {
+ private final RemoteAccessibilityInputConnection mIc;
+ AccessibilityInputConnection(RemoteAccessibilityInputConnection ic) {
+ this.mIc = ic;
+ }
+
+ /**
+ * Commit text to the text box and set the new cursor position. This method is
+ * used to allow the IME to provide extra information while setting up text.
+ *
+ * <p>This method commits the contents of the currently composing text, and then
+ * moves the cursor according to {@code newCursorPosition}. If there
+ * is no composing text when this method is called, the new text is
+ * inserted at the cursor position, removing text inside the selection
+ * if any.
+ *
+ * <p>Calling this method will cause the editor to call
+ * {@link #onUpdateSelection(int, int, int, int,
+ * int, int)} on the current accessibility service after the batch input is over.
+ * <strong>Editor authors</strong>, for this to happen you need to
+ * make the changes known to the accessibility service by calling
+ * {@link InputMethodManager#updateSelection(android.view.View, int, int, int, int)},
+ * but be careful to wait until the batch edit is over if one is
+ * in progress.</p>
+ *
+ * @param text The text to commit. This may include styles.
+ * @param newCursorPosition The new cursor position around the text,
+ * in Java characters. If > 0, this is relative to the end
+ * of the text - 1; if <= 0, this is relative to the start
+ * of the text. So a value of 1 will always advance the cursor
+ * to the position after the full text being inserted. Note that
+ * this means you can't position the cursor within the text,
+ * because the editor can make modifications to the text
+ * you are providing so it is not possible to correctly specify
+ * locations there.
+ * @param textAttribute The extra information about the text.
+ */
+ public void commitText(@NonNull CharSequence text, int newCursorPosition,
+ @Nullable TextAttribute textAttribute) {
+ if (mIc != null) {
+ mIc.commitText(text, newCursorPosition, textAttribute);
+ }
+ }
+
+ /**
+ * Set the selection of the text editor. To set the cursor
+ * position, start and end should have the same value.
+ *
+ * <p>Since this moves the cursor, calling this method will cause
+ * the editor to call
+ * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int,
+ * int,int, int)} on the current IME after the batch input is over.
+ * <strong>Editor authors</strong>, for this to happen you need to
+ * make the changes known to the input method by calling
+ * {@link InputMethodManager#updateSelection(android.view.View, int, int, int, int)},
+ * but be careful to wait until the batch edit is over if one is
+ * in progress.</p>
+ *
+ * <p>This has no effect on the composing region which must stay
+ * unchanged. The order of start and end is not important. In
+ * effect, the region from start to end and the region from end to
+ * start is the same. Editor authors, be ready to accept a start
+ * that is greater than end.</p>
+ *
+ * @param start the character index where the selection should start.
+ * @param end the character index where the selection should end.
+ */
+ public void setSelection(int start, int end) {
+ if (mIc != null) {
+ mIc.setSelection(start, end);
+ }
+ }
+
+ /**
+ * Gets the surrounding text around the current cursor, with <var>beforeLength</var>
+ * characters of text before the cursor (start of the selection), <var>afterLength</var>
+ * characters of text after the cursor (end of the selection), and all of the selected
+ * text. The range are for java characters, not glyphs that can be multiple characters.
+ *
+ * <p>This method may fail either if the input connection has become invalid (such as its
+ * process crashing), or the client is taking too long to respond with the text (it is
+ * given a couple seconds to return), or the protocol is not supported. In any of these
+ * cases, null is returned.
+ *
+ * <p>This method does not affect the text in the editor in any way, nor does it affect the
+ * selection or composing spans.</p>
+ *
+ * <p>If {@link InputConnection#GET_TEXT_WITH_STYLES} is supplied as flags, the editor
+ * should return a {@link android.text.Spanned} with all the spans set on the text.</p>
+ *
+ * <p><strong>Accessibility service authors:</strong> please consider this will trigger an
+ * IPC round-trip that will take some time. Assume this method consumes a lot of time.
+ *
+ * @param beforeLength The expected length of the text before the cursor.
+ * @param afterLength The expected length of the text after the cursor.
+ * @param flags Supplies additional options controlling how the text is returned. May be
+ * either {@code 0} or {@link InputConnection#GET_TEXT_WITH_STYLES}.
+ * @return an {@link android.view.inputmethod.SurroundingText} object describing the
+ * surrounding text and state of selection, or null if the input connection is no longer
+ * valid, or the editor can't comply with the request for some reason, or the application
+ * does not implement this method. The length of the returned text might be less than the
+ * sum of <var>beforeLength</var> and <var>afterLength</var> .
+ * @throws IllegalArgumentException if {@code beforeLength} or {@code afterLength} is
+ * negative.
+ */
+ @Nullable
+ public SurroundingText getSurroundingText(
+ @IntRange(from = 0) int beforeLength, @IntRange(from = 0) int afterLength,
+ @InputConnection.GetTextType int flags) {
+ if (mIc != null) {
+ return mIc.getSurroundingText(beforeLength, afterLength, flags);
+ }
+ return null;
+ }
+
+ /**
+ * Delete <var>beforeLength</var> characters of text before the
+ * current cursor position, and delete <var>afterLength</var>
+ * characters of text after the current cursor position, excluding
+ * the selection. Before and after refer to the order of the
+ * characters in the string, not to their visual representation:
+ * this means you don't have to figure out the direction of the
+ * text and can just use the indices as-is.
+ *
+ * <p>The lengths are supplied in Java chars, not in code points
+ * or in glyphs.</p>
+ *
+ * <p>Since this method only operates on text before and after the
+ * selection, it can't affect the contents of the selection. This
+ * may affect the composing span if the span includes characters
+ * that are to be deleted, but otherwise will not change it. If
+ * some characters in the composing span are deleted, the
+ * composing span will persist but get shortened by however many
+ * chars inside it have been removed.</p>
+ *
+ * <p><strong>Accessibility service authors:</strong> please be careful not to
+ * delete only half of a surrogate pair. Also take care not to
+ * delete more characters than are in the editor, as that may have
+ * ill effects on the application. Calling this method will cause
+ * the editor to call {@link InputMethod#onUpdateSelection(int, int, int, int, int, int)}
+ * on your service after the batch input is over.</p>
+ *
+ * <p><strong>Editor authors:</strong> please be careful of race
+ * conditions in implementing this call. An IME can make a change
+ * to the text or change the selection position and use this
+ * method right away; you need to make sure the effects are
+ * consistent with the results of the latest edits. Also, although
+ * the IME should not send lengths bigger than the contents of the
+ * string, you should check the values for overflows and trim the
+ * indices to the size of the contents to avoid crashes. Since
+ * this changes the contents of the editor, you need to make the
+ * changes known to the input method by calling
+ * {@link InputMethodManager#updateSelection(android.view.View, int, int, int, int)},
+ * but be careful to wait until the batch edit is over if one is
+ * in progress.</p>
+ *
+ * @param beforeLength The number of characters before the cursor to be deleted, in code
+ * unit. If this is greater than the number of existing characters between the
+ * beginning of the text and the cursor, then this method does not fail but deletes
+ * all the characters in that range.
+ * @param afterLength The number of characters after the cursor to be deleted, in code unit.
+ * If this is greater than the number of existing characters between the cursor and
+ * the end of the text, then this method does not fail but deletes all the characters
+ * in that range.
+ */
+ public void deleteSurroundingText(int beforeLength, int afterLength) {
+ if (mIc != null) {
+ mIc.deleteSurroundingText(beforeLength, afterLength);
+ }
+ }
+
+ /**
+ * Send a key event to the process that is currently attached
+ * through this input connection. The event will be dispatched
+ * like a normal key event, to the currently focused view; this
+ * generally is the view that is providing this InputConnection,
+ * but due to the asynchronous nature of this protocol that can
+ * not be guaranteed and the focus may have changed by the time
+ * the event is received.
+ *
+ * <p>This method can be used to send key events to the
+ * application. For example, an on-screen keyboard may use this
+ * method to simulate a hardware keyboard. There are three types
+ * of standard keyboards, numeric (12-key), predictive (20-key)
+ * and ALPHA (QWERTY). You can specify the keyboard type by
+ * specify the device id of the key event.</p>
+ *
+ * <p>You will usually want to set the flag
+ * {@link KeyEvent#FLAG_SOFT_KEYBOARD KeyEvent.FLAG_SOFT_KEYBOARD}
+ * on all key event objects you give to this API; the flag will
+ * not be set for you.</p>
+ *
+ * <p>Note that it's discouraged to send such key events in normal
+ * operation; this is mainly for use with
+ * {@link android.text.InputType#TYPE_NULL} type text fields. Use
+ * the {@link #commitText} family of methods to send text to the
+ * application instead.</p>
+ *
+ * @param event The key event.
+ *
+ * @see KeyEvent
+ * @see KeyCharacterMap#NUMERIC
+ * @see KeyCharacterMap#PREDICTIVE
+ * @see KeyCharacterMap#ALPHA
+ */
+ public void sendKeyEvent(@NonNull KeyEvent event) {
+ if (mIc != null) {
+ mIc.sendKeyEvent(event);
+ }
+ }
+
+ /**
+ * Have the editor perform an action it has said it can do.
+ *
+ * @param editorAction This must be one of the action constants for
+ * {@link EditorInfo#imeOptions EditorInfo.imeOptions}, such as
+ * {@link EditorInfo#IME_ACTION_GO EditorInfo.EDITOR_ACTION_GO}, or the value of
+ * {@link EditorInfo#actionId EditorInfo.actionId} if a custom action is available.
+ */
+ public void performEditorAction(int editorAction) {
+ if (mIc != null) {
+ mIc.performEditorAction(editorAction);
+ }
+ }
+
+ /**
+ * Perform a context menu action on the field. The given id may be one of:
+ * {@link android.R.id#selectAll},
+ * {@link android.R.id#startSelectingText}, {@link android.R.id#stopSelectingText},
+ * {@link android.R.id#cut}, {@link android.R.id#copy},
+ * {@link android.R.id#paste}, {@link android.R.id#copyUrl},
+ * or {@link android.R.id#switchInputMethod}
+ */
+ public void performContextMenuAction(int id) {
+ if (mIc != null) {
+ mIc.performContextMenuAction(id);
+ }
+ }
+
+ /**
+ * Retrieve the current capitalization mode in effect at the
+ * current cursor position in the text. See
+ * {@link android.text.TextUtils#getCapsMode TextUtils.getCapsMode}
+ * for more information.
+ *
+ * <p>This method may fail either if the input connection has
+ * become invalid (such as its process crashing) or the client is
+ * taking too long to respond with the text (it is given a couple
+ * seconds to return). In either case, 0 is returned.</p>
+ *
+ * <p>This method does not affect the text in the editor in any
+ * way, nor does it affect the selection or composing spans.</p>
+ *
+ * <p><strong>Editor authors:</strong> please be careful of race
+ * conditions in implementing this call. An IME can change the
+ * cursor position and use this method right away; you need to make
+ * sure the returned value is consistent with the results of the
+ * latest edits and changes to the cursor position.</p>
+ *
+ * @param reqModes The desired modes to retrieve, as defined by
+ * {@link android.text.TextUtils#getCapsMode TextUtils.getCapsMode}. These
+ * constants are defined so that you can simply pass the current
+ * {@link EditorInfo#inputType TextBoxAttribute.contentType} value
+ * directly in to here.
+ * @return the caps mode flags that are in effect at the current
+ * cursor position. See TYPE_TEXT_FLAG_CAPS_* in {@link android.text.InputType}.
+ */
+ public int getCursorCapsMode(int reqModes) {
+ if (mIc != null) {
+ return mIc.getCursorCapsMode(reqModes);
+ }
+ return 0;
+ }
+
+ /**
+ * Clear the given meta key pressed states in the given input
+ * connection.
+ *
+ * <p>This can be used by the accessibility service to clear the meta key states set
+ * by a hardware keyboard with latched meta keys, if the editor
+ * keeps track of these.</p>
+ *
+ * @param states The states to be cleared, may be one or more bits as
+ * per {@link KeyEvent#getMetaState() KeyEvent.getMetaState()}.
+ */
+ public void clearMetaKeyStates(int states) {
+ if (mIc != null) {
+ mIc.clearMetaKeyStates(states);
+ }
+ }
+ }
+
+ /**
+ * Concrete implementation of {@link AccessibilityInputMethodSession} that provides all of the
+ * standard behavior for an A11y input method session.
+ */
+ private final class SessionImpl implements AccessibilityInputMethodSession {
+ boolean mEnabled = true;
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ mEnabled = enabled;
+ }
+
+ @Override
+ public void finishInput() {
+ if (mEnabled) {
+ doFinishInput();
+ }
+ }
+
+ @Override
+ public void updateSelection(int oldSelStart, int oldSelEnd, int newSelStart,
+ int newSelEnd, int candidatesStart, int candidatesEnd) {
+ if (mEnabled) {
+ InputMethod.this.onUpdateSelection(oldSelEnd, oldSelEnd, newSelStart,
+ newSelEnd, candidatesStart, candidatesEnd);
+ }
+ }
+
+ @Override
+ public void invalidateInput(EditorInfo editorInfo,
+ IRemoteAccessibilityInputConnection connection, int sessionId) {
+ if (!mEnabled || mStartedInputConnection == null
+ || !mStartedInputConnection.isSameConnection(connection)) {
+ // This is not an error, and can be safely ignored.
+ return;
+ }
+ editorInfo.makeCompatible(mService.getApplicationInfo().targetSdkVersion);
+ restartInput(new RemoteAccessibilityInputConnection(mStartedInputConnection, sessionId),
+ editorInfo);
+ }
+ }
+}
diff --git a/android-35/android/accessibilityservice/MagnificationConfig.java b/android-35/android/accessibilityservice/MagnificationConfig.java
new file mode 100644
index 0000000..5019aee
--- /dev/null
+++ b/android-35/android/accessibilityservice/MagnificationConfig.java
@@ -0,0 +1,286 @@
+/*
+ * Copyright (C) 2021 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.accessibilityservice;
+
+import android.annotation.FloatRange;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * This class describes the magnification config for {@link AccessibilityService} to control the
+ * magnification.
+ *
+ * <p>
+ * When the magnification config uses {@link #MAGNIFICATION_MODE_DEFAULT},
+ * {@link AccessibilityService} will be able to control the activated magnifier on the display.
+ * If there is no magnifier activated, it controls the last-activated magnification mode.
+ * If there is no magnifier activated before, it controls full-screen magnifier by default.
+ * </p>
+ *
+ * <p>
+ * When the magnification config uses {@link #MAGNIFICATION_MODE_FULLSCREEN}.
+ * {@link AccessibilityService} will be able to control full-screen magnifier on the display.
+ * </p>
+ *
+ * <p>
+ * When the magnification config uses {@link #MAGNIFICATION_MODE_WINDOW} and the platform
+ * supports {@link android.content.pm.PackageManager#FEATURE_WINDOW_MAGNIFICATION} feature.
+ * {@link AccessibilityService} will be able to control window magnifier on the display.
+ * </p>
+ *
+ * <p>
+ * If the other magnification configs, scale centerX and centerY, are not set by the
+ * {@link Builder}, the configs should be current values or default values. And the center
+ * position ordinarily is the center of the screen.
+ * </p>
+ */
+public final class MagnificationConfig implements Parcelable {
+
+ /** The controlling magnification mode. It controls the activated magnifier. */
+ public static final int MAGNIFICATION_MODE_DEFAULT = 0;
+ /** The controlling magnification mode. It controls full-screen magnifier. */
+ public static final int MAGNIFICATION_MODE_FULLSCREEN = 1;
+ /**
+ * The controlling magnification mode. It is valid if the platform supports
+ * {@link android.content.pm.PackageManager#FEATURE_WINDOW_MAGNIFICATION} feature.
+ */
+ public static final int MAGNIFICATION_MODE_WINDOW = 2;
+
+ /** @hide */
+ @IntDef(prefix = {"MAGNIFICATION_MODE"}, value = {
+ MAGNIFICATION_MODE_DEFAULT,
+ MAGNIFICATION_MODE_FULLSCREEN,
+ MAGNIFICATION_MODE_WINDOW,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface MagnificationMode {
+ }
+
+ private int mMode = MAGNIFICATION_MODE_DEFAULT;
+ private boolean mActivated = false;
+ private float mScale = Float.NaN;
+ private float mCenterX = Float.NaN;
+ private float mCenterY = Float.NaN;
+
+ private MagnificationConfig() {
+ /* do nothing */
+ }
+
+ private MagnificationConfig(@NonNull Parcel parcel) {
+ mMode = parcel.readInt();
+ mActivated = parcel.readBoolean();
+ mScale = parcel.readFloat();
+ mCenterX = parcel.readFloat();
+ mCenterY = parcel.readFloat();
+ }
+
+ /**
+ * Returns the magnification mode that is the current activated mode or the controlling mode of
+ * the config.
+ *
+ * @return The magnification mode
+ */
+ public int getMode() {
+ return mMode;
+ }
+
+ /**
+ * Returns the activated state of the controlling magnifier. The controlling magnifier can be
+ * activated even if the scale returned by {@link MagnificationConfig#getScale()} equals to 1.0.
+ *
+ * @return {@code true} if the magnifier is activated and showing on screen,
+ * {@code false} otherwise.
+ */
+ public boolean isActivated() {
+ return mActivated;
+ }
+
+ /**
+ * Returns the magnification scale of the controlling magnifier
+ *
+ * @return The magnification scale
+ */
+ public float getScale() {
+ return mScale;
+ }
+
+ /**
+ * Returns the screen-relative X coordinate of the center of the magnification viewport.
+ *
+ * @return The X coordinate
+ */
+ public float getCenterX() {
+ return mCenterX;
+ }
+
+ /**
+ * Returns the screen-relative Y coordinate of the center of the magnification viewport.
+ *
+ * @return The Y coordinate
+ */
+ public float getCenterY() {
+ return mCenterY;
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ StringBuilder stringBuilder = new StringBuilder("MagnificationConfig[");
+ stringBuilder.append("mode: ").append(getMode());
+ stringBuilder.append(", ");
+ stringBuilder.append("activated: ").append(isActivated());
+ stringBuilder.append(", ");
+ stringBuilder.append("scale: ").append(getScale());
+ stringBuilder.append(", ");
+ stringBuilder.append("centerX: ").append(getCenterX());
+ stringBuilder.append(", ");
+ stringBuilder.append("centerY: ").append(getCenterY());
+ stringBuilder.append("] ");
+ return stringBuilder.toString();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel parcel, int flags) {
+ parcel.writeInt(mMode);
+ parcel.writeBoolean(mActivated);
+ parcel.writeFloat(mScale);
+ parcel.writeFloat(mCenterX);
+ parcel.writeFloat(mCenterY);
+ }
+
+ /**
+ * Builder for creating {@link MagnificationConfig} objects.
+ */
+ public static final class Builder {
+
+ private int mMode = MAGNIFICATION_MODE_DEFAULT;
+ private boolean mActivated = true;
+ private float mScale = Float.NaN;
+ private float mCenterX = Float.NaN;
+ private float mCenterY = Float.NaN;
+
+ /**
+ * Creates a new Builder.
+ */
+ public Builder() {
+ }
+
+ /**
+ * Sets the magnification mode.
+ *
+ * @param mode The magnification mode
+ * @return This builder
+ */
+ @NonNull
+ public MagnificationConfig.Builder setMode(@MagnificationMode int mode) {
+ mMode = mode;
+ return this;
+ }
+
+ /**
+ * Sets magnification activated state.
+ *
+ * @param activated The magnification activated state
+ * @return This builder
+ */
+ @NonNull
+ public MagnificationConfig.Builder setActivated(boolean activated) {
+ mActivated = activated;
+ return this;
+ }
+
+ /**
+ * Sets the magnification scale.
+ *
+ * @param scale The magnification scale, in the range [1, 8]
+ * @return This builder
+ */
+ @NonNull
+ public MagnificationConfig.Builder setScale(@FloatRange(from = 1f, to = 8f) float scale) {
+ mScale = scale;
+ return this;
+ }
+
+ /**
+ * Sets the X coordinate of the center of the magnification viewport.
+ * The controlling magnifier will apply the given position.
+ *
+ * @param centerX the screen-relative X coordinate around which to
+ * center and scale that is in the range [0, screenWidth],
+ * or {@link Float#NaN} to leave unchanged
+ * @return This builder
+ */
+ @NonNull
+ public MagnificationConfig.Builder setCenterX(float centerX) {
+ mCenterX = centerX;
+ return this;
+ }
+
+ /**
+ * Sets the Y coordinate of the center of the magnification viewport.
+ * The controlling magnifier will apply the given position.
+ *
+ * @param centerY the screen-relative Y coordinate around which to
+ * center and scale that is in the range [0, screenHeight],
+ * or {@link Float#NaN} to leave unchanged
+ * @return This builder
+ */
+ @NonNull
+ public MagnificationConfig.Builder setCenterY(float centerY) {
+ mCenterY = centerY;
+ return this;
+ }
+
+ /**
+ * Builds and returns a {@link MagnificationConfig}
+ */
+ @NonNull
+ public MagnificationConfig build() {
+ MagnificationConfig magnificationConfig = new MagnificationConfig();
+ magnificationConfig.mMode = mMode;
+ magnificationConfig.mActivated = mActivated;
+ magnificationConfig.mScale = mScale;
+ magnificationConfig.mCenterX = mCenterX;
+ magnificationConfig.mCenterY = mCenterY;
+ return magnificationConfig;
+ }
+ }
+
+ /**
+ * @see Parcelable.Creator
+ */
+ public static final @NonNull Parcelable.Creator<MagnificationConfig> CREATOR =
+ new Parcelable.Creator<MagnificationConfig>() {
+ public MagnificationConfig createFromParcel(Parcel parcel) {
+ return new MagnificationConfig(parcel);
+ }
+
+ public MagnificationConfig[] newArray(int size) {
+ return new MagnificationConfig[size];
+ }
+ };
+}
diff --git a/android-35/android/accessibilityservice/TouchInteractionController.java b/android-35/android/accessibilityservice/TouchInteractionController.java
new file mode 100644
index 0000000..6ec956e
--- /dev/null
+++ b/android-35/android/accessibilityservice/TouchInteractionController.java
@@ -0,0 +1,450 @@
+/*
+ * Copyright (C) 2021 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.accessibilityservice;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.view.MotionEvent;
+import android.view.accessibility.AccessibilityInteractionClient;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.LinkedList;
+import java.util.Queue;
+import java.util.concurrent.Executor;
+
+/**
+ * This class allows a service to handle touch exploration and the detection of specialized
+ * accessibility gestures. The service receives motion events and can match those motion events
+ * against the gestures it supports. The service can also request the framework enter three other
+ * states of operation for the duration of this interaction. Upon entering any of these states the
+ * framework will take over and the service will not receive motion events until the start of a new
+ * interaction. The states are as follows:
+ *
+ * <ul>
+ * <li>The service can tell the framework that this interaction is touch exploration. The user is
+ * trying to explore the screen rather than manipulate it. The framework will then convert the
+ * motion events to hover events to support touch exploration.
+ * <li>The service can tell the framework that this interaction is a dragging interaction where
+ * two fingers are used to execute a one-finger gesture such as scrolling the screen. The
+ * service must specify which of the two fingers should be passed through to rest of the input
+ * pipeline.
+ * <li>Finally, the service can request that the framework delegate this interaction, meaning pass
+ * it through to the rest of the input pipeline as-is.
+ * </ul>
+ *
+ * When {@link AccessibilityServiceInfo#FLAG_REQUEST_TOUCH_EXPLORATION_MODE } is enabled, this
+ * controller will receive all motion events received by the framework for the specified display
+ * when not touch-exploring or delegating. If the service classifies this interaction as touch
+ * exploration or delegating the framework will stop sending motion events to the service for the
+ * duration of this interaction. If the service classifies this interaction as a dragging
+ * interaction the framework will send motion events to the service to allow the service to
+ * determine if the interaction still qualifies as dragging or if it has become a delegating
+ * interaction. If {@link AccessibilityServiceInfo#FLAG_REQUEST_TOUCH_EXPLORATION_MODE } is disabled
+ * this controller will not receive any motion events because touch interactions are being passed
+ * through to the input pipeline unaltered.
+ * Note that {@link AccessibilityServiceInfo#FLAG_REQUEST_TOUCH_EXPLORATION_MODE }
+ * requires setting {@link android.R.attr#canRequestTouchExplorationMode} as well.
+ */
+public final class TouchInteractionController {
+ /** The state where the user is not touching the screen. */
+ public static final int STATE_CLEAR = 0;
+ /**
+ * The state where the user is touching the screen and the service is receiving motion events.
+ */
+ public static final int STATE_TOUCH_INTERACTING = 1;
+ /**
+ * The state where the user is explicitly exploring the screen. The service is not receiving
+ * motion events.
+ */
+ public static final int STATE_TOUCH_EXPLORING = 2;
+ /**
+ * The state where the user is dragging with two fingers. The service is not receiving motion
+ * events. The selected finger is being dispatched to the rest of the input pipeline to execute
+ * the drag.
+ */
+ public static final int STATE_DRAGGING = 3;
+ /**
+ * The user is performing a gesture which is being passed through to the input pipeline as-is.
+ * The service is not receiving motion events.
+ */
+ public static final int STATE_DELEGATING = 4;
+
+ @IntDef({
+ STATE_CLEAR,
+ STATE_TOUCH_INTERACTING,
+ STATE_TOUCH_EXPLORING,
+ STATE_DRAGGING,
+ STATE_DELEGATING
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ private @interface State {}
+
+ // The maximum number of pointers that can be touching the screen at once. (See MAX_POINTER_ID
+ // in frameworks/native/include/input/Input.h)
+ private static final int MAX_POINTER_COUNT = 32;
+
+ private final AccessibilityService mService;
+ private final Object mLock;
+ private final int mDisplayId;
+ private boolean mServiceDetectsGestures;
+ /** Map of callbacks to executors. Lazily created when adding the first callback. */
+ private ArrayMap<Callback, Executor> mCallbacks;
+ // A list of motion events that should be queued until a pending transition has taken place.
+ private Queue<MotionEvent> mQueuedMotionEvents = new LinkedList<>();
+ // Whether this controller is waiting for a state transition.
+ // Motion events will be queued and sent to listeners after the transition has taken place.
+ private boolean mStateChangeRequested = false;
+
+ // The current state of the display.
+ private int mState = STATE_CLEAR;
+
+ TouchInteractionController(
+ @NonNull AccessibilityService service, @NonNull Object lock, int displayId) {
+ mDisplayId = displayId;
+ mLock = lock;
+ mService = service;
+ }
+
+ /**
+ * Adds the specified callback to the list of callbacks. The callback will
+ * run using on the specified {@link Executor}', or on the service's main thread if the
+ * Executor is {@code null}.
+ * @param callback the callback to add, must be non-null
+ * @param executor the executor for this callback, or {@code null} to execute on the service's
+ * main thread
+ */
+ public void registerCallback(@Nullable Executor executor, @NonNull Callback callback) {
+ synchronized (mLock) {
+ if (mCallbacks == null) {
+ mCallbacks = new ArrayMap<>();
+ }
+ mCallbacks.put(callback, executor);
+ if (mCallbacks.size() == 1) {
+ setServiceDetectsGestures(true);
+ }
+ }
+ }
+
+ /**
+ * Unregisters the specified callback.
+ *
+ * @param callback the callback to remove, must be non-null
+ * @return {@code true} if the callback was removed, {@code false} otherwise
+ */
+ public boolean unregisterCallback(@NonNull Callback callback) {
+ if (mCallbacks == null) {
+ return false;
+ }
+ synchronized (mLock) {
+ boolean result = mCallbacks.remove(callback) != null;
+ if (result && mCallbacks.size() == 0) {
+ setServiceDetectsGestures(false);
+ }
+ return result;
+ }
+ }
+
+ /**
+ * Removes all callbacks and returns control of touch interactions to the framework.
+ */
+ public void unregisterAllCallbacks() {
+ if (mCallbacks != null) {
+ synchronized (mLock) {
+ mCallbacks.clear();
+ setServiceDetectsGestures(false);
+ }
+ }
+ }
+
+ /**
+ * Dispatches motion events to any registered callbacks. This should be called on the service's
+ * main thread.
+ */
+ void onMotionEvent(MotionEvent event) {
+ if (mStateChangeRequested) {
+ mQueuedMotionEvents.add(event);
+ } else {
+ sendEventToAllListeners(event);
+ }
+ }
+
+ private void sendEventToAllListeners(MotionEvent event) {
+ final ArrayMap<Callback, Executor> entries;
+ synchronized (mLock) {
+ // callbacks may remove themselves. Perform a shallow copy to avoid concurrent
+ // modification.
+ entries = new ArrayMap<>(mCallbacks);
+ }
+ for (int i = 0, count = entries.size(); i < count; i++) {
+ final Callback callback = entries.keyAt(i);
+ final Executor executor = entries.valueAt(i);
+ if (executor != null) {
+ executor.execute(() -> callback.onMotionEvent(event));
+ } else {
+ // We're already on the main thread, just run the callback.
+ callback.onMotionEvent(event);
+ }
+ }
+ }
+
+ /**
+ * Dispatches motion events to any registered callbacks. This should be called on the service's
+ * main thread.
+ */
+ void onStateChanged(@State int state) {
+ mState = state;
+ final ArrayMap<Callback, Executor> entries;
+ synchronized (mLock) {
+ // callbacks may remove themselves. Perform a shallow copy to avoid concurrent
+ // modification.
+ entries = new ArrayMap<>(mCallbacks);
+ }
+ for (int i = 0, count = entries.size(); i < count; i++) {
+ final Callback callback = entries.keyAt(i);
+ final Executor executor = entries.valueAt(i);
+ if (executor != null) {
+ executor.execute(() -> callback.onStateChanged(state));
+ } else {
+ // We're already on the main thread, just run the callback.
+ callback.onStateChanged(state);
+ }
+ }
+ mStateChangeRequested = false;
+ while (mQueuedMotionEvents.size() > 0) {
+ sendEventToAllListeners(mQueuedMotionEvents.poll());
+ }
+ }
+
+ /**
+ * When {@link AccessibilityServiceInfo#FLAG_REQUEST_TOUCH_EXPLORATION_MODE} is enabled, this
+ * controller will receive all motion events received by the framework for the specified display
+ * when not touch-exploring, delegating, or dragging. This allows the service to detect its own
+ * gestures, and use its own logic to judge when the framework should start touch-exploring,
+ * delegating, or dragging. If {@link
+ * AccessibilityServiceInfo#FLAG_REQUEST_TOUCH_EXPLORATION_MODE } is disabled this flag has no
+ * effect.
+ *
+ * @see AccessibilityServiceInfo#FLAG_REQUEST_TOUCH_EXPLORATION_MODE
+ */
+ private void setServiceDetectsGestures(boolean mode) {
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance()
+ .getConnection(mService.getConnectionId());
+ if (connection != null) {
+ try {
+ connection.setServiceDetectsGesturesEnabled(mDisplayId, mode);
+ mServiceDetectsGestures = mode;
+ } catch (RemoteException re) {
+ throw new RuntimeException(re);
+ }
+ }
+ }
+
+ /**
+ * If {@link AccessibilityServiceInfo#FLAG_REQUEST_TOUCH_EXPLORATION_MODE} is enabled and at
+ * least one callback has been added for this display this function tells the framework to
+ * initiate touch exploration. Touch exploration will continue for the duration of this
+ * interaction.
+ */
+ public void requestTouchExploration() {
+ validateTransitionRequest();
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance()
+ .getConnection(mService.getConnectionId());
+ if (connection != null) {
+ try {
+ connection.requestTouchExploration(mDisplayId);
+ } catch (RemoteException re) {
+ throw new RuntimeException(re);
+ }
+ mStateChangeRequested = true;
+ }
+ }
+
+ /**
+ * If {@link AccessibilityServiceInfo#FLAG_REQUEST_TOUCH_EXPLORATION_MODE} and {@link If
+ * {@link AccessibilityServiceInfo#FLAG_REQUEST_TOUCH_EXPLORATION_MODE} is enabled and at least
+ * one callback has been added, this function tells the framework to initiate a dragging
+ * interaction using the specified pointer. The pointer's movements will be passed through to
+ * the rest of the input pipeline. Dragging is often used to perform two-finger scrolling.
+ *
+ * @param pointerId the pointer to be passed through to the rest of the input pipeline. If the
+ * pointer id is valid but not actually present on the screen it will be ignored.
+ * @throws IllegalArgumentException if the pointer id is outside of the allowed range.
+ */
+ public void requestDragging(int pointerId) {
+ validateTransitionRequest();
+ if (pointerId < 0 || pointerId > MAX_POINTER_COUNT) {
+ throw new IllegalArgumentException("Invalid pointer id: " + pointerId);
+ }
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance()
+ .getConnection(mService.getConnectionId());
+ if (connection != null) {
+ try {
+ connection.requestDragging(mDisplayId, pointerId);
+ } catch (RemoteException re) {
+ throw new RuntimeException(re);
+ }
+ mStateChangeRequested = true;
+ }
+ }
+
+ /**
+ * If {@link AccessibilityServiceInfo#FLAG_REQUEST_TOUCH_EXPLORATION_MODE} and {@link If
+ * {@link AccessibilityServiceInfo#FLAG_REQUEST_TOUCH_EXPLORATION_MODE} is enabled and at least
+ * one callback has been added, this function tells the framework to initiate a delegating
+ * interaction. Motion events will be passed through as-is to the rest of the input pipeline for
+ * the duration of this interaction.
+ */
+ public void requestDelegating() {
+ validateTransitionRequest();
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance()
+ .getConnection(mService.getConnectionId());
+ if (connection != null) {
+ try {
+ connection.requestDelegating(mDisplayId);
+ } catch (RemoteException re) {
+ throw new RuntimeException(re);
+ }
+ mStateChangeRequested = true;
+ }
+ }
+
+ /**
+ * If {@link AccessibilityServiceInfo#FLAG_REQUEST_TOUCH_EXPLORATION_MODE} and {@link If
+ * {@link AccessibilityServiceInfo#FLAG_REQUEST_TOUCH_EXPLORATION_MODE} is enabled and at least
+ * one callback has been added, this function tells the framework to perform a click.
+ * The framework will first try to perform
+ * {@link AccessibilityNodeInfo.AccessibilityAction#ACTION_CLICK} on the item with
+ * accessibility focus. If that fails, the framework will simulate a click using motion events
+ * on the last location to have accessibility focus.
+ */
+ public void performClick() {
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance()
+ .getConnection(mService.getConnectionId());
+ if (connection != null) {
+ try {
+ connection.onDoubleTap(mDisplayId);
+ } catch (RemoteException re) {
+ throw new RuntimeException(re);
+ }
+ }
+ }
+
+ /**
+ * If {@link AccessibilityServiceInfo#FLAG_REQUEST_TOUCH_EXPLORATION_MODE} and {@link If
+ * {@link AccessibilityServiceInfo#FLAG_REQUEST_TOUCH_EXPLORATION_MODE} is enabled and at least
+ * one callback has been added, this function tells the framework to perform a long click.
+ * The framework will simulate a long click using motion events on the last location with
+ * accessibility focus and will delegate any movements to the rest of the input pipeline. This
+ * allows a user to double-tap and hold to trigger a drag and then execute that drag by moving
+ * their finger.
+ */
+ public void performLongClickAndStartDrag() {
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance()
+ .getConnection(mService.getConnectionId());
+ if (connection != null) {
+ try {
+ connection.onDoubleTapAndHold(mDisplayId);
+ } catch (RemoteException re) {
+ throw new RuntimeException(re);
+ }
+ }
+ }
+
+ private void validateTransitionRequest() {
+ if (!mServiceDetectsGestures || mCallbacks.size() == 0) {
+ throw new IllegalStateException(
+ "State transitions are not allowed without first adding a callback.");
+ }
+ if ((mState == STATE_DELEGATING || mState == STATE_TOUCH_EXPLORING)) {
+ throw new IllegalStateException(
+ "State transition requests are not allowed in " + stateToString(mState));
+ }
+ }
+
+ /** @return the maximum number of pointers that this display will accept. */
+ public int getMaxPointerCount() {
+ return MAX_POINTER_COUNT;
+ }
+
+ /** @return the display id associated with this controller. */
+ public int getDisplayId() {
+ return mDisplayId;
+ }
+
+ /**
+ * @return the current state of this controller.
+ * @see TouchInteractionController#STATE_CLEAR
+ * @see TouchInteractionController#STATE_DELEGATING
+ * @see TouchInteractionController#STATE_DRAGGING
+ * @see TouchInteractionController#STATE_TOUCH_EXPLORING
+ */
+ public int getState() {
+ synchronized (mLock) {
+ return mState;
+ }
+ }
+
+ /** Returns a string representation of the specified state. */
+ @NonNull
+ public static String stateToString(int state) {
+ switch (state) {
+ case STATE_CLEAR:
+ return "STATE_CLEAR";
+ case STATE_TOUCH_INTERACTING:
+ return "STATE_TOUCH_INTERACTING";
+ case STATE_TOUCH_EXPLORING:
+ return "STATE_TOUCH_EXPLORING";
+ case STATE_DRAGGING:
+ return "STATE_DRAGGING";
+ case STATE_DELEGATING:
+ return "STATE_DELEGATING";
+ default:
+ return "Unknown state: " + state;
+ }
+ }
+
+ /** callbacks allow services to receive motion events and state change updates. */
+ public interface Callback {
+ /**
+ * Called when the framework has sent a motion event to the service.
+ *
+ * @param event the event being passed to the service.
+ */
+ void onMotionEvent(@NonNull MotionEvent event);
+
+ /**
+ * Called when the state of motion event dispatch for this display has changed.
+ *
+ * @param state the new state of motion event dispatch.
+ * @see TouchInteractionController#STATE_CLEAR
+ * @see TouchInteractionController#STATE_DELEGATING
+ * @see TouchInteractionController#STATE_DRAGGING
+ * @see TouchInteractionController#STATE_TOUCH_EXPLORING
+ */
+ void onStateChanged(@State int state);
+ }
+}
diff --git a/android-35/android/accessibilityservice/util/AccessibilityUtils.java b/android-35/android/accessibilityservice/util/AccessibilityUtils.java
new file mode 100644
index 0000000..fa32bb2
--- /dev/null
+++ b/android-35/android/accessibilityservice/util/AccessibilityUtils.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2020 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.accessibilityservice.util;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StringRes;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.util.TypedValue;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.regex.Pattern;
+
+/**
+ * Collection of utilities for accessibility service.
+ *
+ * @hide
+ */
+public final class AccessibilityUtils {
+ private AccessibilityUtils() {}
+
+ // Used for html description of accessibility service. The <img> src tag must follow the
+ // prefix rule. e.g. <img src="R.drawable.fileName"/>
+ private static final String IMG_PREFIX = "R.drawable.";
+ private static final String ANCHOR_TAG = "a";
+ private static final List<String> UNSUPPORTED_TAG_LIST = new ArrayList<>(
+ Collections.singletonList(ANCHOR_TAG));
+
+ /**
+ * Gets the filtered html string for
+ * {@link android.accessibilityservice.AccessibilityServiceInfo} and
+ * {@link android.accessibilityservice.AccessibilityShortcutInfo}. It filters
+ * the <img> tag which do not meet the custom specification and the <a> tag.
+ *
+ * @param text the target text is html format.
+ * @return the filtered html string.
+ */
+ public static @NonNull String getFilteredHtmlText(@NonNull String text) {
+ final String replacementStart = "<invalidtag ";
+ final String replacementEnd = "</invalidtag>";
+
+ for (String tag : UNSUPPORTED_TAG_LIST) {
+ final String regexStart = "(?i)<" + tag + "(\\s+|>)";
+ final String regexEnd = "(?i)</" + tag + "\\s*>";
+ text = Pattern.compile(regexStart).matcher(text).replaceAll(replacementStart);
+ text = Pattern.compile(regexEnd).matcher(text).replaceAll(replacementEnd);
+ }
+
+ final String regexInvalidImgTag = "(?i)<img\\s+(?!src\\s*=\\s*\"(?-i)" + IMG_PREFIX + ")";
+ text = Pattern.compile(regexInvalidImgTag).matcher(text).replaceAll(
+ replacementStart);
+
+ return text;
+ }
+
+ /**
+ * Loads the animated image for
+ * {@link android.accessibilityservice.AccessibilityServiceInfo} and
+ * {@link android.accessibilityservice.AccessibilityShortcutInfo}. It checks the resource
+ * whether to exceed the screen size.
+ *
+ * @param context the current context.
+ * @param applicationInfo the current application.
+ * @param resId the animated image resource id.
+ * @return the animated image which is safe.
+ */
+ @Nullable
+ public static Drawable loadSafeAnimatedImage(@NonNull Context context,
+ @NonNull ApplicationInfo applicationInfo, @StringRes int resId) {
+ if (resId == /* invalid */ 0) {
+ return null;
+ }
+
+ final PackageManager packageManager = context.getPackageManager();
+ final String packageName = applicationInfo.packageName;
+ final Drawable bannerDrawable = packageManager.getDrawable(packageName, resId,
+ applicationInfo);
+ if (bannerDrawable == null) {
+ return null;
+ }
+
+ final boolean isImageWidthOverScreenLength =
+ bannerDrawable.getIntrinsicWidth() > getScreenWidthPixels(context);
+ final boolean isImageHeightOverScreenLength =
+ bannerDrawable.getIntrinsicHeight() > getScreenHeightPixels(context);
+
+ return (isImageWidthOverScreenLength || isImageHeightOverScreenLength)
+ ? null
+ : bannerDrawable;
+ }
+
+ /**
+ * Gets the width of the screen.
+ *
+ * @param context the current context.
+ * @return the width of the screen in term of pixels.
+ */
+ private static int getScreenWidthPixels(@NonNull Context context) {
+ final Resources resources = context.getResources();
+ final int screenWidthDp = resources.getConfiguration().screenWidthDp;
+
+ return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, screenWidthDp,
+ resources.getDisplayMetrics()));
+ }
+
+ /**
+ * Gets the height of the screen.
+ *
+ * @param context the current context.
+ * @return the height of the screen in term of pixels.
+ */
+ private static int getScreenHeightPixels(@NonNull Context context) {
+ final Resources resources = context.getResources();
+ final int screenHeightDp = resources.getConfiguration().screenHeightDp;
+
+ return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, screenHeightDp,
+ resources.getDisplayMetrics()));
+ }
+}
diff --git a/android-35/android/accounts/AbstractAccountAuthenticator.java b/android-35/android/accounts/AbstractAccountAuthenticator.java
new file mode 100644
index 0000000..c1c5c0e
--- /dev/null
+++ b/android-35/android/accounts/AbstractAccountAuthenticator.java
@@ -0,0 +1,1011 @@
+/*
+ * Copyright (C) 2009 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.accounts;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.util.Arrays;
+
+/**
+ * Abstract base class for creating AccountAuthenticators.
+ * In order to be an authenticator one must extend this class, provide implementations for the
+ * abstract methods, and write a service that returns the result of {@link #getIBinder()}
+ * in the service's {@link android.app.Service#onBind(android.content.Intent)} when invoked
+ * with an intent with action {@link AccountManager#ACTION_AUTHENTICATOR_INTENT}. This service
+ * must specify the following intent filter and metadata tags in its AndroidManifest.xml file
+ * <pre>
+ * <intent-filter>
+ * <action android:name="android.accounts.AccountAuthenticator" />
+ * </intent-filter>
+ * <meta-data android:name="android.accounts.AccountAuthenticator"
+ * android:resource="@xml/authenticator" />
+ * </pre>
+ * The <code>android:resource</code> attribute must point to a resource that looks like:
+ * <pre>
+ * <account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
+ * android:accountType="typeOfAuthenticator"
+ * android:icon="@drawable/icon"
+ * android:smallIcon="@drawable/miniIcon"
+ * android:label="@string/label"
+ * android:accountPreferences="@xml/account_preferences"
+ * />
+ * </pre>
+ * Replace the icons and labels with your own resources. The <code>android:accountType</code>
+ * attribute must be a string that uniquely identifies your authenticator and will be the same
+ * string that user will use when making calls on the {@link AccountManager} and it also
+ * corresponds to {@link Account#type} for your accounts. One user of the android:icon is the
+ * "Account & Sync" settings page and one user of the android:smallIcon is the Contact Application's
+ * tab panels.
+ * <p>
+ * The preferences attribute points to a PreferenceScreen xml hierarchy that contains
+ * a list of PreferenceScreens that can be invoked to manage the authenticator. An example is:
+ * <pre>
+ * <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+ * <PreferenceCategory android:title="@string/title_fmt" />
+ * <PreferenceScreen
+ * android:key="key1"
+ * android:title="@string/key1_action"
+ * android:summary="@string/key1_summary">
+ * <intent
+ * android:action="key1.ACTION"
+ * android:targetPackage="key1.package"
+ * android:targetClass="key1.class" />
+ * </PreferenceScreen>
+ * </PreferenceScreen>
+ * </pre>
+ *
+ * <p>
+ * The standard pattern for implementing any of the abstract methods is the following:
+ * <ul>
+ * <li> If the supplied arguments are enough for the authenticator to fully satisfy the request
+ * then it will do so and return a {@link Bundle} that contains the results.
+ * <li> If the authenticator needs information from the user to satisfy the request then it
+ * will create an {@link Intent} to an activity that will prompt the user for the information
+ * and then carry out the request. This intent must be returned in a Bundle as key
+ * {@link AccountManager#KEY_INTENT}.
+ * <p>
+ * The activity needs to return the final result when it is complete so the Intent should contain
+ * the {@link AccountAuthenticatorResponse} as
+ * {@link AccountManager#KEY_ACCOUNT_AUTHENTICATOR_RESPONSE}.
+ * The activity must then call {@link AccountAuthenticatorResponse#onResult} or
+ * {@link AccountAuthenticatorResponse#onError} when it is complete.
+ * <li> If the authenticator cannot synchronously process the request and return a result then it
+ * may choose to return null and then use the AccountManagerResponse to send the result
+ * when it has completed the request. This asynchronous option is not available for the
+ * {@link #addAccount} method, which must complete synchronously.
+ * </ul>
+ * <p>
+ * The following descriptions of each of the abstract authenticator methods will not describe the
+ * possible asynchronous nature of the request handling and will instead just describe the input
+ * parameters and the expected result.
+ * <p>
+ * When writing an activity to satisfy these requests one must pass in the AccountManagerResponse
+ * and return the result via that response when the activity finishes (or whenever else the
+ * activity author deems it is the correct time to respond).
+ */
+public abstract class AbstractAccountAuthenticator {
+ private static final String TAG = "AccountAuthenticator";
+
+ /**
+ * Bundle key used for the {@code long} expiration time (in millis from the unix epoch) of the
+ * associated auth token.
+ *
+ * @see #getAuthToken
+ */
+ public static final String KEY_CUSTOM_TOKEN_EXPIRY = "android.accounts.expiry";
+
+ /**
+ * Bundle key used for the {@link String} account type in session bundle.
+ * This is used in the default implementation of
+ * {@link #startAddAccountSession} and {@link #startUpdateCredentialsSession}.
+ */
+ private static final String KEY_AUTH_TOKEN_TYPE =
+ "android.accounts.AbstractAccountAuthenticato.KEY_AUTH_TOKEN_TYPE";
+ /**
+ * Bundle key used for the {@link String} array of required features in
+ * session bundle. This is used in the default implementation of
+ * {@link #startAddAccountSession} and {@link #startUpdateCredentialsSession}.
+ */
+ private static final String KEY_REQUIRED_FEATURES =
+ "android.accounts.AbstractAccountAuthenticator.KEY_REQUIRED_FEATURES";
+ /**
+ * Bundle key used for the {@link Bundle} options in session bundle. This is
+ * used in default implementation of {@link #startAddAccountSession} and
+ * {@link #startUpdateCredentialsSession}.
+ */
+ private static final String KEY_OPTIONS =
+ "android.accounts.AbstractAccountAuthenticator.KEY_OPTIONS";
+ /**
+ * Bundle key used for the {@link Account} account in session bundle. This is used
+ * used in default implementation of {@link #startUpdateCredentialsSession}.
+ */
+ private static final String KEY_ACCOUNT =
+ "android.accounts.AbstractAccountAuthenticator.KEY_ACCOUNT";
+
+ public AbstractAccountAuthenticator(Context context) {
+ }
+
+ private class Transport extends IAccountAuthenticator.Stub {
+ @android.annotation.EnforcePermission(android.Manifest.permission.ACCOUNT_MANAGER)
+ @Override
+ public void addAccount(IAccountAuthenticatorResponse response, String accountType,
+ String authTokenType, String[] features, Bundle options)
+ throws RemoteException {
+ super.addAccount_enforcePermission();
+
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "addAccount: accountType " + accountType
+ + ", authTokenType " + authTokenType
+ + ", features " + (features == null ? "[]" : Arrays.toString(features)));
+ }
+ try {
+ final Bundle result = AbstractAccountAuthenticator.this.addAccount(
+ new AccountAuthenticatorResponse(response),
+ accountType, authTokenType, features, options);
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ if (result != null) {
+ result.keySet(); // force it to be unparcelled
+ }
+ Log.v(TAG, "addAccount: result " + AccountManager.sanitizeResult(result));
+ }
+ if (result != null) {
+ response.onResult(result);
+ } else {
+ response.onError(AccountManager.ERROR_CODE_INVALID_RESPONSE,
+ "null bundle returned");
+ }
+ } catch (Exception e) {
+ handleException(response, "addAccount", accountType, e);
+ }
+ }
+
+ @android.annotation.EnforcePermission(android.Manifest.permission.ACCOUNT_MANAGER)
+ @Override
+ public void confirmCredentials(IAccountAuthenticatorResponse response,
+ Account account, Bundle options) throws RemoteException {
+ super.confirmCredentials_enforcePermission();
+
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "confirmCredentials: " + account);
+ }
+ try {
+ final Bundle result = AbstractAccountAuthenticator.this.confirmCredentials(
+ new AccountAuthenticatorResponse(response), account, options);
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ if (result != null) {
+ result.keySet(); // force it to be unparcelled
+ }
+ Log.v(TAG, "confirmCredentials: result "
+ + AccountManager.sanitizeResult(result));
+ }
+ if (result != null) {
+ response.onResult(result);
+ }
+ } catch (Exception e) {
+ handleException(response, "confirmCredentials", account.toString(), e);
+ }
+ }
+
+ @android.annotation.EnforcePermission(android.Manifest.permission.ACCOUNT_MANAGER)
+ @Override
+ public void getAuthTokenLabel(IAccountAuthenticatorResponse response,
+ String authTokenType)
+ throws RemoteException {
+ super.getAuthTokenLabel_enforcePermission();
+
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "getAuthTokenLabel: authTokenType " + authTokenType);
+ }
+ try {
+ Bundle result = new Bundle();
+ result.putString(AccountManager.KEY_AUTH_TOKEN_LABEL,
+ AbstractAccountAuthenticator.this.getAuthTokenLabel(authTokenType));
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ if (result != null) {
+ result.keySet(); // force it to be unparcelled
+ }
+ Log.v(TAG, "getAuthTokenLabel: result "
+ + AccountManager.sanitizeResult(result));
+ }
+ response.onResult(result);
+ } catch (Exception e) {
+ handleException(response, "getAuthTokenLabel", authTokenType, e);
+ }
+ }
+
+ @android.annotation.EnforcePermission(android.Manifest.permission.ACCOUNT_MANAGER)
+ @Override
+ public void getAuthToken(IAccountAuthenticatorResponse response,
+ Account account, String authTokenType, Bundle loginOptions)
+ throws RemoteException {
+ super.getAuthToken_enforcePermission();
+
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "getAuthToken: " + account
+ + ", authTokenType " + authTokenType);
+ }
+ try {
+ final Bundle result = AbstractAccountAuthenticator.this.getAuthToken(
+ new AccountAuthenticatorResponse(response), account,
+ authTokenType, loginOptions);
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ if (result != null) {
+ result.keySet(); // force it to be unparcelled
+ }
+ Log.v(TAG, "getAuthToken: result " + AccountManager.sanitizeResult(result));
+ }
+ if (result != null) {
+ response.onResult(result);
+ }
+ } catch (Exception e) {
+ handleException(response, "getAuthToken",
+ account.toString() + "," + authTokenType, e);
+ }
+ }
+
+ @android.annotation.EnforcePermission(android.Manifest.permission.ACCOUNT_MANAGER)
+ @Override
+ public void updateCredentials(IAccountAuthenticatorResponse response, Account account,
+ String authTokenType, Bundle loginOptions) throws RemoteException {
+ super.updateCredentials_enforcePermission();
+
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "updateCredentials: " + account
+ + ", authTokenType " + authTokenType);
+ }
+ try {
+ final Bundle result = AbstractAccountAuthenticator.this.updateCredentials(
+ new AccountAuthenticatorResponse(response), account,
+ authTokenType, loginOptions);
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ // Result may be null.
+ if (result != null) {
+ result.keySet(); // force it to be unparcelled
+ }
+ Log.v(TAG, "updateCredentials: result "
+ + AccountManager.sanitizeResult(result));
+ }
+ if (result != null) {
+ response.onResult(result);
+ }
+ } catch (Exception e) {
+ handleException(response, "updateCredentials",
+ account.toString() + "," + authTokenType, e);
+ }
+ }
+
+ @android.annotation.EnforcePermission(android.Manifest.permission.ACCOUNT_MANAGER)
+ @Override
+ public void editProperties(IAccountAuthenticatorResponse response,
+ String accountType) throws RemoteException {
+ super.editProperties_enforcePermission();
+
+ try {
+ final Bundle result = AbstractAccountAuthenticator.this.editProperties(
+ new AccountAuthenticatorResponse(response), accountType);
+ if (result != null) {
+ response.onResult(result);
+ }
+ } catch (Exception e) {
+ handleException(response, "editProperties", accountType, e);
+ }
+ }
+
+ @android.annotation.EnforcePermission(android.Manifest.permission.ACCOUNT_MANAGER)
+ @Override
+ public void hasFeatures(IAccountAuthenticatorResponse response,
+ Account account, String[] features) throws RemoteException {
+ super.hasFeatures_enforcePermission();
+
+ try {
+ final Bundle result = AbstractAccountAuthenticator.this.hasFeatures(
+ new AccountAuthenticatorResponse(response), account, features);
+ if (result != null) {
+ response.onResult(result);
+ }
+ } catch (Exception e) {
+ handleException(response, "hasFeatures", account.toString(), e);
+ }
+ }
+
+ @android.annotation.EnforcePermission(android.Manifest.permission.ACCOUNT_MANAGER)
+ @Override
+ public void getAccountRemovalAllowed(IAccountAuthenticatorResponse response,
+ Account account) throws RemoteException {
+ super.getAccountRemovalAllowed_enforcePermission();
+
+ try {
+ final Bundle result = AbstractAccountAuthenticator.this.getAccountRemovalAllowed(
+ new AccountAuthenticatorResponse(response), account);
+ if (result != null) {
+ response.onResult(result);
+ }
+ } catch (Exception e) {
+ handleException(response, "getAccountRemovalAllowed", account.toString(), e);
+ }
+ }
+
+ @android.annotation.EnforcePermission(android.Manifest.permission.ACCOUNT_MANAGER)
+ @Override
+ public void getAccountCredentialsForCloning(IAccountAuthenticatorResponse response,
+ Account account) throws RemoteException {
+ super.getAccountCredentialsForCloning_enforcePermission();
+
+ try {
+ final Bundle result =
+ AbstractAccountAuthenticator.this.getAccountCredentialsForCloning(
+ new AccountAuthenticatorResponse(response), account);
+ if (result != null) {
+ response.onResult(result);
+ }
+ } catch (Exception e) {
+ handleException(response, "getAccountCredentialsForCloning", account.toString(), e);
+ }
+ }
+
+ @android.annotation.EnforcePermission(android.Manifest.permission.ACCOUNT_MANAGER)
+ @Override
+ public void addAccountFromCredentials(IAccountAuthenticatorResponse response,
+ Account account,
+ Bundle accountCredentials) throws RemoteException {
+ super.addAccountFromCredentials_enforcePermission();
+
+ try {
+ final Bundle result =
+ AbstractAccountAuthenticator.this.addAccountFromCredentials(
+ new AccountAuthenticatorResponse(response), account,
+ accountCredentials);
+ if (result != null) {
+ response.onResult(result);
+ }
+ } catch (Exception e) {
+ handleException(response, "addAccountFromCredentials", account.toString(), e);
+ }
+ }
+
+ @android.annotation.EnforcePermission(android.Manifest.permission.ACCOUNT_MANAGER)
+ @Override
+ public void startAddAccountSession(IAccountAuthenticatorResponse response,
+ String accountType, String authTokenType, String[] features, Bundle options)
+ throws RemoteException {
+ super.startAddAccountSession_enforcePermission();
+
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG,
+ "startAddAccountSession: accountType " + accountType
+ + ", authTokenType " + authTokenType
+ + ", features " + (features == null ? "[]" : Arrays.toString(features)));
+ }
+ try {
+ final Bundle result = AbstractAccountAuthenticator.this.startAddAccountSession(
+ new AccountAuthenticatorResponse(response), accountType, authTokenType,
+ features, options);
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ if (result != null) {
+ result.keySet(); // force it to be unparcelled
+ }
+ Log.v(TAG, "startAddAccountSession: result "
+ + AccountManager.sanitizeResult(result));
+ }
+ if (result != null) {
+ response.onResult(result);
+ }
+ } catch (Exception e) {
+ handleException(response, "startAddAccountSession", accountType, e);
+ }
+ }
+
+ @android.annotation.EnforcePermission(android.Manifest.permission.ACCOUNT_MANAGER)
+ @Override
+ public void startUpdateCredentialsSession(
+ IAccountAuthenticatorResponse response,
+ Account account,
+ String authTokenType,
+ Bundle loginOptions) throws RemoteException {
+ super.startUpdateCredentialsSession_enforcePermission();
+
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "startUpdateCredentialsSession: "
+ + account
+ + ", authTokenType "
+ + authTokenType);
+ }
+ try {
+ final Bundle result = AbstractAccountAuthenticator.this
+ .startUpdateCredentialsSession(
+ new AccountAuthenticatorResponse(response),
+ account,
+ authTokenType,
+ loginOptions);
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ // Result may be null.
+ if (result != null) {
+ result.keySet(); // force it to be unparcelled
+ }
+ Log.v(TAG, "startUpdateCredentialsSession: result "
+ + AccountManager.sanitizeResult(result));
+
+ }
+ if (result != null) {
+ response.onResult(result);
+ }
+ } catch (Exception e) {
+ handleException(response, "startUpdateCredentialsSession",
+ account.toString() + "," + authTokenType, e);
+
+ }
+ }
+
+ @android.annotation.EnforcePermission(android.Manifest.permission.ACCOUNT_MANAGER)
+ @Override
+ public void finishSession(
+ IAccountAuthenticatorResponse response,
+ String accountType,
+ Bundle sessionBundle) throws RemoteException {
+ super.finishSession_enforcePermission();
+
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "finishSession: accountType " + accountType);
+ }
+ try {
+ final Bundle result = AbstractAccountAuthenticator.this.finishSession(
+ new AccountAuthenticatorResponse(response), accountType, sessionBundle);
+ if (result != null) {
+ result.keySet(); // force it to be unparcelled
+ }
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "finishSession: result " + AccountManager.sanitizeResult(result));
+ }
+ if (result != null) {
+ response.onResult(result);
+ }
+ } catch (Exception e) {
+ handleException(response, "finishSession", accountType, e);
+
+ }
+ }
+
+ @android.annotation.EnforcePermission(android.Manifest.permission.ACCOUNT_MANAGER)
+ @Override
+ public void isCredentialsUpdateSuggested(
+ IAccountAuthenticatorResponse response,
+ Account account,
+ String statusToken) throws RemoteException {
+ super.isCredentialsUpdateSuggested_enforcePermission();
+
+ try {
+ final Bundle result = AbstractAccountAuthenticator.this
+ .isCredentialsUpdateSuggested(
+ new AccountAuthenticatorResponse(response), account, statusToken);
+ if (result != null) {
+ response.onResult(result);
+ }
+ } catch (Exception e) {
+ handleException(response, "isCredentialsUpdateSuggested", account.toString(), e);
+ }
+ }
+ }
+
+ private void handleException(IAccountAuthenticatorResponse response, String method,
+ String data, Exception e) throws RemoteException {
+ if (e instanceof NetworkErrorException) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, method + "(" + data + ")", e);
+ }
+ response.onError(AccountManager.ERROR_CODE_NETWORK_ERROR, e.getMessage());
+ } else if (e instanceof UnsupportedOperationException) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, method + "(" + data + ")", e);
+ }
+ response.onError(AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION,
+ method + " not supported");
+ } else if (e instanceof IllegalArgumentException) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, method + "(" + data + ")", e);
+ }
+ response.onError(AccountManager.ERROR_CODE_BAD_ARGUMENTS,
+ method + " not supported");
+ } else {
+ Log.w(TAG, method + "(" + data + ")", e);
+ response.onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION,
+ method + " failed");
+ }
+ }
+
+ private Transport mTransport = new Transport();
+
+ /**
+ * @return the IBinder for the AccountAuthenticator
+ */
+ public final IBinder getIBinder() {
+ return mTransport.asBinder();
+ }
+
+ /**
+ * Returns a Bundle that contains the Intent of the activity that can be used to edit the
+ * properties. In order to indicate success the activity should call response.setResult()
+ * with a non-null Bundle.
+ * @param response used to set the result for the request. If the Constants.INTENT_KEY
+ * is set in the bundle then this response field is to be used for sending future
+ * results if and when the Intent is started.
+ * @param accountType the AccountType whose properties are to be edited.
+ * @return a Bundle containing the result or the Intent to start to continue the request.
+ * If this is null then the request is considered to still be active and the result should
+ * sent later using response.
+ */
+ public abstract Bundle editProperties(AccountAuthenticatorResponse response,
+ String accountType);
+
+ /**
+ * Adds an account of the specified accountType.
+ * @param response to send the result back to the AccountManager, will never be null
+ * @param accountType the type of account to add, will never be null
+ * @param authTokenType the type of auth token to retrieve after adding the account, may be null
+ * @param requiredFeatures a String array of authenticator-specific features that the added
+ * account must support, may be null
+ * @param options a Bundle of authenticator-specific options. It always contains
+ * {@link AccountManager#KEY_CALLER_PID} and {@link AccountManager#KEY_CALLER_UID}
+ * fields which will let authenticator know the identity of the caller.
+ * @return a Bundle result or null if the result is to be returned via the response. The result
+ * will contain either:
+ * <ul>
+ * <li> {@link AccountManager#KEY_INTENT}, or
+ * <li> {@link AccountManager#KEY_ACCOUNT_NAME} and {@link AccountManager#KEY_ACCOUNT_TYPE} of
+ * the account that was added, or
+ * <li> {@link AccountManager#KEY_ERROR_CODE} and {@link AccountManager#KEY_ERROR_MESSAGE} to
+ * indicate an error
+ * </ul>
+ * @throws NetworkErrorException if the authenticator could not honor the request due to a
+ * network error
+ */
+ public abstract Bundle addAccount(AccountAuthenticatorResponse response, String accountType,
+ String authTokenType, String[] requiredFeatures, Bundle options)
+ throws NetworkErrorException;
+
+ /**
+ * Checks that the user knows the credentials of an account.
+ * @param response to send the result back to the AccountManager, will never be null
+ * @param account the account whose credentials are to be checked, will never be null
+ * @param options a Bundle of authenticator-specific options, may be null
+ * @return a Bundle result or null if the result is to be returned via the response. The result
+ * will contain either:
+ * <ul>
+ * <li> {@link AccountManager#KEY_INTENT}, or
+ * <li> {@link AccountManager#KEY_BOOLEAN_RESULT}, true if the check succeeded, false otherwise
+ * <li> {@link AccountManager#KEY_ERROR_CODE} and {@link AccountManager#KEY_ERROR_MESSAGE} to
+ * indicate an error
+ * </ul>
+ * @throws NetworkErrorException if the authenticator could not honor the request due to a
+ * network error
+ */
+ public abstract Bundle confirmCredentials(AccountAuthenticatorResponse response,
+ Account account, Bundle options)
+ throws NetworkErrorException;
+
+ /**
+ * Gets an authtoken for an account.
+ *
+ * If not {@code null}, the resultant {@link Bundle} will contain different sets of keys
+ * depending on whether a token was successfully issued and, if not, whether one
+ * could be issued via some {@link android.app.Activity}.
+ * <p>
+ * If a token cannot be provided without some additional activity, the Bundle should contain
+ * {@link AccountManager#KEY_INTENT} with an associated {@link Intent}. On the other hand, if
+ * there is no such activity, then a Bundle containing
+ * {@link AccountManager#KEY_ERROR_CODE} and {@link AccountManager#KEY_ERROR_MESSAGE} should be
+ * returned.
+ * <p>
+ * If a token can be successfully issued, the implementation should return the
+ * {@link AccountManager#KEY_ACCOUNT_NAME} and {@link AccountManager#KEY_ACCOUNT_TYPE} of the
+ * account associated with the token as well as the {@link AccountManager#KEY_AUTHTOKEN}. In
+ * addition {@link AbstractAccountAuthenticator} implementations that declare themselves
+ * {@code android:customTokens=true} may also provide a non-negative {@link
+ * #KEY_CUSTOM_TOKEN_EXPIRY} long value containing the expiration timestamp of the expiration
+ * time (in millis since the unix epoch), tokens will be cached in memory based on
+ * application's packageName/signature for however long that was specified.
+ * <p>
+ * Implementers should assume that tokens will be cached on the basis of account and
+ * authTokenType. The system may ignore the contents of the supplied options Bundle when
+ * determining to re-use a cached token. Furthermore, implementers should assume a supplied
+ * expiration time will be treated as non-binding advice.
+ * <p>
+ * Finally, note that for {@code android:customTokens=false} authenticators, tokens are cached
+ * indefinitely until some client calls {@link
+ * AccountManager#invalidateAuthToken(String,String)}.
+ *
+ * @param response to send the result back to the AccountManager, will never be null
+ * @param account the account whose credentials are to be retrieved, will never be null
+ * @param authTokenType the type of auth token to retrieve, will never be null
+ * @param options a Bundle of authenticator-specific options. It always contains
+ * {@link AccountManager#KEY_CALLER_PID} and {@link AccountManager#KEY_CALLER_UID}
+ * fields which will let authenticator know the identity of the caller.
+ * @return a Bundle result or null if the result is to be returned via the response.
+ * @throws NetworkErrorException if the authenticator could not honor the request due to a
+ * network error
+ */
+ public abstract Bundle getAuthToken(AccountAuthenticatorResponse response,
+ Account account, String authTokenType, Bundle options)
+ throws NetworkErrorException;
+
+ /**
+ * Ask the authenticator for a localized label for the given authTokenType.
+ * @param authTokenType the authTokenType whose label is to be returned, will never be null
+ * @return the localized label of the auth token type, may be null if the type isn't known
+ */
+ public abstract String getAuthTokenLabel(String authTokenType);
+
+ /**
+ * Update the locally stored credentials for an account.
+ * @param response to send the result back to the AccountManager, will never be null
+ * @param account the account whose credentials are to be updated, will never be null
+ * @param authTokenType the type of auth token to retrieve after updating the credentials,
+ * may be null
+ * @param options a Bundle of authenticator-specific options, may be null
+ * @return a Bundle result or null if the result is to be returned via the response. The result
+ * will contain either:
+ * <ul>
+ * <li> {@link AccountManager#KEY_INTENT}, or
+ * <li> {@link AccountManager#KEY_ACCOUNT_NAME} and {@link AccountManager#KEY_ACCOUNT_TYPE} of
+ * the account whose credentials were updated, or
+ * <li> {@link AccountManager#KEY_ERROR_CODE} and {@link AccountManager#KEY_ERROR_MESSAGE} to
+ * indicate an error
+ * </ul>
+ * @throws NetworkErrorException if the authenticator could not honor the request due to a
+ * network error
+ */
+ public abstract Bundle updateCredentials(AccountAuthenticatorResponse response,
+ Account account, String authTokenType, Bundle options) throws NetworkErrorException;
+
+ /**
+ * Checks if the account supports all the specified authenticator specific features.
+ * @param response to send the result back to the AccountManager, will never be null
+ * @param account the account to check, will never be null
+ * @param features an array of features to check, will never be null
+ * @return a Bundle result or null if the result is to be returned via the response. The result
+ * will contain either:
+ * <ul>
+ * <li> {@link AccountManager#KEY_INTENT}, or
+ * <li> {@link AccountManager#KEY_BOOLEAN_RESULT}, true if the account has all the features,
+ * false otherwise
+ * <li> {@link AccountManager#KEY_ERROR_CODE} and {@link AccountManager#KEY_ERROR_MESSAGE} to
+ * indicate an error
+ * </ul>
+ * @throws NetworkErrorException if the authenticator could not honor the request due to a
+ * network error
+ */
+ public abstract Bundle hasFeatures(AccountAuthenticatorResponse response,
+ Account account, String[] features) throws NetworkErrorException;
+
+ /**
+ * Checks if the removal of this account is allowed.
+ * @param response to send the result back to the AccountManager, will never be null
+ * @param account the account to check, will never be null
+ * @return a Bundle result or null if the result is to be returned via the response. The result
+ * will contain either:
+ * <ul>
+ * <li> {@link AccountManager#KEY_INTENT}, or
+ * <li> {@link AccountManager#KEY_BOOLEAN_RESULT}, true if the removal of the account is
+ * allowed, false otherwise
+ * <li> {@link AccountManager#KEY_ERROR_CODE} and {@link AccountManager#KEY_ERROR_MESSAGE} to
+ * indicate an error
+ * </ul>
+ * @throws NetworkErrorException if the authenticator could not honor the request due to a
+ * network error
+ */
+ public Bundle getAccountRemovalAllowed(AccountAuthenticatorResponse response,
+ Account account) throws NetworkErrorException {
+ final Bundle result = new Bundle();
+ result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, true);
+ return result;
+ }
+
+ /**
+ * Returns a Bundle that contains whatever is required to clone the account on a different
+ * user. The Bundle is passed to the authenticator instance in the target user via
+ * {@link #addAccountFromCredentials(AccountAuthenticatorResponse, Account, Bundle)}.
+ * The default implementation returns null, indicating that cloning is not supported.
+ * @param response to send the result back to the AccountManager, will never be null
+ * @param account the account to clone, will never be null
+ * @return a Bundle result or null if the result is to be returned via the response.
+ * @throws NetworkErrorException
+ * @see #addAccountFromCredentials(AccountAuthenticatorResponse, Account, Bundle)
+ */
+ public Bundle getAccountCredentialsForCloning(final AccountAuthenticatorResponse response,
+ final Account account) throws NetworkErrorException {
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ Bundle result = new Bundle();
+ result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, false);
+ response.onResult(result);
+ }
+ }).start();
+ return null;
+ }
+
+ /**
+ * Creates an account based on credentials provided by the authenticator instance of another
+ * user on the device, who has chosen to share the account with this user.
+ * @param response to send the result back to the AccountManager, will never be null
+ * @param account the account to clone, will never be null
+ * @param accountCredentials the Bundle containing the required credentials to create the
+ * account. Contents of the Bundle are only meaningful to the authenticator. This Bundle is
+ * provided by {@link #getAccountCredentialsForCloning(AccountAuthenticatorResponse, Account)}.
+ * @return a Bundle result or null if the result is to be returned via the response.
+ * @throws NetworkErrorException
+ * @see #getAccountCredentialsForCloning(AccountAuthenticatorResponse, Account)
+ */
+ public Bundle addAccountFromCredentials(final AccountAuthenticatorResponse response,
+ Account account,
+ Bundle accountCredentials) throws NetworkErrorException {
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ Bundle result = new Bundle();
+ result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, false);
+ response.onResult(result);
+ }
+ }).start();
+ return null;
+ }
+
+ /**
+ * Starts the add account session to authenticate user to an account of the
+ * specified accountType. No file I/O should be performed in this call.
+ * Account should be added to device only when {@link #finishSession} is
+ * called after this.
+ * <p>
+ * Note: when overriding this method, {@link #finishSession} should be
+ * overridden too.
+ * </p>
+ *
+ * @param response to send the result back to the AccountManager, will never
+ * be null
+ * @param accountType the type of account to authenticate with, will never
+ * be null
+ * @param authTokenType the type of auth token to retrieve after
+ * authenticating with the account, may be null
+ * @param requiredFeatures a String array of authenticator-specific features
+ * that the account authenticated with must support, may be null
+ * @param options a Bundle of authenticator-specific options, may be null
+ * @return a Bundle result or null if the result is to be returned via the
+ * response. The result will contain either:
+ * <ul>
+ * <li>{@link AccountManager#KEY_INTENT}, or
+ * <li>{@link AccountManager#KEY_ACCOUNT_SESSION_BUNDLE} for adding
+ * the account to device later, and if account is authenticated,
+ * optional {@link AccountManager#KEY_PASSWORD} and
+ * {@link AccountManager#KEY_ACCOUNT_STATUS_TOKEN} for checking the
+ * status of the account, or
+ * <li>{@link AccountManager#KEY_ERROR_CODE} and
+ * {@link AccountManager#KEY_ERROR_MESSAGE} to indicate an error
+ * </ul>
+ * @throws NetworkErrorException if the authenticator could not honor the
+ * request due to a network error
+ * @see #finishSession(AccountAuthenticatorResponse, String, Bundle)
+ */
+ public Bundle startAddAccountSession(
+ final AccountAuthenticatorResponse response,
+ final String accountType,
+ final String authTokenType,
+ final String[] requiredFeatures,
+ final Bundle options)
+ throws NetworkErrorException {
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ Bundle sessionBundle = new Bundle();
+ sessionBundle.putString(KEY_AUTH_TOKEN_TYPE, authTokenType);
+ sessionBundle.putStringArray(KEY_REQUIRED_FEATURES, requiredFeatures);
+ sessionBundle.putBundle(KEY_OPTIONS, options);
+ Bundle result = new Bundle();
+ result.putBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE, sessionBundle);
+ response.onResult(result);
+ }
+
+ }).start();
+ return null;
+ }
+
+ /**
+ * Asks user to re-authenticate for an account but defers updating the
+ * locally stored credentials. No file I/O should be performed in this call.
+ * Local credentials should be updated only when {@link #finishSession} is
+ * called after this.
+ * <p>
+ * Note: when overriding this method, {@link #finishSession} should be
+ * overridden too.
+ * </p>
+ *
+ * @param response to send the result back to the AccountManager, will never
+ * be null
+ * @param account the account whose credentials are to be updated, will
+ * never be null
+ * @param authTokenType the type of auth token to retrieve after updating
+ * the credentials, may be null
+ * @param options a Bundle of authenticator-specific options, may be null
+ * @return a Bundle result or null if the result is to be returned via the
+ * response. The result will contain either:
+ * <ul>
+ * <li>{@link AccountManager#KEY_INTENT}, or
+ * <li>{@link AccountManager#KEY_ACCOUNT_SESSION_BUNDLE} for
+ * updating the locally stored credentials later, and if account is
+ * re-authenticated, optional {@link AccountManager#KEY_PASSWORD}
+ * and {@link AccountManager#KEY_ACCOUNT_STATUS_TOKEN} for checking
+ * the status of the account later, or
+ * <li>{@link AccountManager#KEY_ERROR_CODE} and
+ * {@link AccountManager#KEY_ERROR_MESSAGE} to indicate an error
+ * </ul>
+ * @throws NetworkErrorException if the authenticator could not honor the
+ * request due to a network error
+ * @see #finishSession(AccountAuthenticatorResponse, String, Bundle)
+ */
+ public Bundle startUpdateCredentialsSession(
+ final AccountAuthenticatorResponse response,
+ final Account account,
+ final String authTokenType,
+ final Bundle options) throws NetworkErrorException {
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ Bundle sessionBundle = new Bundle();
+ sessionBundle.putString(KEY_AUTH_TOKEN_TYPE, authTokenType);
+ sessionBundle.putParcelable(KEY_ACCOUNT, account);
+ sessionBundle.putBundle(KEY_OPTIONS, options);
+ Bundle result = new Bundle();
+ result.putBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE, sessionBundle);
+ response.onResult(result);
+ }
+
+ }).start();
+ return null;
+ }
+
+ /**
+ * Finishes the session started by #startAddAccountSession or
+ * #startUpdateCredentials by installing the account to device with
+ * AccountManager, or updating the local credentials. File I/O may be
+ * performed in this call.
+ * <p>
+ * Note: when overriding this method, {@link #startAddAccountSession} and
+ * {@link #startUpdateCredentialsSession} should be overridden too.
+ * </p>
+ *
+ * @param response to send the result back to the AccountManager, will never
+ * be null
+ * @param accountType the type of account to authenticate with, will never
+ * be null
+ * @param sessionBundle a bundle of session data created by
+ * {@link #startAddAccountSession} used for adding account to
+ * device, or by {@link #startUpdateCredentialsSession} used for
+ * updating local credentials.
+ * @return a Bundle result or null if the result is to be returned via the
+ * response. The result will contain either:
+ * <ul>
+ * <li>{@link AccountManager#KEY_INTENT}, or
+ * <li>{@link AccountManager#KEY_ACCOUNT_NAME} and
+ * {@link AccountManager#KEY_ACCOUNT_TYPE} of the account that was
+ * added or local credentials were updated, and optional
+ * {@link AccountManager#KEY_ACCOUNT_STATUS_TOKEN} for checking
+ * the status of the account later, or
+ * <li>{@link AccountManager#KEY_ERROR_CODE} and
+ * {@link AccountManager#KEY_ERROR_MESSAGE} to indicate an error
+ * </ul>
+ * @throws NetworkErrorException if the authenticator could not honor the request due to a
+ * network error
+ * @see #startAddAccountSession and #startUpdateCredentialsSession
+ */
+ public Bundle finishSession(
+ final AccountAuthenticatorResponse response,
+ final String accountType,
+ final Bundle sessionBundle) throws NetworkErrorException {
+ if (TextUtils.isEmpty(accountType)) {
+ Log.e(TAG, "Account type cannot be empty.");
+ Bundle result = new Bundle();
+ result.putInt(AccountManager.KEY_ERROR_CODE, AccountManager.ERROR_CODE_BAD_ARGUMENTS);
+ result.putString(AccountManager.KEY_ERROR_MESSAGE,
+ "accountType cannot be empty.");
+ return result;
+ }
+
+ if (sessionBundle == null) {
+ Log.e(TAG, "Session bundle cannot be null.");
+ Bundle result = new Bundle();
+ result.putInt(AccountManager.KEY_ERROR_CODE, AccountManager.ERROR_CODE_BAD_ARGUMENTS);
+ result.putString(AccountManager.KEY_ERROR_MESSAGE,
+ "sessionBundle cannot be null.");
+ return result;
+ }
+
+ if (!sessionBundle.containsKey(KEY_AUTH_TOKEN_TYPE)) {
+ // We cannot handle Session bundle not created by default startAddAccountSession(...)
+ // nor startUpdateCredentialsSession(...) implementation. Return error.
+ Bundle result = new Bundle();
+ result.putInt(AccountManager.KEY_ERROR_CODE,
+ AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION);
+ result.putString(AccountManager.KEY_ERROR_MESSAGE,
+ "Authenticator must override finishSession if startAddAccountSession"
+ + " or startUpdateCredentialsSession is overridden.");
+ response.onResult(result);
+ return result;
+ }
+ String authTokenType = sessionBundle.getString(KEY_AUTH_TOKEN_TYPE);
+ Bundle options = sessionBundle.getBundle(KEY_OPTIONS);
+ String[] requiredFeatures = sessionBundle.getStringArray(KEY_REQUIRED_FEATURES);
+ Account account = sessionBundle.getParcelable(KEY_ACCOUNT, android.accounts.Account.class);
+ boolean containsKeyAccount = sessionBundle.containsKey(KEY_ACCOUNT);
+
+ // Actual options passed to add account or update credentials flow.
+ Bundle sessionOptions = new Bundle(sessionBundle);
+ // Remove redundant extras in session bundle before passing it to addAccount(...) or
+ // updateCredentials(...).
+ sessionOptions.remove(KEY_AUTH_TOKEN_TYPE);
+ sessionOptions.remove(KEY_REQUIRED_FEATURES);
+ sessionOptions.remove(KEY_OPTIONS);
+ sessionOptions.remove(KEY_ACCOUNT);
+
+ if (options != null) {
+ // options may contains old system info such as
+ // AccountManager.KEY_ANDROID_PACKAGE_NAME required by the add account flow or update
+ // credentials flow, we should replace with the new values of the current call added
+ // to sessionBundle by AccountManager or AccountManagerService.
+ options.putAll(sessionOptions);
+ sessionOptions = options;
+ }
+
+ // Session bundle created by startUpdateCredentialsSession default implementation should
+ // contain KEY_ACCOUNT.
+ if (containsKeyAccount) {
+ return updateCredentials(response, account, authTokenType, options);
+ }
+ // Otherwise, session bundle was created by startAddAccountSession default implementation.
+ return addAccount(response, accountType, authTokenType, requiredFeatures, sessionOptions);
+ }
+
+ /**
+ * Checks if update of the account credentials is suggested.
+ *
+ * @param response to send the result back to the AccountManager, will never be null.
+ * @param account the account to check, will never be null
+ * @param statusToken a String of token which can be used to check the status of locally
+ * stored credentials and if update of credentials is suggested
+ * @return a Bundle result or null if the result is to be returned via the response. The result
+ * will contain either:
+ * <ul>
+ * <li>{@link AccountManager#KEY_BOOLEAN_RESULT}, true if update of account's
+ * credentials is suggested, false otherwise
+ * <li>{@link AccountManager#KEY_ERROR_CODE} and
+ * {@link AccountManager#KEY_ERROR_MESSAGE} to indicate an error
+ * </ul>
+ * @throws NetworkErrorException if the authenticator could not honor the request due to a
+ * network error
+ */
+ public Bundle isCredentialsUpdateSuggested(
+ final AccountAuthenticatorResponse response,
+ Account account,
+ String statusToken) throws NetworkErrorException {
+ Bundle result = new Bundle();
+ result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, false);
+ return result;
+ }
+}
diff --git a/android-35/android/accounts/Account.java b/android-35/android/accounts/Account.java
new file mode 100644
index 0000000..76735b6
--- /dev/null
+++ b/android-35/android/accounts/Account.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2009 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.accounts;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.text.TextUtils;
+import android.util.ArraySet;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.Set;
+
+/**
+ * Value type that represents an Account in the {@link AccountManager}. This object is
+ * {@link Parcelable} and also overrides {@link #equals} and {@link #hashCode}, making it
+ * suitable for use as the key of a {@link java.util.Map}
+ */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
+public class Account implements Parcelable {
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ private static final String TAG = "Account";
+
+ @GuardedBy("sAccessedAccounts")
+ private static final Set<Account> sAccessedAccounts = new ArraySet<>();
+
+ public final @NonNull String name;
+ public final @NonNull String type;
+ private String mSafeName;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ private final @Nullable String accessId;
+
+ public boolean equals(@Nullable Object o) {
+ if (o == this) return true;
+ if (!(o instanceof Account)) return false;
+ final Account other = (Account)o;
+ return name.equals(other.name) && type.equals(other.type);
+ }
+
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + name.hashCode();
+ result = 31 * result + type.hashCode();
+ return result;
+ }
+
+ public Account(@NonNull String name, @NonNull String type) {
+ this(name, type, null);
+ }
+
+ /**
+ * @hide
+ */
+ public Account(@NonNull Account other, @NonNull String accessId) {
+ this(other.name, other.type, accessId);
+ }
+
+ /**
+ * @hide
+ */
+ public Account(@NonNull String name, @NonNull String type, String accessId) {
+ if (TextUtils.isEmpty(name)) {
+ throw new IllegalArgumentException("the name must not be empty: " + name);
+ }
+ if (TextUtils.isEmpty(type)) {
+ throw new IllegalArgumentException("the type must not be empty: " + type);
+ }
+ this.name = name;
+ this.type = type;
+ this.accessId = accessId;
+ }
+
+ public Account(Parcel in) {
+ this.name = in.readString();
+ this.type = in.readString();
+ if (TextUtils.isEmpty(name)) {
+ throw new android.os.BadParcelableException("the name must not be empty: " + name);
+ }
+ if (TextUtils.isEmpty(type)) {
+ throw new android.os.BadParcelableException("the type must not be empty: " + type);
+ }
+ this.accessId = in.readString();
+ if (accessId != null) {
+ synchronized (sAccessedAccounts) {
+ if (sAccessedAccounts.add(this)) {
+ onAccountAccessed(accessId);
+ }
+ }
+ }
+ }
+
+ @android.ravenwood.annotation.RavenwoodReplace
+ private static void onAccountAccessed(String accessId) {
+ try {
+ IAccountManager accountManager = IAccountManager.Stub.asInterface(
+ ServiceManager.getService(Context.ACCOUNT_SERVICE));
+ accountManager.onAccountAccessed(accessId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error noting account access", e);
+ }
+ }
+
+ private static void onAccountAccessed$ravenwood(String accessId) {
+ // No AccountManager to communicate with; ignored
+ }
+
+ /** @hide */
+ public String getAccessId() {
+ return accessId;
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(name);
+ dest.writeString(type);
+ dest.writeString(accessId);
+ }
+
+ public static final @android.annotation.NonNull Creator<Account> CREATOR = new Creator<Account>() {
+ public Account createFromParcel(Parcel source) {
+ return new Account(source);
+ }
+
+ public Account[] newArray(int size) {
+ return new Account[size];
+ }
+ };
+
+ public String toString() {
+ return "Account {name=" + name + ", type=" + type + "}";
+ }
+
+ /**
+ * Return a string representation of the account that is safe to print
+ * to logs and other places where PII should be avoided.
+ * @hide
+ */
+ public String toSafeString() {
+ if (mSafeName == null) {
+ mSafeName = toSafeName(name, 'x');
+ }
+ return "Account {name=" + mSafeName + ", type=" + type + "}";
+ }
+
+ /**
+ * Given a name, replace all letter or digits with the replacement char.
+ * @param name The input name string.
+ * @param replacement the replacement character.
+ * @return the string after replacement.
+ * @hide
+ */
+ public static String toSafeName(String name, char replacement) {
+ final StringBuilder builder = new StringBuilder(64);
+ final int len = name.length();
+ for (int i = 0; i < len; i++) {
+ final char c = name.charAt(i);
+ if (Character.isLetterOrDigit(c)) {
+ builder.append(replacement);
+ } else {
+ builder.append(c);
+ }
+ }
+ return builder.toString();
+ }
+}
diff --git a/android-35/android/accounts/AccountAndUser.java b/android-35/android/accounts/AccountAndUser.java
new file mode 100644
index 0000000..adbc4e9
--- /dev/null
+++ b/android-35/android/accounts/AccountAndUser.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2012 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.accounts;
+
+import android.annotation.Nullable;
+import android.compat.annotation.UnsupportedAppUsage;
+
+/**
+ * Used to store the Account and the UserId this account is associated with.
+ *
+ * @hide
+ */
+public class AccountAndUser {
+ @UnsupportedAppUsage
+ public Account account;
+ @UnsupportedAppUsage
+ public int userId;
+
+ @UnsupportedAppUsage
+ public AccountAndUser(Account account, int userId) {
+ this.account = account;
+ this.userId = userId;
+ }
+
+ public boolean equals(@Nullable Object o) {
+ if (this == o) return true;
+ if (!(o instanceof AccountAndUser)) return false;
+ final AccountAndUser other = (AccountAndUser) o;
+ return this.account.equals(other.account)
+ && this.userId == other.userId;
+ }
+
+ @Override
+ public int hashCode() {
+ return account.hashCode() + userId;
+ }
+
+ public String toString() {
+ return account.toString() + " u" + userId;
+ }
+}
diff --git a/android-35/android/accounts/AccountAuthenticatorActivity.java b/android-35/android/accounts/AccountAuthenticatorActivity.java
new file mode 100644
index 0000000..5f620f3
--- /dev/null
+++ b/android-35/android/accounts/AccountAuthenticatorActivity.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2009 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.accounts;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+/**
+ * Base class for implementing an Activity that is used to help implement an
+ * AbstractAccountAuthenticator. If the AbstractAccountAuthenticator needs to use an activity
+ * to handle the request then it can have the activity extend AccountAuthenticatorActivity.
+ * The AbstractAccountAuthenticator passes in the response to the intent using the following:
+ * <pre>
+ * intent.putExtra({@link AccountManager#KEY_ACCOUNT_AUTHENTICATOR_RESPONSE}, response);
+ * </pre>
+ * The activity then sets the result that is to be handed to the response via
+ * {@link #setAccountAuthenticatorResult(android.os.Bundle)}.
+ * This result will be sent as the result of the request when the activity finishes. If this
+ * is never set or if it is set to null then error {@link AccountManager#ERROR_CODE_CANCELED}
+ * will be called on the response.
+ *
+ * @deprecated Applications should extend Activity themselves. This class is not compatible with
+ * AppCompat, and the functionality it provides is not complex.
+ */
+@Deprecated
+public class AccountAuthenticatorActivity extends Activity {
+ private AccountAuthenticatorResponse mAccountAuthenticatorResponse = null;
+ private Bundle mResultBundle = null;
+
+ /**
+ * Set the result that is to be sent as the result of the request that caused this
+ * Activity to be launched. If result is null or this method is never called then
+ * the request will be canceled.
+ * @param result this is returned as the result of the AbstractAccountAuthenticator request
+ */
+ public final void setAccountAuthenticatorResult(Bundle result) {
+ mResultBundle = result;
+ }
+
+ /**
+ * Retrieves the AccountAuthenticatorResponse from either the intent of the icicle, if the
+ * icicle is non-zero.
+ * @param icicle the save instance data of this Activity, may be null
+ */
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ mAccountAuthenticatorResponse =
+ getIntent().getParcelableExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, android.accounts.AccountAuthenticatorResponse.class);
+
+ if (mAccountAuthenticatorResponse != null) {
+ mAccountAuthenticatorResponse.onRequestContinued();
+ }
+ }
+
+ /**
+ * Sends the result or a Constants.ERROR_CODE_CANCELED error if a result isn't present.
+ */
+ public void finish() {
+ if (mAccountAuthenticatorResponse != null) {
+ // send the result bundle back if set, otherwise send an error.
+ if (mResultBundle != null) {
+ mAccountAuthenticatorResponse.onResult(mResultBundle);
+ } else {
+ mAccountAuthenticatorResponse.onError(AccountManager.ERROR_CODE_CANCELED,
+ "canceled");
+ }
+ mAccountAuthenticatorResponse = null;
+ }
+ super.finish();
+ }
+}
diff --git a/android-35/android/accounts/AccountAuthenticatorResponse.java b/android-35/android/accounts/AccountAuthenticatorResponse.java
new file mode 100644
index 0000000..a2a5799
--- /dev/null
+++ b/android-35/android/accounts/AccountAuthenticatorResponse.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2009 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.accounts;
+
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.util.Log;
+
+/**
+ * Object used to communicate responses back to the AccountManager
+ */
+public class AccountAuthenticatorResponse implements Parcelable {
+ private static final String TAG = "AccountAuthenticator";
+
+ private IAccountAuthenticatorResponse mAccountAuthenticatorResponse;
+
+ /**
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public AccountAuthenticatorResponse(IAccountAuthenticatorResponse response) {
+ mAccountAuthenticatorResponse = response;
+ }
+
+ public AccountAuthenticatorResponse(Parcel parcel) {
+ mAccountAuthenticatorResponse =
+ IAccountAuthenticatorResponse.Stub.asInterface(parcel.readStrongBinder());
+ }
+
+ public void onResult(Bundle result) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ result.keySet(); // force it to be unparcelled
+ Log.v(TAG, "AccountAuthenticatorResponse.onResult: "
+ + AccountManager.sanitizeResult(result));
+ }
+ try {
+ mAccountAuthenticatorResponse.onResult(result);
+ } catch (RemoteException e) {
+ // this should never happen
+ }
+ }
+
+ public void onRequestContinued() {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "AccountAuthenticatorResponse.onRequestContinued");
+ }
+ try {
+ mAccountAuthenticatorResponse.onRequestContinued();
+ } catch (RemoteException e) {
+ // this should never happen
+ }
+ }
+
+ public void onError(int errorCode, String errorMessage) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "AccountAuthenticatorResponse.onError: " + errorCode + ", " + errorMessage);
+ }
+ try {
+ mAccountAuthenticatorResponse.onError(errorCode, errorMessage);
+ } catch (RemoteException e) {
+ // this should never happen
+ }
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeStrongBinder(mAccountAuthenticatorResponse.asBinder());
+ }
+
+ public static final @android.annotation.NonNull Creator<AccountAuthenticatorResponse> CREATOR =
+ new Creator<AccountAuthenticatorResponse>() {
+ public AccountAuthenticatorResponse createFromParcel(Parcel source) {
+ return new AccountAuthenticatorResponse(source);
+ }
+
+ public AccountAuthenticatorResponse[] newArray(int size) {
+ return new AccountAuthenticatorResponse[size];
+ }
+ };
+}
diff --git a/android-35/android/accounts/AccountManager.java b/android-35/android/accounts/AccountManager.java
new file mode 100644
index 0000000..497d47a
--- /dev/null
+++ b/android-35/android/accounts/AccountManager.java
@@ -0,0 +1,3480 @@
+/*
+ * Copyright (C) 2009 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.accounts;
+
+import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
+
+import android.annotation.BroadcastBehavior;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.Size;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.annotation.UserHandleAware;
+import android.app.Activity;
+import android.app.PropertyInvalidatedCache;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.IntentSender;
+import android.content.pm.UserPackage;
+import android.content.res.Resources;
+import android.database.SQLException;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Parcelable;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.internal.R;
+
+import com.google.android.collect.Maps;
+
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * This class provides access to a centralized registry of the user's
+ * online accounts. The user enters credentials (username and password) once
+ * per account, granting applications access to online resources with
+ * "one-click" approval.
+ *
+ * <p>Different online services have different ways of handling accounts and
+ * authentication, so the account manager uses pluggable <em>authenticator</em>
+ * modules for different <em>account types</em>. Authenticators (which may be
+ * written by third parties) handle the actual details of validating account
+ * credentials and storing account information. For example, Google, Facebook,
+ * and Microsoft Exchange each have their own authenticator.
+ *
+ * <p>Many servers support some notion of an <em>authentication token</em>,
+ * which can be used to authenticate a request to the server without sending
+ * the user's actual password. (Auth tokens are normally created with a
+ * separate request which does include the user's credentials.) AccountManager
+ * can generate auth tokens for applications, so the application doesn't need to
+ * handle passwords directly. Auth tokens are normally reusable and cached by
+ * AccountManager, but must be refreshed periodically. It's the responsibility
+ * of applications to <em>invalidate</em> auth tokens when they stop working so
+ * the AccountManager knows it needs to regenerate them.
+ *
+ * <p>Applications accessing a server normally go through these steps:
+ *
+ * <ul>
+ * <li>Get an instance of AccountManager using {@link #get(Context)}.
+ *
+ * <li>List the available accounts using {@link #getAccountsByType} or
+ * {@link #getAccountsByTypeAndFeatures}. Normally applications will only
+ * be interested in accounts with one particular <em>type</em>, which
+ * identifies the authenticator. Account <em>features</em> are used to
+ * identify particular account subtypes and capabilities. Both the account
+ * type and features are authenticator-specific strings, and must be known by
+ * the application in coordination with its preferred authenticators.
+ *
+ * <li>Select one or more of the available accounts, possibly by asking the
+ * user for their preference. If no suitable accounts are available,
+ * {@link #addAccount} may be called to prompt the user to create an
+ * account of the appropriate type.
+ *
+ * <li><b>Important:</b> If the application is using a previously remembered
+ * account selection, it must make sure the account is still in the list
+ * of accounts returned by {@link #getAccountsByType}. Requesting an auth token
+ * for an account no longer on the device results in an undefined failure.
+ *
+ * <li>Request an auth token for the selected account(s) using one of the
+ * {@link #getAuthToken} methods or related helpers. Refer to the description
+ * of each method for exact usage and error handling details.
+ *
+ * <li>Make the request using the auth token. The form of the auth token,
+ * the format of the request, and the protocol used are all specific to the
+ * service you are accessing. The application may use whatever network and
+ * protocol libraries are useful.
+ *
+ * <li><b>Important:</b> If the request fails with an authentication error,
+ * it could be that a cached auth token is stale and no longer honored by
+ * the server. The application must call {@link #invalidateAuthToken} to remove
+ * the token from the cache, otherwise requests will continue failing! After
+ * invalidating the auth token, immediately go back to the "Request an auth
+ * token" step above. If the process fails the second time, then it can be
+ * treated as a "genuine" authentication failure and the user notified or other
+ * appropriate actions taken.
+ * </ul>
+ *
+ * <p>Some AccountManager methods may need to interact with the user to
+ * prompt for credentials, present options, or ask the user to add an account.
+ * The caller may choose whether to allow AccountManager to directly launch the
+ * necessary user interface and wait for the user, or to return an Intent which
+ * the caller may use to launch the interface, or (in some cases) to install a
+ * notification which the user can select at any time to launch the interface.
+ * To have AccountManager launch the interface directly, the caller must supply
+ * the current foreground {@link Activity} context.
+ *
+ * <p>Many AccountManager methods take {@link AccountManagerCallback} and
+ * {@link Handler} as parameters. These methods return immediately and
+ * run asynchronously. If a callback is provided then
+ * {@link AccountManagerCallback#run} will be invoked on the Handler's
+ * thread when the request completes, successfully or not.
+ * The result is retrieved by calling {@link AccountManagerFuture#getResult()}
+ * on the {@link AccountManagerFuture} returned by the method (and also passed
+ * to the callback). This method waits for the operation to complete (if
+ * necessary) and either returns the result or throws an exception if an error
+ * occurred during the operation. To make the request synchronously, call
+ * {@link AccountManagerFuture#getResult()} immediately on receiving the
+ * future from the method; no callback need be supplied.
+ *
+ * <p>Requests which may block, including
+ * {@link AccountManagerFuture#getResult()}, must never be called on
+ * the application's main event thread. These operations throw
+ * {@link IllegalStateException} if they are used on the main thread.
+ */
+@SystemService(Context.ACCOUNT_SERVICE)
+public class AccountManager {
+
+ private static final String TAG = "AccountManager";
+
+ public static final int ERROR_CODE_REMOTE_EXCEPTION = 1;
+ public static final int ERROR_CODE_NETWORK_ERROR = 3;
+ public static final int ERROR_CODE_CANCELED = 4;
+ public static final int ERROR_CODE_INVALID_RESPONSE = 5;
+ public static final int ERROR_CODE_UNSUPPORTED_OPERATION = 6;
+ public static final int ERROR_CODE_BAD_ARGUMENTS = 7;
+ public static final int ERROR_CODE_BAD_REQUEST = 8;
+ public static final int ERROR_CODE_BAD_AUTHENTICATION = 9;
+
+ /** @hide */
+ public static final int ERROR_CODE_USER_RESTRICTED = 100;
+ /** @hide */
+ public static final int ERROR_CODE_MANAGEMENT_DISABLED_FOR_ACCOUNT_TYPE = 101;
+
+ /**
+ * Bundle key used for the {@link String} account name in results
+ * from methods which return information about a particular account.
+ */
+ public static final String KEY_ACCOUNT_NAME = "authAccount";
+
+ /**
+ * Bundle key used for the {@link String} account type in results
+ * from methods which return information about a particular account.
+ */
+ public static final String KEY_ACCOUNT_TYPE = "accountType";
+
+ /**
+ * Bundle key used for the account access id used for noting the
+ * account was accessed when unmarshaled from a parcel.
+ *
+ * @hide
+ */
+ public static final String KEY_ACCOUNT_ACCESS_ID = "accountAccessId";
+
+ /**
+ * Bundle key used for the auth token value in results
+ * from {@link #getAuthToken} and friends.
+ */
+ public static final String KEY_AUTHTOKEN = "authtoken";
+
+ /**
+ * Bundle key used for an {@link Intent} in results from methods that
+ * may require the caller to interact with the user. The Intent can
+ * be used to start the corresponding user interface activity.
+ */
+ public static final String KEY_INTENT = "intent";
+
+ /**
+ * Bundle key used to supply the password directly in options to
+ * {@link #confirmCredentials}, rather than prompting the user with
+ * the standard password prompt.
+ */
+ public static final String KEY_PASSWORD = "password";
+
+ public static final String KEY_ACCOUNTS = "accounts";
+
+ public static final String KEY_ACCOUNT_AUTHENTICATOR_RESPONSE = "accountAuthenticatorResponse";
+ public static final String KEY_ACCOUNT_MANAGER_RESPONSE = "accountManagerResponse";
+ public static final String KEY_AUTHENTICATOR_TYPES = "authenticator_types";
+ public static final String KEY_AUTH_FAILED_MESSAGE = "authFailedMessage";
+ public static final String KEY_AUTH_TOKEN_LABEL = "authTokenLabelKey";
+ public static final String KEY_BOOLEAN_RESULT = "booleanResult";
+ public static final String KEY_ERROR_CODE = "errorCode";
+ public static final String KEY_ERROR_MESSAGE = "errorMessage";
+ public static final String KEY_USERDATA = "userdata";
+
+ /**
+ * Bundle key used to supply the last time the credentials of the account
+ * were authenticated successfully. Time is specified in milliseconds since
+ * epoch. Associated time is updated on successful authentication of account
+ * on adding account, confirming credentials, or updating credentials.
+ */
+ public static final String KEY_LAST_AUTHENTICATED_TIME = "lastAuthenticatedTime";
+
+ /**
+ * The UID of caller app.
+ */
+ public static final String KEY_CALLER_UID = "callerUid";
+
+ /**
+ * The process id of caller app.
+ */
+ public static final String KEY_CALLER_PID = "callerPid";
+
+ /**
+ * The Android package of the caller will be set in the options bundle by the
+ * {@link AccountManager} and will be passed to the AccountManagerService and
+ * to the AccountAuthenticators. The uid of the caller will be known by the
+ * AccountManagerService as well as the AccountAuthenticators so they will be able to
+ * verify that the package is consistent with the uid (a uid might be shared by many
+ * packages).
+ */
+ public static final String KEY_ANDROID_PACKAGE_NAME = "androidPackageName";
+
+ /**
+ * Boolean, if set and 'customTokens' the authenticator is responsible for
+ * notifications.
+ * @hide
+ */
+ public static final String KEY_NOTIFY_ON_FAILURE = "notifyOnAuthFailure";
+
+ /**
+ * Bundle key used for a {@link Bundle} in result from
+ * {@link #startAddAccountSession} and friends which returns session data
+ * for installing an account later.
+ */
+ public static final String KEY_ACCOUNT_SESSION_BUNDLE = "accountSessionBundle";
+
+ /**
+ * Bundle key used for the {@link String} account status token in result
+ * from {@link #startAddAccountSession} and friends which returns
+ * information about a particular account.
+ */
+ public static final String KEY_ACCOUNT_STATUS_TOKEN = "accountStatusToken";
+
+ public static final String ACTION_AUTHENTICATOR_INTENT =
+ "android.accounts.AccountAuthenticator";
+ public static final String AUTHENTICATOR_META_DATA_NAME =
+ "android.accounts.AccountAuthenticator";
+ public static final String AUTHENTICATOR_ATTRIBUTES_NAME = "account-authenticator";
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "VISIBILITY_" }, value = {
+ VISIBILITY_UNDEFINED,
+ VISIBILITY_VISIBLE,
+ VISIBILITY_USER_MANAGED_VISIBLE,
+ VISIBILITY_NOT_VISIBLE,
+ VISIBILITY_USER_MANAGED_NOT_VISIBLE
+ })
+ public @interface AccountVisibility {
+ }
+
+ /**
+ * Account visibility was not set. Default visibility value will be used.
+ * See {@link #PACKAGE_NAME_KEY_LEGACY_VISIBLE}, {@link #PACKAGE_NAME_KEY_LEGACY_NOT_VISIBLE}
+ */
+ public static final int VISIBILITY_UNDEFINED = 0;
+
+ /**
+ * Account is always visible to given application and only authenticator can revoke visibility.
+ */
+ public static final int VISIBILITY_VISIBLE = 1;
+
+ /**
+ * Account is visible to given application, but user can revoke visibility.
+ */
+ public static final int VISIBILITY_USER_MANAGED_VISIBLE = 2;
+
+ /**
+ * Account is not visible to given application and only authenticator can grant visibility.
+ */
+ public static final int VISIBILITY_NOT_VISIBLE = 3;
+
+ /**
+ * Account is not visible to given application, but user can reveal it, for example, using
+ * {@link #newChooseAccountIntent(Account, List, String[], String, String, String[], Bundle)}
+ */
+ public static final int VISIBILITY_USER_MANAGED_NOT_VISIBLE = 4;
+
+ /**
+ * Token type for the special case where a UID has access only to an account
+ * but no authenticator specific auth token types.
+ *
+ * @hide
+ */
+ public static final String ACCOUNT_ACCESS_TOKEN_TYPE =
+ "com.android.AccountManager.ACCOUNT_ACCESS_TOKEN_TYPE";
+
+ /**
+ * @hide
+ */
+ public static final String CACHE_KEY_ACCOUNTS_DATA_PROPERTY = "cache_key.system_server.accounts_data";
+
+ /**
+ * @hide
+ */
+ public static final int CACHE_ACCOUNTS_DATA_SIZE = 4;
+
+ PropertyInvalidatedCache<UserPackage, Account[]> mAccountsForUserCache =
+ new PropertyInvalidatedCache<UserPackage, Account[]>(
+ CACHE_ACCOUNTS_DATA_SIZE, CACHE_KEY_ACCOUNTS_DATA_PROPERTY) {
+ @Override
+ public Account[] recompute(UserPackage userAndPackage) {
+ try {
+ return mService.getAccountsAsUser(null, userAndPackage.userId, userAndPackage.packageName);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ @Override
+ public boolean bypass(UserPackage query) {
+ return query.userId < 0;
+ }
+ @Override
+ public boolean resultEquals(Account[] l, Account[] r) {
+ if (l == r) {
+ return true;
+ } else if (l == null || r == null) {
+ return false;
+ } else {
+ return Arrays.equals(l, r);
+ }
+ }
+ };
+
+ /**
+ * @hide
+ */
+ public static final String CACHE_KEY_USER_DATA_PROPERTY = "cache_key.system_server.account_user_data";
+
+ /**
+ * @hide
+ */
+ public static final int CACHE_USER_DATA_SIZE = 4;
+
+ private static final class AccountKeyData {
+ final public Account account;
+ final public String key;
+
+ public AccountKeyData(Account Account, String Key) {
+ this.account = Account;
+ this.key = Key;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (o == null) {
+ return false;
+ }
+
+ if (o == this) {
+ return true;
+ }
+
+ if (o.getClass() != getClass()) {
+ return false;
+ }
+
+ AccountKeyData e = (AccountKeyData) o;
+
+ return e.account.equals(account) && e.key.equals(key);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(account,key);
+ }
+ }
+
+ PropertyInvalidatedCache<AccountKeyData, String> mUserDataCache =
+ new PropertyInvalidatedCache<AccountKeyData, String>(CACHE_USER_DATA_SIZE,
+ CACHE_KEY_USER_DATA_PROPERTY) {
+ @Override
+ public String recompute(AccountKeyData accountKeyData) {
+ Account account = accountKeyData.account;
+ String key = accountKeyData.key;
+
+ if (account == null) throw new IllegalArgumentException("account is null");
+ if (key == null) throw new IllegalArgumentException("key is null");
+ try {
+ return mService.getUserData(account, key);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ };
+
+ @UnsupportedAppUsage
+ private final Context mContext;
+ private final IAccountManager mService;
+ private final Handler mMainHandler;
+
+ /**
+ * Action sent as a broadcast Intent by the AccountsService when accounts are added, accounts
+ * are removed, or an account's credentials (saved password, etc) are changed.
+ *
+ * @see #addOnAccountsUpdatedListener
+ * @see #ACTION_ACCOUNT_REMOVED
+ *
+ * @deprecated use {@link #addOnAccountsUpdatedListener} to get account updates in runtime.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ @BroadcastBehavior(includeBackground = true)
+ public static final String LOGIN_ACCOUNTS_CHANGED_ACTION =
+ "android.accounts.LOGIN_ACCOUNTS_CHANGED";
+
+ /**
+ * Action sent as a broadcast Intent by the AccountsService when any account is removed
+ * or renamed. Only applications which were able to see the account will receive the intent.
+ * Intent extra will include the following fields:
+ * <ul>
+ * <li> {@link #KEY_ACCOUNT_NAME} - the name of the removed account
+ * <li> {@link #KEY_ACCOUNT_TYPE} - the type of the account
+ * </ul>
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ @BroadcastBehavior(includeBackground = true)
+ public static final String ACTION_ACCOUNT_REMOVED =
+ "android.accounts.action.ACCOUNT_REMOVED";
+
+ /**
+ * Action sent as a broadcast Intent to specific package by the AccountsService
+ * when account visibility or account's credentials (saved password, etc) are changed.
+ *
+ * @see #addOnAccountsUpdatedListener
+ *
+ * @hide
+ */
+ public static final String ACTION_VISIBLE_ACCOUNTS_CHANGED =
+ "android.accounts.action.VISIBLE_ACCOUNTS_CHANGED";
+
+ /**
+ * Key to set visibility for applications which satisfy one of the following conditions:
+ * <ul>
+ * <li>Target API level below {@link android.os.Build.VERSION_CODES#O} and have
+ * deprecated {@link android.Manifest.permission#GET_ACCOUNTS} permission.
+ * </li>
+ * <li> Have {@link android.Manifest.permission#GET_ACCOUNTS_PRIVILEGED} permission. </li>
+ * <li> Have the same signature as authenticator. </li>
+ * <li> Have {@link android.Manifest.permission#READ_CONTACTS} permission and
+ * account type may be associated with contacts data - (verified by
+ * {@link android.Manifest.permission#WRITE_CONTACTS} permission check for the authenticator).
+ * </li>
+ * </ul>
+ * See {@link #getAccountVisibility}. If the value was not set by authenticator
+ * {@link #VISIBILITY_USER_MANAGED_VISIBLE} is used.
+ */
+ public static final String PACKAGE_NAME_KEY_LEGACY_VISIBLE =
+ "android:accounts:key_legacy_visible";
+
+ /**
+ * Key to set default visibility for applications which don't satisfy conditions in
+ * {@link #PACKAGE_NAME_KEY_LEGACY_VISIBLE}. If the value was not set by authenticator
+ * {@link #VISIBILITY_USER_MANAGED_NOT_VISIBLE} is used.
+ */
+ public static final String PACKAGE_NAME_KEY_LEGACY_NOT_VISIBLE =
+ "android:accounts:key_legacy_not_visible";
+
+ /**
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public AccountManager(Context context, IAccountManager service) {
+ mContext = context;
+ mService = service;
+ mMainHandler = new Handler(mContext.getMainLooper());
+ }
+
+ /**
+ * @hide used for testing only
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public AccountManager(Context context, IAccountManager service, Handler handler) {
+ mContext = context;
+ mService = service;
+ mMainHandler = handler;
+ }
+
+ /**
+ * @hide for internal use only
+ */
+ public static Bundle sanitizeResult(Bundle result) {
+ if (result != null) {
+ if (result.containsKey(KEY_AUTHTOKEN)
+ && !TextUtils.isEmpty(result.getString(KEY_AUTHTOKEN))) {
+ final Bundle newResult = new Bundle(result);
+ newResult.putString(KEY_AUTHTOKEN, "<omitted for logging purposes>");
+ return newResult;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Gets an AccountManager instance associated with a Context.
+ * The {@link Context} will be used as long as the AccountManager is
+ * active, so make sure to use a {@link Context} whose lifetime is
+ * commensurate with any listeners registered to
+ * {@link #addOnAccountsUpdatedListener} or similar methods.
+ *
+ * <p>It is safe to call this method from the main thread.
+ *
+ * <p>No permission is required to call this method.
+ *
+ * @param context The {@link Context} to use when necessary
+ * @return An {@link AccountManager} instance
+ */
+ public static AccountManager get(Context context) {
+ if (context == null) throw new IllegalArgumentException("context is null");
+ return (AccountManager) context.getSystemService(Context.ACCOUNT_SERVICE);
+ }
+
+ /**
+ * Gets the saved password associated with the account. This is intended for authenticators and
+ * related code; applications should get an auth token instead.
+ *
+ * <p>
+ * It is safe to call this method from the main thread.
+ *
+ * <p>
+ * This method requires the caller to have a signature match with the authenticator that owns
+ * the specified account.
+ *
+ * <p>
+ * <b>NOTE:</b> If targeting your app to work on API level
+ * {@link android.os.Build.VERSION_CODES#LOLLIPOP_MR1} and before, AUTHENTICATE_ACCOUNTS
+ * permission is needed for those platforms. See docs for this function in API level
+ * {@link android.os.Build.VERSION_CODES#LOLLIPOP_MR1}.
+ *
+ * @param account The account to query for a password. Must not be {@code null}.
+ * @return The account's password, null if none or if the account doesn't exist
+ */
+ public String getPassword(final Account account) {
+ if (account == null) throw new IllegalArgumentException("account is null");
+ try {
+ return mService.getPassword(account);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Gets the user data named by "key" associated with the account. This is intended for
+ * authenticators and related code to store arbitrary metadata along with accounts. The meaning
+ * of the keys and values is up to the authenticator for the account.
+ *
+ * <p>
+ * It is safe to call this method from the main thread.
+ *
+ * <p>
+ * This method requires the caller to have a signature match with the authenticator that owns
+ * the specified account.
+ *
+ * <p>
+ * <b>NOTE:</b> If targeting your app to work on API level
+ * {@link android.os.Build.VERSION_CODES#LOLLIPOP_MR1} and before, AUTHENTICATE_ACCOUNTS
+ * permission is needed for those platforms. See docs for this function in API level
+ * {@link android.os.Build.VERSION_CODES#LOLLIPOP_MR1}.
+ *
+ * @param account The account to query for user data
+ * @return The user data, null if the account, key doesn't exist, or the user is locked
+ */
+ public String getUserData(final Account account, final String key) {
+ return mUserDataCache.query(new AccountKeyData(account,key));
+ }
+
+ /**
+ * Lists the currently registered authenticators.
+ *
+ * <p>It is safe to call this method from the main thread.
+ *
+ * <p>No permission is required to call this method.
+ *
+ * <p>Caller targeting API level 34 and above, the results are filtered
+ * by the rules of <a href="/training/basics/intents/package-visibility">package visibility</a>.
+ *
+ * @return An array of {@link AuthenticatorDescription} for every
+ * authenticator known to the AccountManager service. Empty (never
+ * null) if no authenticators are known.
+ */
+ @UserHandleAware
+ public AuthenticatorDescription[] getAuthenticatorTypes() {
+ return getAuthenticatorTypesAsUser(mContext.getUserId());
+ }
+
+ /**
+ * @hide
+ * Lists the currently registered authenticators for a given user id.
+ *
+ * <p>It is safe to call this method from the main thread.
+ *
+ * <p>The caller has to be in the same user or have the permission
+ * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL}.
+ *
+ * @return An array of {@link AuthenticatorDescription} for every
+ * authenticator known to the AccountManager service. Empty (never
+ * null) if no authenticators are known.
+ */
+ public AuthenticatorDescription[] getAuthenticatorTypesAsUser(int userId) {
+ try {
+ return mService.getAuthenticatorTypes(userId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Lists all accounts visible to the caller regardless of type. Equivalent to
+ * getAccountsByType(null). These accounts may be visible because the user granted access to the
+ * account, or the AbstractAccountAuthenticator managing the account did so or because the
+ * client shares a signature with the managing AbstractAccountAuthenticator.
+ *
+ * <div class="caution"><p><b>Caution: </b>This method returns personal and sensitive user data.
+ * If your app accesses, collects, uses, or shares personal and sensitive data, you must clearly
+ * disclose that fact to users. For apps published on Google Play, policies protecting user data
+ * require that you do the following:</p>
+ * <ul>
+ * <li>Disclose to the user how your app accesses, collects, uses, or shares personal and
+ * sensitive data. Learn more about
+ * <a href="https://play.google.com/about/privacy-security-deception/user-data/#!#personal-sensitive">acceptable
+ * disclosure and consent</a>.</li>
+ * <li>Provide a privacy policy that describes your use of this data on- and off-device.</li>
+ * </ul>
+ * <p>To learn more, visit the
+ * <a href="https://play.google.com/about/privacy-security-deception/user-data">Google Play
+ * Policy regarding user data</a>.</p></div>
+ *
+ * <p>
+ * It is safe to call this method from the main thread.
+ *
+ * @return An array of {@link Account}, one for each account. Empty (never null) if no accounts
+ * have been added.
+ */
+ @UserHandleAware
+ @NonNull
+ public Account[] getAccounts() {
+ return getAccountsAsUser(mContext.getUserId());
+ }
+
+ /**
+ * @hide
+ * Lists all accounts visible to caller regardless of type for a given user id. Equivalent to
+ * getAccountsByType(null).
+ *
+ * <p>
+ * It is safe to call this method from the main thread.
+ *
+ * @return An array of {@link Account}, one for each account. Empty (never null) if no accounts
+ * have been added.
+ */
+ @NonNull
+ public Account[] getAccountsAsUser(int userId) {
+ UserPackage userAndPackage = UserPackage.of(userId, mContext.getOpPackageName());
+ return mAccountsForUserCache.query(userAndPackage);
+ }
+
+ /**
+ * @hide
+ * For use by internal activities. Returns the list of accounts that the calling package
+ * is authorized to use, particularly for shared accounts.
+ * @param packageName package name of the calling app.
+ * @param uid the uid of the calling app.
+ * @return the accounts that are available to this package and user.
+ */
+ @NonNull
+ public Account[] getAccountsForPackage(String packageName, int uid) {
+ try {
+ return mService.getAccountsForPackage(packageName, uid, mContext.getOpPackageName());
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns the accounts visible to the specified package in an environment where some apps are
+ * not authorized to view all accounts. This method can only be called by system apps and
+ * authenticators managing the type.
+ * Beginning API level {@link android.os.Build.VERSION_CODES#O} it also return accounts
+ * which user can make visible to the application (see {@link #VISIBILITY_USER_MANAGED_VISIBLE}).
+ *
+ * @param type The type of accounts to return, null to retrieve all accounts
+ * @param packageName The package name of the app for which the accounts are to be returned
+ * @return An array of {@link Account}, one per matching account. Empty (never null) if no
+ * accounts of the specified type can be accessed by the package.
+ *
+ */
+ @NonNull
+ public Account[] getAccountsByTypeForPackage(String type, String packageName) {
+ try {
+ return mService.getAccountsByTypeForPackage(type, packageName,
+ mContext.getOpPackageName());
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Lists all accounts of particular type visible to the caller. These accounts may be visible
+ * because the user granted access to the account, or the AbstractAccountAuthenticator managing
+ * the account did so or because the client shares a signature with the managing
+ * AbstractAccountAuthenticator.
+ *
+ * <p>
+ * The account type is a string token corresponding to the authenticator and useful domain of
+ * the account. For example, there are types corresponding to Google and Facebook. The exact
+ * string token to use will be published somewhere associated with the authenticator in
+ * question.
+ * </p>
+ *
+ * <div class="caution"><p><b>Caution: </b>This method returns personal and sensitive user data.
+ * If your app accesses, collects, uses, or shares personal and sensitive data, you must clearly
+ * disclose that fact to users. For apps published on Google Play, policies protecting user data
+ * require that you do the following:</p>
+ * <ul>
+ * <li>Disclose to the user how your app accesses, collects, uses, or shares personal and
+ * sensitive data. Learn more about
+ * <a href="https://play.google.com/about/privacy-security-deception/user-data/#!#personal-sensitive">acceptable
+ * disclosure and consent</a>.</li>
+ * <li>Provide a privacy policy that describes your use of this data on- and off-device.</li>
+ * </ul>
+ * <p>To learn more, visit the
+ * <a href="https://play.google.com/about/privacy-security-deception/user-data">Google Play
+ * Policy regarding user data</a>.</p></div>
+ *
+ * <p>
+ * It is safe to call this method from the main thread.
+ *
+ * <p>
+ * Caller targeting API level {@link android.os.Build.VERSION_CODES#O} and above, will get list
+ * of accounts made visible to it by user
+ * (see {@link #newChooseAccountIntent(Account, List, String[], String,
+ * String, String[], Bundle)}) or AbstractAccountAuthenticator
+ * using {@link #setAccountVisibility}.
+ * {@link android.Manifest.permission#GET_ACCOUNTS} permission is not used.
+ *
+ * <p>
+ * Caller targeting API level below {@link android.os.Build.VERSION_CODES#O} that have not been
+ * granted the {@link android.Manifest.permission#GET_ACCOUNTS} permission, will only see those
+ * accounts managed by AbstractAccountAuthenticators whose signature matches the client.
+ *
+ * <p>
+ * <b>NOTE:</b> If targeting your app to work on API level
+ * {@link android.os.Build.VERSION_CODES#LOLLIPOP_MR1} and before,
+ * {@link android.Manifest.permission#GET_ACCOUNTS} permission is
+ * needed for those platforms, irrespective of uid or signature match. See docs for this
+ * function in API level {@link android.os.Build.VERSION_CODES#LOLLIPOP_MR1}.
+ *
+ * @param type The type of accounts to return, null to retrieve all accounts
+ * @return An array of {@link Account}, one per matching account. Empty (never null) if no
+ * accounts of the specified type have been added.
+ */
+ @UserHandleAware
+ @NonNull
+ public Account[] getAccountsByType(String type) {
+ return getAccountsByTypeAsUser(type, mContext.getUser());
+ }
+
+ /** @hide Same as {@link #getAccountsByType(String)} but for a specific user. */
+ @NonNull
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public Account[] getAccountsByTypeAsUser(String type, UserHandle userHandle) {
+ try {
+ return mService.getAccountsAsUser(type, userHandle.getIdentifier(),
+ mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Change whether or not an app (identified by its uid) is allowed to retrieve an authToken
+ * for an account.
+ * <p>
+ * This is only meant to be used by system activities and is not in the SDK.
+ * @param account The account whose permissions are being modified
+ * @param authTokenType The type of token whose permissions are being modified
+ * @param uid The uid that identifies the app which is being granted or revoked permission.
+ * @param value true is permission is being granted, false for revoked
+ * @hide
+ */
+ public void updateAppPermission(Account account, String authTokenType, int uid, boolean value) {
+ try {
+ mService.updateAppPermission(account, authTokenType, uid, value);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Get the user-friendly label associated with an authenticator's auth token.
+ * @param accountType the type of the authenticator. must not be null.
+ * @param authTokenType the token type. must not be null.
+ * @param callback callback to invoke when the result is available. may be null.
+ * @param handler the handler on which to invoke the callback, or null for the main thread
+ * @return a future containing the label string
+ * @hide
+ */
+ public AccountManagerFuture<String> getAuthTokenLabel(
+ final String accountType, final String authTokenType,
+ AccountManagerCallback<String> callback, Handler handler) {
+ if (accountType == null) throw new IllegalArgumentException("accountType is null");
+ if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
+ return new Future2Task<String>(handler, callback) {
+ @Override
+ public void doWork() throws RemoteException {
+ mService.getAuthTokenLabel(mResponse, accountType, authTokenType);
+ }
+
+ @Override
+ public String bundleToResult(Bundle bundle) throws AuthenticatorException {
+ if (!bundle.containsKey(KEY_AUTH_TOKEN_LABEL)) {
+ throw new AuthenticatorException("no result in response");
+ }
+ return bundle.getString(KEY_AUTH_TOKEN_LABEL);
+ }
+ }.start();
+ }
+
+ /**
+ * Finds out whether a particular account has all the specified features. Account features are
+ * authenticator-specific string tokens identifying boolean account properties. For example,
+ * features are used to tell whether Google accounts have a particular service (such as Google
+ * Calendar or Google Talk) enabled. The feature names and their meanings are published
+ * somewhere associated with the authenticator in question.
+ *
+ * <p>
+ * This method may be called from any thread, but the returned {@link AccountManagerFuture} must
+ * not be used on the main thread.
+ *
+ * <p>
+ * If caller target API level is below {@link android.os.Build.VERSION_CODES#O}, it is
+ * required to hold the permission {@link android.Manifest.permission#GET_ACCOUNTS} or have a
+ * signature match with the AbstractAccountAuthenticator that manages the account.
+ *
+ * @param account The {@link Account} to test
+ * @param features An array of the account features to check
+ * @param callback Callback to invoke when the request completes, null for no callback
+ * @param handler {@link Handler} identifying the callback thread, null for the main thread
+ * @return An {@link AccountManagerFuture} which resolves to a Boolean, true if the account
+ * exists and has all of the specified features.
+ */
+ @UserHandleAware(enabledSinceTargetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE,
+ requiresPermissionIfNotCaller = INTERACT_ACROSS_USERS_FULL)
+ public AccountManagerFuture<Boolean> hasFeatures(final Account account,
+ final String[] features,
+ AccountManagerCallback<Boolean> callback, Handler handler) {
+ return hasFeaturesAsUser(account, features, callback, handler, mContext.getUserId());
+ }
+
+ private AccountManagerFuture<Boolean> hasFeaturesAsUser(
+ final Account account, final String[] features,
+ AccountManagerCallback<Boolean> callback, Handler handler, int userId) {
+ if (account == null) throw new IllegalArgumentException("account is null");
+ if (features == null) throw new IllegalArgumentException("features is null");
+ return new Future2Task<Boolean>(handler, callback) {
+ @Override
+ public void doWork() throws RemoteException {
+ mService.hasFeatures(
+ mResponse, account, features, userId, mContext.getOpPackageName());
+ }
+ @Override
+ public Boolean bundleToResult(Bundle bundle) throws AuthenticatorException {
+ if (!bundle.containsKey(KEY_BOOLEAN_RESULT)) {
+ throw new AuthenticatorException("no result in response");
+ }
+ return bundle.getBoolean(KEY_BOOLEAN_RESULT);
+ }
+ }.start();
+ }
+
+ /**
+ * Lists all accounts of a type which have certain features. The account type identifies the
+ * authenticator (see {@link #getAccountsByType}). Account features are authenticator-specific
+ * string tokens identifying boolean account properties (see {@link #hasFeatures}).
+ *
+ * <p>
+ * Unlike {@link #getAccountsByType}, this method calls the authenticator, which may contact the
+ * server or do other work to check account features, so the method returns an
+ * {@link AccountManagerFuture}.
+ *
+ * <p>
+ * This method may be called from any thread, but the returned {@link AccountManagerFuture} must
+ * not be used on the main thread.
+ *
+ * <p>
+ * Caller targeting API level {@link android.os.Build.VERSION_CODES#O} and above, will get list
+ * of accounts made visible to it by user
+ * (see {@link #newChooseAccountIntent(Account, List, String[], String,
+ * String, String[], Bundle)}) or AbstractAccountAuthenticator
+ * using {@link #setAccountVisibility}.
+ * {@link android.Manifest.permission#GET_ACCOUNTS} permission is not used.
+ *
+ * <p>
+ * Caller targeting API level below {@link android.os.Build.VERSION_CODES#O} that have not been
+ * granted the {@link android.Manifest.permission#GET_ACCOUNTS} permission, will only see those
+ * accounts managed by AbstractAccountAuthenticators whose signature matches the client.
+ * <p>
+ * <b>NOTE:</b> If targeting your app to work on API level
+ * {@link android.os.Build.VERSION_CODES#LOLLIPOP_MR1} and before,
+ * {@link android.Manifest.permission#GET_ACCOUNTS} permission is
+ * needed for those platforms, irrespective of uid or signature match. See docs for this
+ * function in API level {@link android.os.Build.VERSION_CODES#LOLLIPOP_MR1}.
+ *
+ *
+ * @param type The type of accounts to return, must not be null
+ * @param features An array of the account features to require, may be null or empty *
+ * @param callback Callback to invoke when the request completes, null for no callback
+ * @param handler {@link Handler} identifying the callback thread, null for the main thread
+ * @return An {@link AccountManagerFuture} which resolves to an array of {@link Account}, one
+ * per account of the specified type which matches the requested features.
+ */
+ public AccountManagerFuture<Account[]> getAccountsByTypeAndFeatures(
+ final String type, final String[] features,
+ AccountManagerCallback<Account[]> callback, Handler handler) {
+ if (type == null) throw new IllegalArgumentException("type is null");
+ return new Future2Task<Account[]>(handler, callback) {
+ @Override
+ public void doWork() throws RemoteException {
+ mService.getAccountsByFeatures(mResponse, type, features,
+ mContext.getOpPackageName());
+ }
+ @Override
+ public Account[] bundleToResult(Bundle bundle) throws AuthenticatorException {
+ if (!bundle.containsKey(KEY_ACCOUNTS)) {
+ throw new AuthenticatorException("no result in response");
+ }
+ final Parcelable[] parcelables = bundle.getParcelableArray(KEY_ACCOUNTS);
+ Account[] descs = new Account[parcelables.length];
+ for (int i = 0; i < parcelables.length; i++) {
+ descs[i] = (Account) parcelables[i];
+ }
+ return descs;
+ }
+ }.start();
+ }
+
+ /**
+ * Adds an account directly to the AccountManager. Normally used by sign-up
+ * wizards associated with authenticators, not directly by applications.
+ * <p>Calling this method does not update the last authenticated timestamp,
+ * referred by {@link #KEY_LAST_AUTHENTICATED_TIME}. To update it, call
+ * {@link #notifyAccountAuthenticated(Account)} after getting success.
+ * However, if this method is called when it is triggered by addAccount() or
+ * addAccountAsUser() or similar functions, then there is no need to update
+ * timestamp manually as it is updated automatically by framework on
+ * successful completion of the mentioned functions.
+ * <p>It is safe to call this method from the main thread.
+ * <p>This method requires the caller to have a signature match with the
+ * authenticator that owns the specified account.
+ *
+ * <p><b>NOTE:</b> If targeting your app to work on API level 22 and before,
+ * AUTHENTICATE_ACCOUNTS permission is needed for those platforms. See docs
+ * for this function in API level 22.
+ *
+ * @param account The {@link Account} to add
+ * @param password The password to associate with the account, null for none
+ * @param userdata String values to use for the account's userdata, null for
+ * none
+ * @return True if the account was successfully added, false if the account
+ * already exists, the account is null, the user is locked, or another error occurs.
+ */
+ public boolean addAccountExplicitly(Account account, String password, Bundle userdata) {
+ if (account == null) throw new IllegalArgumentException("account is null");
+ try {
+ return mService.addAccountExplicitly(
+ account, password, userdata, mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Adds an account directly to the AccountManager. Additionally it specifies Account visibility
+ * for given list of packages.
+ * <p>
+ * Normally used by sign-up wizards associated with authenticators, not directly by
+ * applications.
+ * <p>
+ * Calling this method does not update the last authenticated timestamp, referred by
+ * {@link #KEY_LAST_AUTHENTICATED_TIME}. To update it, call
+ * {@link #notifyAccountAuthenticated(Account)} after getting success.
+ * <p>
+ * It is safe to call this method from the main thread.
+ * <p>
+ * This method requires the caller to have a signature match with the authenticator that owns
+ * the specified account.
+ *
+ * @param account The {@link Account} to add
+ * @param password The password to associate with the account, null for none
+ * @param extras String values to use for the account's userdata, null for none
+ * @param visibility Map from packageName to visibility values which will be set before account
+ * is added. See {@link #getAccountVisibility} for possible values. Declaring
+ * <a href="/training/basics/intents/package-visibility">package visibility</a> needs for
+ * package names in the map is needed, if the caller is targeting API level 34 and above.
+ *
+ * @return True if the account was successfully added, false if the account already exists, the
+ * account is null, or another error occurs.
+ */
+ public boolean addAccountExplicitly(Account account, String password, Bundle extras,
+ Map<String, Integer> visibility) {
+ if (account == null)
+ throw new IllegalArgumentException("account is null");
+ try {
+ return mService.addAccountExplicitlyWithVisibility(account, password, extras,
+ visibility, mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns package names and visibility which were explicitly set for given account.
+ * <p>
+ * This method requires the caller to have a signature match with the authenticator that owns
+ * the specified account.
+ *
+ * @param account The account for which visibility data should be returned
+ *
+ * @return Map from package names to visibility for given account
+ */
+ public Map<String, Integer> getPackagesAndVisibilityForAccount(Account account) {
+ try {
+ if (account == null)
+ throw new IllegalArgumentException("account is null");
+ @SuppressWarnings("unchecked")
+ Map<String, Integer> result = (Map<String, Integer>) mService
+ .getPackagesAndVisibilityForAccount(account);
+ return result;
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Gets all accounts of given type and their visibility for specific package. This method
+ * requires the caller to have a signature match with the authenticator that manages
+ * accountType. It is a helper method which combines calls to {@link #getAccountsByType} by
+ * authenticator and {@link #getAccountVisibility} for every returned account.
+ *
+ * <p>
+ *
+ * @param packageName Package name
+ * @param accountType {@link Account} type
+ *
+ * @return Map with visibility for all accounts of given type
+ * See {@link #getAccountVisibility} for possible values
+ */
+ public Map<Account, Integer> getAccountsAndVisibilityForPackage(String packageName,
+ String accountType) {
+ try {
+ @SuppressWarnings("unchecked")
+ Map<Account, Integer> result = (Map<Account, Integer>) mService
+ .getAccountsAndVisibilityForPackage(packageName, accountType);
+ return result;
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Set visibility value of given account to certain package.
+ * Package name must match installed application, or be equal to
+ * {@link #PACKAGE_NAME_KEY_LEGACY_VISIBLE} or {@link #PACKAGE_NAME_KEY_LEGACY_NOT_VISIBLE}.
+ * <p>
+ * Possible visibility values:
+ * <ul>
+ * <li>{@link #VISIBILITY_UNDEFINED}</li>
+ * <li>{@link #VISIBILITY_VISIBLE}</li>
+ * <li>{@link #VISIBILITY_USER_MANAGED_VISIBLE}</li>
+ * <li>{@link #VISIBILITY_NOT_VISIBLE}
+ * <li>{@link #VISIBILITY_USER_MANAGED_NOT_VISIBLE}</li>
+ * </ul>
+ * <p>
+ * This method requires the caller to have a signature match with the authenticator that owns
+ * the specified account.
+ *
+ * @param account {@link Account} to update visibility
+ * @param packageName Package name of the application to modify account visibility. Declaring
+ * <a href="/training/basics/intents/package-visibility">package visibility</a> needs
+ * for it is needed, if the caller is targeting API level 34 and above.
+ * @param visibility New visibility value
+ *
+ * @return True, if visibility value was successfully updated.
+ */
+ public boolean setAccountVisibility(Account account, String packageName,
+ @AccountVisibility int visibility) {
+ if (account == null)
+ throw new IllegalArgumentException("account is null");
+ try {
+ return mService.setAccountVisibility(account, packageName, visibility);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Get visibility of certain account for given application. Possible returned values are:
+ * <ul>
+ * <li>{@link #VISIBILITY_VISIBLE}</li>
+ * <li>{@link #VISIBILITY_USER_MANAGED_VISIBLE}</li>
+ * <li>{@link #VISIBILITY_NOT_VISIBLE}
+ * <li>{@link #VISIBILITY_USER_MANAGED_NOT_VISIBLE}</li>
+ * </ul>
+ *
+ * <p>
+ * This method requires the caller to have a signature match with the authenticator that owns
+ * the specified account.
+ *
+ * @param account {@link Account} to get visibility
+ * @param packageName Package name of the application to get account visibility
+ *
+ * @return int Visibility of given account. For the caller targeting API level 34 and above,
+ * {@link #VISIBILITY_NOT_VISIBLE} is returned if the given package is filtered by the rules of
+ * <a href="/training/basics/intents/package-visibility">package visibility</a>.
+ */
+ public @AccountVisibility int getAccountVisibility(Account account, String packageName) {
+ if (account == null)
+ throw new IllegalArgumentException("account is null");
+ try {
+ return mService.getAccountVisibility(account, packageName);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Notifies the system that the account has just been authenticated. This
+ * information may be used by other applications to verify the account. This
+ * should be called only when the user has entered correct credentials for
+ * the account.
+ * <p>
+ * It is not safe to call this method from the main thread. As such, call it
+ * from another thread.
+ * <p>This method requires the caller to have a signature match with the
+ * authenticator that owns the specified account.
+ *
+ * @param account The {@link Account} to be updated.
+ * @return boolean {@code true} if the authentication of the account has been successfully
+ * acknowledged. Otherwise {@code false}.
+ */
+ public boolean notifyAccountAuthenticated(Account account) {
+ if (account == null)
+ throw new IllegalArgumentException("account is null");
+ try {
+ return mService.accountAuthenticated(account);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Rename the specified {@link Account}. This is equivalent to removing
+ * the existing account and adding a new renamed account with the old
+ * account's user data.
+ *
+ * <p>It is safe to call this method from the main thread.
+ *
+ * <p>This method requires the caller to have a signature match with the
+ * authenticator that manages the specified account.
+ *
+ * <p><b>NOTE:</b> If targeting your app to work on API level 22 and before,
+ * AUTHENTICATE_ACCOUNTS permission and same UID as account's authenticator
+ * is needed for those platforms. See docs for this function in API level 22.
+ *
+ * @param account The {@link Account} to rename
+ * @param newName String name to be associated with the account.
+ * @param callback Callback to invoke when the request completes, null for
+ * no callback
+ * @param handler {@link Handler} identifying the callback thread, null for
+ * the main thread
+ * @return An {@link AccountManagerFuture} which resolves to the Account
+ * after the name change. If successful the account's name will be the
+ * specified new name.
+ */
+ public AccountManagerFuture<Account> renameAccount(
+ final Account account,
+ @Size(min = 1) final String newName,
+ AccountManagerCallback<Account> callback,
+ Handler handler) {
+ if (account == null) throw new IllegalArgumentException("account is null.");
+ if (TextUtils.isEmpty(newName)) {
+ throw new IllegalArgumentException("newName is empty or null.");
+ }
+ return new Future2Task<Account>(handler, callback) {
+ @Override
+ public void doWork() throws RemoteException {
+ mService.renameAccount(mResponse, account, newName);
+ }
+ @Override
+ public Account bundleToResult(Bundle bundle) throws AuthenticatorException {
+ String name = bundle.getString(KEY_ACCOUNT_NAME);
+ String type = bundle.getString(KEY_ACCOUNT_TYPE);
+ String accessId = bundle.getString(KEY_ACCOUNT_ACCESS_ID);
+ return new Account(name, type, accessId);
+ }
+ }.start();
+ }
+
+ /**
+ * Gets the previous name associated with the account or {@code null}, if
+ * none. This is intended so that clients of
+ * {@link OnAccountsUpdateListener} can determine if an
+ * authenticator has renamed an account.
+ *
+ * <p>It is safe to call this method from the main thread.
+ *
+ * @param account The account to query for a previous name.
+ * @return The account's previous name, null if the account has never been
+ * renamed.
+ */
+ public String getPreviousName(final Account account) {
+ if (account == null) throw new IllegalArgumentException("account is null");
+ try {
+ return mService.getPreviousName(account);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Removes an account from the AccountManager. Does nothing if the account
+ * does not exist. Does not delete the account from the server.
+ * The authenticator may have its own policies preventing account
+ * deletion, in which case the account will not be deleted.
+ *
+ * <p>This method requires the caller to have a signature match with the
+ * authenticator that manages the specified account.
+ *
+ * <p><b>NOTE:</b> If targeting your app to work on API level 22 and before,
+ * MANAGE_ACCOUNTS permission is needed for those platforms. See docs for
+ * this function in API level 22.
+ *
+ * @param account The {@link Account} to remove
+ * @param callback Callback to invoke when the request completes,
+ * null for no callback
+ * @param handler {@link Handler} identifying the callback thread,
+ * null for the main thread
+ * @return An {@link AccountManagerFuture} which resolves to a Boolean,
+ * true if the account has been successfully removed
+ * @deprecated use
+ * {@link #removeAccount(Account, Activity, AccountManagerCallback, Handler)}
+ * instead
+ */
+ @UserHandleAware
+ @Deprecated
+ public AccountManagerFuture<Boolean> removeAccount(final Account account,
+ AccountManagerCallback<Boolean> callback, Handler handler) {
+ return removeAccountAsUser(account, callback, handler, mContext.getUser());
+ }
+
+ /**
+ * Removes an account from the AccountManager. Does nothing if the account
+ * does not exist. Does not delete the account from the server.
+ * The authenticator may have its own policies preventing account
+ * deletion, in which case the account will not be deleted.
+ *
+ * <p>This method may be called from any thread, but the returned
+ * {@link AccountManagerFuture} must not be used on the main thread.
+ *
+ * <p>This method requires the caller to have a signature match with the
+ * authenticator that manages the specified account.
+ *
+ * <p><b>NOTE:</b> If targeting your app to work on API level 22 and before,
+ * MANAGE_ACCOUNTS permission is needed for those platforms. See docs for
+ * this function in API level 22.
+ *
+ * @param account The {@link Account} to remove
+ * @param activity The {@link Activity} context to use for launching a new
+ * authenticator-defined sub-Activity to prompt the user to delete an
+ * account; used only to call startActivity(); if null, the prompt
+ * will not be launched directly, but the {@link Intent} may be
+ * returned to the caller instead
+ * @param callback Callback to invoke when the request completes,
+ * null for no callback
+ * @param handler {@link Handler} identifying the callback thread,
+ * null for the main thread
+ * @return An {@link AccountManagerFuture} which resolves to a Bundle with
+ * {@link #KEY_BOOLEAN_RESULT} if activity was specified and an account
+ * was removed or if active. If no activity was specified, the returned
+ * Bundle contains only {@link #KEY_INTENT} with the {@link Intent}
+ * needed to launch the actual account removal process, if authenticator
+ * needs the activity launch. If an error occurred,
+ * {@link AccountManagerFuture#getResult()} throws:
+ * <ul>
+ * <li> {@link AuthenticatorException} if no authenticator was registered for
+ * this account type or the authenticator failed to respond
+ * <li> {@link OperationCanceledException} if the operation was canceled for
+ * any reason, including the user canceling the creation process or
+ * adding accounts (of this type) has been disabled by policy
+ * </ul>
+ */
+ @UserHandleAware
+ public AccountManagerFuture<Bundle> removeAccount(final Account account,
+ final Activity activity, AccountManagerCallback<Bundle> callback, Handler handler) {
+ return removeAccountAsUser(account, activity, callback, handler, mContext.getUser());
+ }
+
+ /**
+ * @see #removeAccount(Account, AccountManagerCallback, Handler)
+ * @hide
+ * @deprecated use
+ * {@link #removeAccountAsUser(Account, Activity, AccountManagerCallback, Handler)}
+ * instead
+ */
+ @Deprecated
+ public AccountManagerFuture<Boolean> removeAccountAsUser(final Account account,
+ AccountManagerCallback<Boolean> callback, Handler handler,
+ final UserHandle userHandle) {
+ if (account == null) throw new IllegalArgumentException("account is null");
+ if (userHandle == null) throw new IllegalArgumentException("userHandle is null");
+ return new Future2Task<Boolean>(handler, callback) {
+ @Override
+ public void doWork() throws RemoteException {
+ mService.removeAccountAsUser(mResponse, account, false, userHandle.getIdentifier());
+ }
+ @Override
+ public Boolean bundleToResult(Bundle bundle) throws AuthenticatorException {
+ if (!bundle.containsKey(KEY_BOOLEAN_RESULT)) {
+ throw new AuthenticatorException("no result in response");
+ }
+ return bundle.getBoolean(KEY_BOOLEAN_RESULT);
+ }
+ }.start();
+ }
+
+ /**
+ * @see #removeAccount(Account, Activity, AccountManagerCallback, Handler)
+ * @hide
+ */
+ public AccountManagerFuture<Bundle> removeAccountAsUser(final Account account,
+ final Activity activity, AccountManagerCallback<Bundle> callback, Handler handler,
+ final UserHandle userHandle) {
+ if (account == null)
+ throw new IllegalArgumentException("account is null");
+ if (userHandle == null)
+ throw new IllegalArgumentException("userHandle is null");
+ return new AmsTask(activity, handler, callback) {
+ @Override
+ public void doWork() throws RemoteException {
+ mService.removeAccountAsUser(mResponse, account, activity != null,
+ userHandle.getIdentifier());
+ }
+ }.start();
+ }
+
+ /**
+ * Removes an account directly. Normally used by authenticators, not
+ * directly by applications. Does not delete the account from the server.
+ * The authenticator may have its own policies preventing account deletion,
+ * in which case the account will not be deleted.
+ * <p>
+ * It is safe to call this method from the main thread.
+ * <p>This method requires the caller to have a signature match with the
+ * authenticator that manages the specified account.
+ *
+ * <p><b>NOTE:</b> If targeting your app to work on API level 22 and before,
+ * AUTHENTICATE_ACCOUNTS permission and same UID as account's authenticator
+ * is needed for those platforms. See docs for this function in API level 22.
+ *
+ * @param account The {@link Account} to delete.
+ * @return True if the account was successfully deleted, false if the
+ * account did not exist, the account is null, or another error
+ * occurs.
+ */
+ public boolean removeAccountExplicitly(Account account) {
+ if (account == null) throw new IllegalArgumentException("account is null");
+ try {
+ return mService.removeAccountExplicitly(account);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Removes an auth token from the AccountManager's cache. Does nothing if
+ * the auth token is not currently in the cache. Applications must call this
+ * method when the auth token is found to have expired or otherwise become
+ * invalid for authenticating requests. The AccountManager does not validate
+ * or expire cached auth tokens otherwise.
+ *
+ * <p>It is safe to call this method from the main thread.
+ *
+ * <p><b>NOTE:</b> If targeting your app to work on API level 22 and before,
+ * MANAGE_ACCOUNTS or USE_CREDENTIALS permission is needed for those
+ * platforms. See docs for this function in API level 22.
+ *
+ * @param accountType The account type of the auth token to invalidate, must not be null
+ * @param authToken The auth token to invalidate, may be null
+ */
+ public void invalidateAuthToken(final String accountType, final String authToken) {
+ if (accountType == null) throw new IllegalArgumentException("accountType is null");
+ try {
+ if (authToken != null) {
+ mService.invalidateAuthToken(accountType, authToken);
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Gets an auth token from the AccountManager's cache. If no auth
+ * token is cached for this account, null will be returned -- a new
+ * auth token will not be generated, and the server will not be contacted.
+ * Intended for use by the authenticator, not directly by applications.
+ *
+ * <p>It is safe to call this method from the main thread.
+ *
+ * <p>This method requires the caller to have a signature match with the
+ * authenticator that manages the specified account.
+ *
+ * <p><b>NOTE:</b> If targeting your app to work on API level 22 and before,
+ * AUTHENTICATE_ACCOUNTS permission and same UID as account's authenticator
+ * is needed for those platforms. See docs for this function in API level 22.
+ *
+ * @param account The account for which an auth token is to be fetched. Cannot be {@code null}.
+ * @param authTokenType The type of auth token to fetch. Cannot be {@code null}.
+ * @return The cached auth token for this account and type, or null if
+ * no auth token is cached, the account does not exist, or the user is locked
+ * @see #getAuthToken
+ */
+ public String peekAuthToken(final Account account, final String authTokenType) {
+ if (account == null) throw new IllegalArgumentException("account is null");
+ if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
+ try {
+ return mService.peekAuthToken(account, authTokenType);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Sets or forgets a saved password. This modifies the local copy of the
+ * password used to automatically authenticate the user; it does not change
+ * the user's account password on the server. Intended for use by the
+ * authenticator, not directly by applications.
+ * <p>Calling this method does not update the last authenticated timestamp,
+ * referred by {@link #KEY_LAST_AUTHENTICATED_TIME}. To update it, call
+ * {@link #notifyAccountAuthenticated(Account)} after getting success.
+ * <p>It is safe to call this method from the main thread.
+ * <p>This method requires the caller to have a signature match with the
+ * authenticator that manages the specified account.
+ *
+ * <p><b>NOTE:</b> If targeting your app to work on API level 22 and before,
+ * AUTHENTICATE_ACCOUNTS permission and same UID as account's authenticator
+ * is needed for those platforms. See docs for this function in API level 22.
+ *
+ * @param account The account whose password is to be set. Cannot be
+ * {@code null}.
+ * @param password The password to set, null to clear the password
+ */
+ public void setPassword(final Account account, final String password) {
+ if (account == null) throw new IllegalArgumentException("account is null");
+ try {
+ mService.setPassword(account, password);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Forgets a saved password. This erases the local copy of the password;
+ * it does not change the user's account password on the server.
+ * Has the same effect as setPassword(account, null) but requires fewer
+ * permissions, and may be used by applications or management interfaces
+ * to "sign out" from an account.
+ *
+ * <p>This method only successfully clear the account's password when the
+ * caller has the same signature as the authenticator that owns the
+ * specified account. Otherwise, this method will silently fail.
+ *
+ * <p>It is safe to call this method from the main thread.
+ *
+ * <p><b>NOTE:</b> If targeting your app to work on API level 22 and before,
+ * MANAGE_ACCOUNTS permission is needed for those platforms. See docs for
+ * this function in API level 22.
+ *
+ * @param account The account whose password to clear
+ */
+ public void clearPassword(final Account account) {
+ if (account == null) throw new IllegalArgumentException("account is null");
+ try {
+ mService.clearPassword(account);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Sets one userdata key for an account. Intended by use for the
+ * authenticator to stash state for itself, not directly by applications.
+ * The meaning of the keys and values is up to the authenticator.
+ *
+ * <p>It is safe to call this method from the main thread.
+ *
+ * <p>This method requires the caller to have a signature match with the
+ * authenticator that manages the specified account.
+ *
+ * <p><b>NOTE:</b> If targeting your app to work on API level 22 and before,
+ * AUTHENTICATE_ACCOUNTS permission and same UID as account's authenticator
+ * is needed for those platforms. See docs for this function in API level 22.
+ *
+ * @param account Account whose user data is to be set. Must not be {@code null}.
+ * @param key String user data key to set. Must not be null
+ * @param value String value to set, {@code null} to clear this user data key
+ */
+ public void setUserData(final Account account, final String key, final String value) {
+ if (account == null) throw new IllegalArgumentException("account is null");
+ if (key == null) throw new IllegalArgumentException("key is null");
+ try {
+ mService.setUserData(account, key, value);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Adds an auth token to the AccountManager cache for an account.
+ * If the account does not exist then this call has no effect.
+ * Replaces any previous auth token for this account and auth token type.
+ * Intended for use by the authenticator, not directly by applications.
+ *
+ * <p>It is safe to call this method from the main thread.
+ *
+ * <p>This method requires the caller to have a signature match with the
+ * authenticator that manages the specified account.
+ *
+ * <p><b>NOTE:</b> If targeting your app to work on API level 22 and before,
+ * AUTHENTICATE_ACCOUNTS permission and same UID as account's authenticator
+ * is needed for those platforms. See docs for this function in API level 22.
+ *
+ * @param account The account to set an auth token for
+ * @param authTokenType The type of the auth token, see {#getAuthToken}
+ * @param authToken The auth token to add to the cache
+ */
+ public void setAuthToken(Account account, final String authTokenType, final String authToken) {
+ if (account == null) throw new IllegalArgumentException("account is null");
+ if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
+ try {
+ mService.setAuthToken(account, authTokenType, authToken);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * This convenience helper synchronously gets an auth token with
+ * {@link #getAuthToken(Account, String, boolean, AccountManagerCallback, Handler)}.
+ *
+ * <p>This method may block while a network request completes, and must
+ * never be made from the main thread.
+ *
+ * <p><b>NOTE:</b> If targeting your app to work on API level 22 and before,
+ * USE_CREDENTIALS permission is needed for those platforms. See docs for
+ * this function in API level 22.
+ *
+ * @param account The account to fetch an auth token for
+ * @param authTokenType The auth token type, see {@link #getAuthToken getAuthToken()}
+ * @param notifyAuthFailure If true, display a notification and return null
+ * if authentication fails; if false, prompt and wait for the user to
+ * re-enter correct credentials before returning
+ * @return An auth token of the specified type for this account, or null
+ * if authentication fails or none can be fetched.
+ * @throws AuthenticatorException if the authenticator failed to respond
+ * @throws OperationCanceledException if the request was canceled for any
+ * reason, including the user canceling a credential request
+ * @throws java.io.IOException if the authenticator experienced an I/O problem
+ * creating a new auth token, usually because of network trouble
+ */
+ public String blockingGetAuthToken(Account account, String authTokenType,
+ boolean notifyAuthFailure)
+ throws OperationCanceledException, IOException, AuthenticatorException {
+ if (account == null) throw new IllegalArgumentException("account is null");
+ if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
+ Bundle bundle = getAuthToken(account, authTokenType, notifyAuthFailure, null /* callback */,
+ null /* handler */).getResult();
+ if (bundle == null) {
+ // This should never happen, but it does, occasionally. If it does return null to
+ // signify that we were not able to get the authtoken.
+ // TODO: remove this when the bug is found that sometimes causes a null bundle to be
+ // returned
+ Log.e(TAG, "blockingGetAuthToken: null was returned from getResult() for "
+ + account + ", authTokenType " + authTokenType);
+ return null;
+ }
+ return bundle.getString(KEY_AUTHTOKEN);
+ }
+
+ /**
+ * Gets an auth token of the specified type for a particular account,
+ * prompting the user for credentials if necessary. This method is
+ * intended for applications running in the foreground where it makes
+ * sense to ask the user directly for a password.
+ *
+ * <p>If a previously generated auth token is cached for this account and
+ * type, then it is returned. Otherwise, if a saved password is
+ * available, it is sent to the server to generate a new auth token.
+ * Otherwise, the user is prompted to enter a password.
+ *
+ * <p>Some authenticators have auth token <em>types</em>, whose value
+ * is authenticator-dependent. Some services use different token types to
+ * access different functionality -- for example, Google uses different auth
+ * tokens to access Gmail and Google Calendar for the same account.
+ *
+ * <p><b>NOTE:</b> If targeting your app to work on API level 22 and before,
+ * USE_CREDENTIALS permission is needed for those platforms. See docs for
+ * this function in API level 22.
+ *
+ * <p>This method may be called from any thread, but the returned
+ * {@link AccountManagerFuture} must not be used on the main thread.
+ *
+ * @param account The account to fetch an auth token for
+ * @param authTokenType The auth token type, an authenticator-dependent
+ * string token, must not be null
+ * @param options Authenticator-specific options for the request,
+ * may be null or empty
+ * @param activity The {@link Activity} context to use for launching a new
+ * authenticator-defined sub-Activity to prompt the user for a password
+ * if necessary; used only to call startActivity(); must not be null.
+ * @param callback Callback to invoke when the request completes,
+ * null for no callback
+ * @param handler {@link Handler} identifying the callback thread,
+ * null for the main thread
+ * @return An {@link AccountManagerFuture} which resolves to a Bundle with
+ * at least the following fields:
+ * <ul>
+ * <li> {@link #KEY_ACCOUNT_NAME} - the name of the account you supplied
+ * <li> {@link #KEY_ACCOUNT_TYPE} - the type of the account
+ * <li> {@link #KEY_AUTHTOKEN} - the auth token you wanted
+ * </ul>
+ *
+ * (Other authenticator-specific values may be returned.) If an auth token
+ * could not be fetched, {@link AccountManagerFuture#getResult()} throws:
+ * <ul>
+ * <li> {@link AuthenticatorException} if the authenticator failed to respond
+ * <li> {@link OperationCanceledException} if the operation is canceled for
+ * any reason, incluidng the user canceling a credential request
+ * <li> {@link IOException} if the authenticator experienced an I/O problem
+ * creating a new auth token, usually because of network trouble
+ * </ul>
+ * If the account is no longer present on the device, the return value is
+ * authenticator-dependent. The caller should verify the validity of the
+ * account before requesting an auth token.
+ */
+ public AccountManagerFuture<Bundle> getAuthToken(
+ final Account account, final String authTokenType, final Bundle options,
+ final Activity activity, AccountManagerCallback<Bundle> callback, Handler handler) {
+ if (account == null) throw new IllegalArgumentException("account is null");
+ if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
+ final Bundle optionsIn = new Bundle();
+ if (options != null) {
+ optionsIn.putAll(options);
+ }
+ optionsIn.putString(KEY_ANDROID_PACKAGE_NAME, mContext.getPackageName());
+ return new AmsTask(activity, handler, callback) {
+ @Override
+ public void doWork() throws RemoteException {
+ mService.getAuthToken(mResponse, account, authTokenType,
+ false /* notifyOnAuthFailure */, true /* expectActivityLaunch */,
+ optionsIn);
+ }
+ }.start();
+ }
+
+ /**
+ * Gets an auth token of the specified type for a particular account,
+ * optionally raising a notification if the user must enter credentials.
+ * This method is intended for background tasks and services where the
+ * user should not be immediately interrupted with a password prompt.
+ *
+ * <p>If a previously generated auth token is cached for this account and
+ * type, then it is returned. Otherwise, if a saved password is
+ * available, it is sent to the server to generate a new auth token.
+ * Otherwise, an {@link Intent} is returned which, when started, will
+ * prompt the user for a password. If the notifyAuthFailure parameter is
+ * set, a status bar notification is also created with the same Intent,
+ * alerting the user that they need to enter a password at some point.
+ *
+ * <p>In that case, you may need to wait until the user responds, which
+ * could take hours or days or forever. When the user does respond and
+ * supply a new password, the account manager will broadcast the
+ * {@link #LOGIN_ACCOUNTS_CHANGED_ACTION} Intent and
+ * notify {@link OnAccountsUpdateListener} which applications can
+ * use to try again.
+ *
+ * <p>If notifyAuthFailure is not set, it is the application's
+ * responsibility to launch the returned Intent at some point.
+ * Either way, the result from this call will not wait for user action.
+ *
+ * <p>Some authenticators have auth token <em>types</em>, whose value
+ * is authenticator-dependent. Some services use different token types to
+ * access different functionality -- for example, Google uses different auth
+ * tokens to access Gmail and Google Calendar for the same account.
+ *
+ * <p>This method may be called from any thread, but the returned
+ * {@link AccountManagerFuture} must not be used on the main thread.
+ *
+ * @param account The account to fetch an auth token for
+ * @param authTokenType The auth token type, an authenticator-dependent
+ * string token, must not be null
+ * @param notifyAuthFailure True to add a notification to prompt the
+ * user for a password if necessary, false to leave that to the caller
+ * @param callback Callback to invoke when the request completes,
+ * null for no callback
+ * @param handler {@link Handler} identifying the callback thread,
+ * null for the main thread
+ * @return An {@link AccountManagerFuture} which resolves to a Bundle with
+ * at least the following fields on success:
+ * <ul>
+ * <li> {@link #KEY_ACCOUNT_NAME} - the name of the account you supplied
+ * <li> {@link #KEY_ACCOUNT_TYPE} - the type of the account
+ * <li> {@link #KEY_AUTHTOKEN} - the auth token you wanted
+ * </ul>
+ *
+ * (Other authenticator-specific values may be returned.) If the user
+ * must enter credentials, the returned Bundle contains only
+ * {@link #KEY_INTENT} with the {@link Intent} needed to launch a prompt.
+ *
+ * If an error occurred, {@link AccountManagerFuture#getResult()} throws:
+ * <ul>
+ * <li> {@link AuthenticatorException} if the authenticator failed to respond
+ * <li> {@link OperationCanceledException} if the operation is canceled for
+ * any reason, incluidng the user canceling a credential request
+ * <li> {@link IOException} if the authenticator experienced an I/O problem
+ * creating a new auth token, usually because of network trouble
+ * </ul>
+ * If the account is no longer present on the device, the return value is
+ * authenticator-dependent. The caller should verify the validity of the
+ * account before requesting an auth token.
+ * @deprecated use {@link #getAuthToken(Account, String, android.os.Bundle,
+ * boolean, AccountManagerCallback, android.os.Handler)} instead
+ */
+ @Deprecated
+ public AccountManagerFuture<Bundle> getAuthToken(
+ final Account account, final String authTokenType,
+ final boolean notifyAuthFailure,
+ AccountManagerCallback<Bundle> callback, Handler handler) {
+ return getAuthToken(account, authTokenType, null, notifyAuthFailure, callback,
+ handler);
+ }
+
+ /**
+ * Gets an auth token of the specified type for a particular account,
+ * optionally raising a notification if the user must enter credentials.
+ * This method is intended for background tasks and services where the
+ * user should not be immediately interrupted with a password prompt.
+ *
+ * <p>If a previously generated auth token is cached for this account and
+ * type, then it is returned. Otherwise, if a saved password is
+ * available, it is sent to the server to generate a new auth token.
+ * Otherwise, an {@link Intent} is returned which, when started, will
+ * prompt the user for a password. If the notifyAuthFailure parameter is
+ * set, a status bar notification is also created with the same Intent,
+ * alerting the user that they need to enter a password at some point.
+ *
+ * <p>In that case, you may need to wait until the user responds, which
+ * could take hours or days or forever. When the user does respond and
+ * supply a new password, the account manager will broadcast the
+ * {@link #LOGIN_ACCOUNTS_CHANGED_ACTION} Intent and
+ * notify {@link OnAccountsUpdateListener} which applications can
+ * use to try again.
+ *
+ * <p>If notifyAuthFailure is not set, it is the application's
+ * responsibility to launch the returned Intent at some point.
+ * Either way, the result from this call will not wait for user action.
+ *
+ * <p>Some authenticators have auth token <em>types</em>, whose value
+ * is authenticator-dependent. Some services use different token types to
+ * access different functionality -- for example, Google uses different auth
+ * tokens to access Gmail and Google Calendar for the same account.
+ *
+ * <p>This method may be called from any thread, but the returned
+ * {@link AccountManagerFuture} must not be used on the main thread.
+ *
+ * <p><b>NOTE:</b> If targeting your app to work on API level 22 and before,
+ * USE_CREDENTIALS permission is needed for those platforms. See docs for
+ * this function in API level 22.
+ *
+ * @param account The account to fetch an auth token for
+ * @param authTokenType The auth token type, an authenticator-dependent
+ * string token, must not be null
+ * @param options Authenticator-specific options for the request,
+ * may be null or empty
+ * @param notifyAuthFailure True to add a notification to prompt the
+ * user for a password if necessary, false to leave that to the caller
+ * @param callback Callback to invoke when the request completes,
+ * null for no callback
+ * @param handler {@link Handler} identifying the callback thread,
+ * null for the main thread
+ * @return An {@link AccountManagerFuture} which resolves to a Bundle with
+ * at least the following fields on success:
+ * <ul>
+ * <li> {@link #KEY_ACCOUNT_NAME} - the name of the account you supplied
+ * <li> {@link #KEY_ACCOUNT_TYPE} - the type of the account
+ * <li> {@link #KEY_AUTHTOKEN} - the auth token you wanted
+ * </ul>
+ *
+ * (Other authenticator-specific values may be returned.) If the user
+ * must enter credentials, the returned Bundle contains only
+ * {@link #KEY_INTENT} with the {@link Intent} needed to launch a prompt.
+ *
+ * If an error occurred, {@link AccountManagerFuture#getResult()} throws:
+ * <ul>
+ * <li> {@link AuthenticatorException} if the authenticator failed to respond
+ * <li> {@link OperationCanceledException} if the operation is canceled for
+ * any reason, incluidng the user canceling a credential request
+ * <li> {@link IOException} if the authenticator experienced an I/O problem
+ * creating a new auth token, usually because of network trouble
+ * </ul>
+ * If the account is no longer present on the device, the return value is
+ * authenticator-dependent. The caller should verify the validity of the
+ * account before requesting an auth token.
+ */
+ public AccountManagerFuture<Bundle> getAuthToken(
+ final Account account, final String authTokenType, final Bundle options,
+ final boolean notifyAuthFailure,
+ AccountManagerCallback<Bundle> callback, Handler handler) {
+
+ if (account == null) throw new IllegalArgumentException("account is null");
+ if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
+ final Bundle optionsIn = new Bundle();
+ if (options != null) {
+ optionsIn.putAll(options);
+ }
+ optionsIn.putString(KEY_ANDROID_PACKAGE_NAME, mContext.getPackageName());
+ return new AmsTask(null, handler, callback) {
+ @Override
+ public void doWork() throws RemoteException {
+ mService.getAuthToken(mResponse, account, authTokenType,
+ notifyAuthFailure, false /* expectActivityLaunch */, optionsIn);
+ }
+ }.start();
+ }
+
+ /**
+ * Asks the user to add an account of a specified type. The authenticator
+ * for this account type processes this request with the appropriate user
+ * interface. If the user does elect to create a new account, the account
+ * name is returned.
+ *
+ * <p>This method may be called from any thread, but the returned
+ * {@link AccountManagerFuture} must not be used on the main thread.
+ *
+ * <p><b>NOTE:</b> If targeting your app to work on API level 22 and before,
+ * MANAGE_ACCOUNTS permission is needed for those platforms. See docs for
+ * this function in API level 22.
+ *
+ * @param accountType The type of account to add; must not be null
+ * @param authTokenType The type of auth token (see {@link #getAuthToken})
+ * this account will need to be able to generate, null for none
+ * @param requiredFeatures The features (see {@link #hasFeatures}) this
+ * account must have, null for none
+ * @param addAccountOptions Authenticator-specific options for the request,
+ * may be null or empty
+ * @param activity The {@link Activity} context to use for launching a new
+ * authenticator-defined sub-Activity to prompt the user to create an
+ * account; used only to call startActivity(); if null, the prompt
+ * will not be launched directly, but the necessary {@link Intent}
+ * will be returned to the caller instead
+ * @param callback Callback to invoke when the request completes,
+ * null for no callback
+ * @param handler {@link Handler} identifying the callback thread,
+ * null for the main thread
+ * @return An {@link AccountManagerFuture} which resolves to a Bundle with
+ * these fields if activity was specified and an account was created:
+ * <ul>
+ * <li> {@link #KEY_ACCOUNT_NAME} - the name of the account created
+ * <li> {@link #KEY_ACCOUNT_TYPE} - the type of the account
+ * </ul>
+ *
+ * If no activity was specified, the returned Bundle contains only
+ * {@link #KEY_INTENT} with the {@link Intent} needed to launch the
+ * actual account creation process. If an error occurred,
+ * {@link AccountManagerFuture#getResult()} throws:
+ * <ul>
+ * <li> {@link AuthenticatorException} if no authenticator was registered for
+ * this account type or the authenticator failed to respond
+ * <li> {@link OperationCanceledException} if the operation was canceled for
+ * any reason, including the user canceling the creation process or adding accounts
+ * (of this type) has been disabled by policy
+ * <li> {@link IOException} if the authenticator experienced an I/O problem
+ * creating a new account, usually because of network trouble
+ * </ul>
+ */
+ @UserHandleAware
+ public AccountManagerFuture<Bundle> addAccount(final String accountType,
+ final String authTokenType, final String[] requiredFeatures,
+ final Bundle addAccountOptions,
+ final Activity activity, AccountManagerCallback<Bundle> callback, Handler handler) {
+ if (Process.myUserHandle().equals(mContext.getUser())) {
+ if (accountType == null) throw new IllegalArgumentException("accountType is null");
+ final Bundle optionsIn = new Bundle();
+ if (addAccountOptions != null) {
+ optionsIn.putAll(addAccountOptions);
+ }
+ optionsIn.putString(KEY_ANDROID_PACKAGE_NAME, mContext.getPackageName());
+
+ return new AmsTask(activity, handler, callback) {
+ @Override
+ public void doWork() throws RemoteException {
+ mService.addAccount(mResponse, accountType, authTokenType,
+ requiredFeatures, activity != null, optionsIn);
+ }
+ }.start();
+ } else {
+ return addAccountAsUser(accountType, authTokenType, requiredFeatures, addAccountOptions,
+ activity, callback, handler, mContext.getUser());
+ }
+ }
+
+ /**
+ * @see #addAccount(String, String, String[], Bundle, Activity, AccountManagerCallback, Handler)
+ * @hide
+ */
+ public AccountManagerFuture<Bundle> addAccountAsUser(final String accountType,
+ final String authTokenType, final String[] requiredFeatures,
+ final Bundle addAccountOptions, final Activity activity,
+ AccountManagerCallback<Bundle> callback, Handler handler, final UserHandle userHandle) {
+ if (accountType == null) throw new IllegalArgumentException("accountType is null");
+ if (userHandle == null) throw new IllegalArgumentException("userHandle is null");
+ final Bundle optionsIn = new Bundle();
+ if (addAccountOptions != null) {
+ optionsIn.putAll(addAccountOptions);
+ }
+ optionsIn.putString(KEY_ANDROID_PACKAGE_NAME, mContext.getPackageName());
+
+ return new AmsTask(activity, handler, callback) {
+ @Override
+ public void doWork() throws RemoteException {
+ mService.addAccountAsUser(mResponse, accountType, authTokenType,
+ requiredFeatures, activity != null, optionsIn, userHandle.getIdentifier());
+ }
+ }.start();
+ }
+
+
+ /**
+ * Adds shared accounts from a parent user to a secondary user. Adding the shared account
+ * doesn't take effect immediately. When the target user starts up, any pending shared accounts
+ * are attempted to be copied to the target user from the primary via calls to the
+ * authenticator.
+ * @param parentUser parent user
+ * @param user target user
+ * @hide
+ */
+ public void addSharedAccountsFromParentUser(UserHandle parentUser, UserHandle user) {
+ try {
+ mService.addSharedAccountsFromParentUser(parentUser.getIdentifier(),
+ user.getIdentifier(), mContext.getOpPackageName());
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Copies an account from one user to another user.
+ * @param account the account to copy
+ * @param fromUser the user to copy the account from
+ * @param toUser the target user
+ * @param callback Callback to invoke when the request completes,
+ * null for no callback
+ * @param handler {@link Handler} identifying the callback thread,
+ * null for the main thread
+ * @return An {@link AccountManagerFuture} which resolves to a Boolean indicated whether it
+ * succeeded.
+ * @hide
+ */
+ public AccountManagerFuture<Boolean> copyAccountToUser(
+ final Account account, final UserHandle fromUser, final UserHandle toUser,
+ AccountManagerCallback<Boolean> callback, Handler handler) {
+ if (account == null) throw new IllegalArgumentException("account is null");
+ if (toUser == null || fromUser == null) {
+ throw new IllegalArgumentException("fromUser and toUser cannot be null");
+ }
+
+ return new Future2Task<Boolean>(handler, callback) {
+ @Override
+ public void doWork() throws RemoteException {
+ mService.copyAccountToUser(
+ mResponse, account, fromUser.getIdentifier(), toUser.getIdentifier());
+ }
+ @Override
+ public Boolean bundleToResult(Bundle bundle) throws AuthenticatorException {
+ if (!bundle.containsKey(KEY_BOOLEAN_RESULT)) {
+ throw new AuthenticatorException("no result in response");
+ }
+ return bundle.getBoolean(KEY_BOOLEAN_RESULT);
+ }
+ }.start();
+ }
+
+ /**
+ * Confirms that the user knows the password for an account to make extra
+ * sure they are the owner of the account. The user-entered password can
+ * be supplied directly, otherwise the authenticator for this account type
+ * prompts the user with the appropriate interface. This method is
+ * intended for applications which want extra assurance; for example, the
+ * phone lock screen uses this to let the user unlock the phone with an
+ * account password if they forget the lock pattern.
+ *
+ * <p>If the user-entered password matches a saved password for this
+ * account, the request is considered valid; otherwise the authenticator
+ * verifies the password (usually by contacting the server).
+ *
+ * <p>This method may be called from any thread, but the returned
+ * {@link AccountManagerFuture} must not be used on the main thread.
+ *
+ * <p><b>NOTE:</b> If targeting your app to work on API level 22 and before,
+ * MANAGE_ACCOUNTS permission is needed for those platforms. See docs
+ * for this function in API level 22.
+ *
+ * @param account The account to confirm password knowledge for
+ * @param options Authenticator-specific options for the request;
+ * if the {@link #KEY_PASSWORD} string field is present, the
+ * authenticator may use it directly rather than prompting the user;
+ * may be null or empty
+ * @param activity The {@link Activity} context to use for launching a new
+ * authenticator-defined sub-Activity to prompt the user to enter a
+ * password; used only to call startActivity(); if null, the prompt
+ * will not be launched directly, but the necessary {@link Intent}
+ * will be returned to the caller instead
+ * @param callback Callback to invoke when the request completes,
+ * null for no callback
+ * @param handler {@link Handler} identifying the callback thread,
+ * null for the main thread
+ * @return An {@link AccountManagerFuture} which resolves to a Bundle
+ * with these fields if activity or password was supplied and
+ * the account was successfully verified:
+ * <ul>
+ * <li> {@link #KEY_ACCOUNT_NAME} - the name of the account verified
+ * <li> {@link #KEY_ACCOUNT_TYPE} - the type of the account
+ * <li> {@link #KEY_BOOLEAN_RESULT} - true to indicate success
+ * </ul>
+ *
+ * If no activity or password was specified, the returned Bundle contains
+ * {@link #KEY_INTENT} with the {@link Intent} needed to launch the
+ * password prompt.
+ *
+ * <p>Also the returning Bundle may contain {@link
+ * #KEY_LAST_AUTHENTICATED_TIME} indicating the last time the
+ * credential was validated/created.
+ *
+ * If an error occurred,{@link AccountManagerFuture#getResult()} throws:
+ * <ul>
+ * <li> {@link AuthenticatorException} if the authenticator failed to respond
+ * <li> {@link OperationCanceledException} if the operation was canceled for
+ * any reason, including the user canceling the password prompt
+ * <li> {@link IOException} if the authenticator experienced an I/O problem
+ * verifying the password, usually because of network trouble
+ * </ul>
+ */
+ @UserHandleAware
+ public AccountManagerFuture<Bundle> confirmCredentials(final Account account,
+ final Bundle options,
+ final Activity activity,
+ final AccountManagerCallback<Bundle> callback,
+ final Handler handler) {
+ return confirmCredentialsAsUser(account, options, activity, callback, handler,
+ mContext.getUser());
+ }
+
+ /**
+ * @hide
+ * Same as {@link #confirmCredentials(Account, Bundle, Activity, AccountManagerCallback, Handler)}
+ * but for the specified user.
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public AccountManagerFuture<Bundle> confirmCredentialsAsUser(final Account account,
+ final Bundle options,
+ final Activity activity,
+ final AccountManagerCallback<Bundle> callback,
+ final Handler handler, UserHandle userHandle) {
+ if (account == null) throw new IllegalArgumentException("account is null");
+ final int userId = userHandle.getIdentifier();
+ return new AmsTask(activity, handler, callback) {
+ @Override
+ public void doWork() throws RemoteException {
+ mService.confirmCredentialsAsUser(mResponse, account, options, activity != null,
+ userId);
+ }
+ }.start();
+ }
+
+ /**
+ * Asks the user to enter a new password for an account, updating the
+ * saved credentials for the account. Normally this happens automatically
+ * when the server rejects credentials during an auth token fetch, but this
+ * can be invoked directly to ensure we have the correct credentials stored.
+ *
+ * <p>This method may be called from any thread, but the returned
+ * {@link AccountManagerFuture} must not be used on the main thread.
+ *
+ * <p><b>NOTE:</b> If targeting your app to work on API level 22 and before,
+ * MANAGE_ACCOUNTS permission is needed for those platforms. See docs for
+ * this function in API level 22.
+ *
+ * @param account The account to update credentials for
+ * @param authTokenType The credentials entered must allow an auth token
+ * of this type to be created (but no actual auth token is returned);
+ * may be null
+ * @param options Authenticator-specific options for the request;
+ * may be null or empty
+ * @param activity The {@link Activity} context to use for launching a new
+ * authenticator-defined sub-Activity to prompt the user to enter a
+ * password; used only to call startActivity(); if null, the prompt
+ * will not be launched directly, but the necessary {@link Intent}
+ * will be returned to the caller instead
+ * @param callback Callback to invoke when the request completes,
+ * null for no callback
+ * @param handler {@link Handler} identifying the callback thread,
+ * null for the main thread
+ * @return An {@link AccountManagerFuture} which resolves to a Bundle
+ * with these fields if an activity was supplied and the account
+ * credentials were successfully updated:
+ * <ul>
+ * <li> {@link #KEY_ACCOUNT_NAME} - the name of the account
+ * <li> {@link #KEY_ACCOUNT_TYPE} - the type of the account
+ * </ul>
+ *
+ * If no activity was specified, the returned Bundle contains
+ * {@link #KEY_INTENT} with the {@link Intent} needed to launch the
+ * password prompt. If an error occurred,
+ * {@link AccountManagerFuture#getResult()} throws:
+ * <ul>
+ * <li> {@link AuthenticatorException} if the authenticator failed to respond
+ * <li> {@link OperationCanceledException} if the operation was canceled for
+ * any reason, including the user canceling the password prompt
+ * <li> {@link IOException} if the authenticator experienced an I/O problem
+ * verifying the password, usually because of network trouble
+ * </ul>
+ */
+ public AccountManagerFuture<Bundle> updateCredentials(final Account account,
+ final String authTokenType,
+ final Bundle options, final Activity activity,
+ final AccountManagerCallback<Bundle> callback,
+ final Handler handler) {
+ if (account == null) throw new IllegalArgumentException("account is null");
+ return new AmsTask(activity, handler, callback) {
+ @Override
+ public void doWork() throws RemoteException {
+ mService.updateCredentials(mResponse, account, authTokenType, activity != null,
+ options);
+ }
+ }.start();
+ }
+
+ /**
+ * Offers the user an opportunity to change an authenticator's settings.
+ * These properties are for the authenticator in general, not a particular
+ * account. Not all authenticators support this method.
+ *
+ * <p>This method may be called from any thread, but the returned
+ * {@link AccountManagerFuture} must not be used on the main thread.
+ *
+ * <p>This method requires the caller to have the same signature as the
+ * authenticator associated with the specified account type.
+ *
+ * <p><b>NOTE:</b> If targeting your app to work on API level 22 and before,
+ * MANAGE_ACCOUNTS permission is needed for those platforms. See docs
+ * for this function in API level 22.
+ *
+ * @param accountType The account type associated with the authenticator
+ * to adjust
+ * @param activity The {@link Activity} context to use for launching a new
+ * authenticator-defined sub-Activity to adjust authenticator settings;
+ * used only to call startActivity(); if null, the settings dialog will
+ * not be launched directly, but the necessary {@link Intent} will be
+ * returned to the caller instead
+ * @param callback Callback to invoke when the request completes,
+ * null for no callback
+ * @param handler {@link Handler} identifying the callback thread,
+ * null for the main thread
+ * @return An {@link AccountManagerFuture} which resolves to a Bundle
+ * which is empty if properties were edited successfully, or
+ * if no activity was specified, contains only {@link #KEY_INTENT}
+ * needed to launch the authenticator's settings dialog.
+ * If an error occurred, {@link AccountManagerFuture#getResult()}
+ * throws:
+ * <ul>
+ * <li> {@link AuthenticatorException} if no authenticator was registered for
+ * this account type or the authenticator failed to respond
+ * <li> {@link OperationCanceledException} if the operation was canceled for
+ * any reason, including the user canceling the settings dialog
+ * <li> {@link IOException} if the authenticator experienced an I/O problem
+ * updating settings, usually because of network trouble
+ * </ul>
+ */
+ public AccountManagerFuture<Bundle> editProperties(final String accountType,
+ final Activity activity, final AccountManagerCallback<Bundle> callback,
+ final Handler handler) {
+ if (accountType == null) throw new IllegalArgumentException("accountType is null");
+ return new AmsTask(activity, handler, callback) {
+ @Override
+ public void doWork() throws RemoteException {
+ mService.editProperties(mResponse, accountType, activity != null);
+ }
+ }.start();
+ }
+
+ /**
+ * @hide
+ * Checks if the given account exists on any of the users on the device.
+ * Only the system process can call this method.
+ *
+ * @param account The account to check for existence.
+ * @return whether any user has this account
+ */
+ public boolean someUserHasAccount(@NonNull final Account account) {
+ try {
+ return mService.someUserHasAccount(account);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ private void ensureNotOnMainThread() {
+ final Looper looper = Looper.myLooper();
+ if (looper != null && looper == mContext.getMainLooper()) {
+ final IllegalStateException exception = new IllegalStateException(
+ "calling this from your main thread can lead to deadlock");
+ Log.e(TAG, "calling this from your main thread can lead to deadlock and/or ANRs",
+ exception);
+ if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.FROYO) {
+ throw exception;
+ }
+ }
+ }
+
+ private void postToHandler(Handler handler, final AccountManagerCallback<Bundle> callback,
+ final AccountManagerFuture<Bundle> future) {
+ handler = handler == null ? mMainHandler : handler;
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ callback.run(future);
+ }
+ });
+ }
+
+ private void postToHandler(Handler handler, final OnAccountsUpdateListener listener,
+ final Account[] accounts) {
+ final Account[] accountsCopy = new Account[accounts.length];
+ // send a copy to make sure that one doesn't
+ // change what another sees
+ System.arraycopy(accounts, 0, accountsCopy, 0, accountsCopy.length);
+ handler = (handler == null) ? mMainHandler : handler;
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ synchronized (mAccountsUpdatedListeners) {
+ try {
+ if (mAccountsUpdatedListeners.containsKey(listener)) {
+ Set<String> types = mAccountsUpdatedListenersTypes.get(listener);
+ if (types != null) {
+ // filter by account type;
+ ArrayList<Account> filtered = new ArrayList<>();
+ for (Account account : accountsCopy) {
+ if (types.contains(account.type)) {
+ filtered.add(account);
+ }
+ }
+ listener.onAccountsUpdated(
+ filtered.toArray(new Account[filtered.size()]));
+ } else {
+ listener.onAccountsUpdated(accountsCopy);
+ }
+ }
+ } catch (SQLException e) {
+ // Better luck next time. If the problem was disk-full,
+ // the STORAGE_OK intent will re-trigger the update.
+ Log.e(TAG, "Can't update accounts", e);
+ }
+ }
+ }
+ });
+ }
+
+ private abstract class AmsTask extends FutureTask<Bundle> implements AccountManagerFuture<Bundle> {
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ final IAccountManagerResponse mResponse;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ final Handler mHandler;
+ final AccountManagerCallback<Bundle> mCallback;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ final Activity mActivity;
+ public AmsTask(Activity activity, Handler handler, AccountManagerCallback<Bundle> callback) {
+ super(new Callable<Bundle>() {
+ @Override
+ public Bundle call() throws Exception {
+ throw new IllegalStateException("this should never be called");
+ }
+ });
+
+ mHandler = handler;
+ mCallback = callback;
+ mActivity = activity;
+ mResponse = new Response();
+ }
+
+ public final AccountManagerFuture<Bundle> start() {
+ try {
+ doWork();
+ } catch (RemoteException e) {
+ setException(e);
+ }
+ return this;
+ }
+
+ @Override
+ protected void set(Bundle bundle) {
+ // TODO: somehow a null is being set as the result of the Future. Log this
+ // case to help debug where this is occurring. When this bug is fixed this
+ // condition statement should be removed.
+ if (bundle == null) {
+ Log.e(TAG, "the bundle must not be null", new Exception());
+ }
+ super.set(bundle);
+ }
+
+ public abstract void doWork() throws RemoteException;
+
+ private Bundle internalGetResult(Long timeout, TimeUnit unit)
+ throws OperationCanceledException, IOException, AuthenticatorException {
+ if (!isDone()) {
+ ensureNotOnMainThread();
+ }
+ try {
+ if (timeout == null) {
+ return get();
+ } else {
+ return get(timeout, unit);
+ }
+ } catch (CancellationException | TimeoutException | InterruptedException e) {
+ throw new OperationCanceledException(e);
+ } catch (ExecutionException e) {
+ final Throwable cause = e.getCause();
+ if (cause instanceof IOException) {
+ throw (IOException) cause;
+ } else if (cause instanceof UnsupportedOperationException) {
+ throw new AuthenticatorException(cause);
+ } else if (cause instanceof AuthenticatorException) {
+ throw (AuthenticatorException) cause;
+ } else if (cause instanceof RuntimeException) {
+ throw (RuntimeException) cause;
+ } else if (cause instanceof Error) {
+ throw (Error) cause;
+ } else {
+ throw new IllegalStateException(cause);
+ }
+ } finally {
+ cancel(true /* interrupt if running */);
+ }
+ }
+
+ @Override
+ public Bundle getResult()
+ throws OperationCanceledException, IOException, AuthenticatorException {
+ return internalGetResult(null, null);
+ }
+
+ @Override
+ public Bundle getResult(long timeout, TimeUnit unit)
+ throws OperationCanceledException, IOException, AuthenticatorException {
+ return internalGetResult(timeout, unit);
+ }
+
+ @Override
+ protected void done() {
+ if (mCallback != null) {
+ postToHandler(mHandler, mCallback, this);
+ }
+ }
+
+ /** Handles the responses from the AccountManager */
+ private class Response extends IAccountManagerResponse.Stub {
+ @Override
+ public void onResult(Bundle bundle) {
+ if (bundle == null) {
+ onError(ERROR_CODE_INVALID_RESPONSE, "null bundle returned");
+ return;
+ }
+ Intent intent = bundle.getParcelable(KEY_INTENT, android.content.Intent.class);
+ if (intent != null && mActivity != null) {
+ // since the user provided an Activity we will silently start intents
+ // that we see
+ mActivity.startActivity(intent);
+ // leave the Future running to wait for the real response to this request
+ } else if (bundle.getBoolean("retry")) {
+ try {
+ doWork();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ } else {
+ set(bundle);
+ }
+ }
+
+ @Override
+ public void onError(int code, String message) {
+ if (code == ERROR_CODE_CANCELED || code == ERROR_CODE_USER_RESTRICTED
+ || code == ERROR_CODE_MANAGEMENT_DISABLED_FOR_ACCOUNT_TYPE) {
+ // the authenticator indicated that this request was canceled or we were
+ // forbidden to fulfill; cancel now
+ cancel(true /* mayInterruptIfRunning */);
+ return;
+ }
+ setException(convertErrorToException(code, message));
+ }
+ }
+
+ }
+
+ private abstract class BaseFutureTask<T> extends FutureTask<T> {
+ final public IAccountManagerResponse mResponse;
+ final Handler mHandler;
+
+ public BaseFutureTask(Handler handler) {
+ super(new Callable<T>() {
+ @Override
+ public T call() throws Exception {
+ throw new IllegalStateException("this should never be called");
+ }
+ });
+ mHandler = handler;
+ mResponse = new Response();
+ }
+
+ public abstract void doWork() throws RemoteException;
+
+ public abstract T bundleToResult(Bundle bundle) throws AuthenticatorException;
+
+ protected void postRunnableToHandler(Runnable runnable) {
+ Handler handler = (mHandler == null) ? mMainHandler : mHandler;
+ handler.post(runnable);
+ }
+
+ protected void startTask() {
+ try {
+ doWork();
+ } catch (RemoteException e) {
+ setException(e);
+ }
+ }
+
+ protected class Response extends IAccountManagerResponse.Stub {
+ @Override
+ public void onResult(Bundle bundle) {
+ try {
+ T result = bundleToResult(bundle);
+ if (result == null) {
+ return;
+ }
+ set(result);
+ return;
+ } catch (ClassCastException e) {
+ // we will set the exception below
+ } catch (AuthenticatorException e) {
+ // we will set the exception below
+ }
+ onError(ERROR_CODE_INVALID_RESPONSE, "no result in response");
+ }
+
+ @Override
+ public void onError(int code, String message) {
+ if (code == ERROR_CODE_CANCELED || code == ERROR_CODE_USER_RESTRICTED
+ || code == ERROR_CODE_MANAGEMENT_DISABLED_FOR_ACCOUNT_TYPE) {
+ // the authenticator indicated that this request was canceled or we were
+ // forbidden to fulfill; cancel now
+ cancel(true /* mayInterruptIfRunning */);
+ return;
+ }
+ setException(convertErrorToException(code, message));
+ }
+ }
+ }
+
+ private abstract class Future2Task<T>
+ extends BaseFutureTask<T> implements AccountManagerFuture<T> {
+ final AccountManagerCallback<T> mCallback;
+ public Future2Task(Handler handler, AccountManagerCallback<T> callback) {
+ super(handler);
+ mCallback = callback;
+ }
+
+ @Override
+ protected void done() {
+ if (mCallback != null) {
+ postRunnableToHandler(new Runnable() {
+ @Override
+ public void run() {
+ mCallback.run(Future2Task.this);
+ }
+ });
+ }
+ }
+
+ public Future2Task<T> start() {
+ startTask();
+ return this;
+ }
+
+ private T internalGetResult(Long timeout, TimeUnit unit)
+ throws OperationCanceledException, IOException, AuthenticatorException {
+ if (!isDone()) {
+ ensureNotOnMainThread();
+ }
+ try {
+ if (timeout == null) {
+ return get();
+ } else {
+ return get(timeout, unit);
+ }
+ } catch (InterruptedException e) {
+ // fall through and cancel
+ } catch (TimeoutException e) {
+ // fall through and cancel
+ } catch (CancellationException e) {
+ // fall through and cancel
+ } catch (ExecutionException e) {
+ final Throwable cause = e.getCause();
+ if (cause instanceof IOException) {
+ throw (IOException) cause;
+ } else if (cause instanceof UnsupportedOperationException) {
+ throw new AuthenticatorException(cause);
+ } else if (cause instanceof AuthenticatorException) {
+ throw (AuthenticatorException) cause;
+ } else if (cause instanceof RuntimeException) {
+ throw (RuntimeException) cause;
+ } else if (cause instanceof Error) {
+ throw (Error) cause;
+ } else {
+ throw new IllegalStateException(cause);
+ }
+ } finally {
+ cancel(true /* interrupt if running */);
+ }
+ throw new OperationCanceledException();
+ }
+
+ @Override
+ public T getResult()
+ throws OperationCanceledException, IOException, AuthenticatorException {
+ return internalGetResult(null, null);
+ }
+
+ @Override
+ public T getResult(long timeout, TimeUnit unit)
+ throws OperationCanceledException, IOException, AuthenticatorException {
+ return internalGetResult(timeout, unit);
+ }
+
+ }
+
+ private Exception convertErrorToException(int code, String message) {
+ if (code == ERROR_CODE_NETWORK_ERROR) {
+ return new IOException(message);
+ }
+
+ if (code == ERROR_CODE_UNSUPPORTED_OPERATION) {
+ return new UnsupportedOperationException(message);
+ }
+
+ if (code == ERROR_CODE_INVALID_RESPONSE) {
+ return new AuthenticatorException(message);
+ }
+
+ if (code == ERROR_CODE_BAD_ARGUMENTS) {
+ return new IllegalArgumentException(message);
+ }
+
+ return new AuthenticatorException(message);
+ }
+
+ private void getAccountByTypeAndFeatures(String accountType, String[] features,
+ AccountManagerCallback<Bundle> callback, Handler handler) {
+ (new AmsTask(null, handler, callback) {
+ @Override
+ public void doWork() throws RemoteException {
+ mService.getAccountByTypeAndFeatures(mResponse, accountType, features,
+ mContext.getOpPackageName());
+ }
+
+ }).start();
+ }
+
+ private class GetAuthTokenByTypeAndFeaturesTask
+ extends AmsTask implements AccountManagerCallback<Bundle> {
+ GetAuthTokenByTypeAndFeaturesTask(final String accountType, final String authTokenType,
+ final String[] features, Activity activityForPrompting,
+ final Bundle addAccountOptions, final Bundle loginOptions,
+ AccountManagerCallback<Bundle> callback, Handler handler) {
+ super(activityForPrompting, handler, callback);
+ if (accountType == null) throw new IllegalArgumentException("account type is null");
+ mAccountType = accountType;
+ mAuthTokenType = authTokenType;
+ mFeatures = features;
+ mAddAccountOptions = addAccountOptions;
+ mLoginOptions = loginOptions;
+ mMyCallback = this;
+ }
+ volatile AccountManagerFuture<Bundle> mFuture = null;
+ final String mAccountType;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ final String mAuthTokenType;
+ final String[] mFeatures;
+ final Bundle mAddAccountOptions;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ final Bundle mLoginOptions;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ final AccountManagerCallback<Bundle> mMyCallback;
+ private volatile int mNumAccounts = 0;
+
+ @Override
+ public void doWork() throws RemoteException {
+ getAccountByTypeAndFeatures(mAccountType, mFeatures,
+ new AccountManagerCallback<Bundle>() {
+ @Override
+ public void run(AccountManagerFuture<Bundle> future) {
+ String accountName = null;
+ String accountType = null;
+ try {
+ Bundle result = future.getResult();
+ accountName = result.getString(AccountManager.KEY_ACCOUNT_NAME);
+ accountType = result.getString(AccountManager.KEY_ACCOUNT_TYPE);
+ } catch (OperationCanceledException e) {
+ setException(e);
+ return;
+ } catch (IOException e) {
+ setException(e);
+ return;
+ } catch (AuthenticatorException e) {
+ setException(e);
+ return;
+ }
+
+ if (accountName == null) {
+ if (mActivity != null) {
+ // no accounts, add one now. pretend that the user directly
+ // made this request
+ mFuture = addAccount(mAccountType, mAuthTokenType, mFeatures,
+ mAddAccountOptions, mActivity, mMyCallback, mHandler);
+ } else {
+ // send result since we can't prompt to add an account
+ Bundle result = new Bundle();
+ result.putString(KEY_ACCOUNT_NAME, null);
+ result.putString(KEY_ACCOUNT_TYPE, null);
+ result.putString(KEY_AUTHTOKEN, null);
+ result.putBinder(KEY_ACCOUNT_ACCESS_ID, null);
+ try {
+ mResponse.onResult(result);
+ } catch (RemoteException e) {
+ // this will never happen
+ }
+ // we are done
+ }
+ } else {
+ mNumAccounts = 1;
+ Account account = new Account(accountName, accountType);
+ // have a single account, return an authtoken for it
+ if (mActivity == null) {
+ mFuture = getAuthToken(account, mAuthTokenType,
+ false /* notifyAuthFailure */, mMyCallback, mHandler);
+ } else {
+ mFuture = getAuthToken(account, mAuthTokenType, mLoginOptions,
+ mActivity, mMyCallback, mHandler);
+ }
+ }
+ }}, mHandler);
+ }
+
+ @Override
+ public void run(AccountManagerFuture<Bundle> future) {
+ try {
+ final Bundle result = future.getResult();
+ if (mNumAccounts == 0) {
+ final String accountName = result.getString(KEY_ACCOUNT_NAME);
+ final String accountType = result.getString(KEY_ACCOUNT_TYPE);
+ if (TextUtils.isEmpty(accountName) || TextUtils.isEmpty(accountType)) {
+ setException(new AuthenticatorException("account not in result"));
+ return;
+ }
+ final String accessId = result.getString(KEY_ACCOUNT_ACCESS_ID);
+ final Account account = new Account(accountName, accountType, accessId);
+ mNumAccounts = 1;
+ getAuthToken(account, mAuthTokenType, null /* options */, mActivity,
+ mMyCallback, mHandler);
+ return;
+ }
+ set(result);
+ } catch (OperationCanceledException e) {
+ cancel(true /* mayInterruptIfRUnning */);
+ } catch (IOException e) {
+ setException(e);
+ } catch (AuthenticatorException e) {
+ setException(e);
+ }
+ }
+ }
+
+ /**
+ * This convenience helper combines the functionality of {@link #getAccountsByTypeAndFeatures},
+ * {@link #getAuthToken}, and {@link #addAccount}.
+ *
+ * <p>
+ * This method gets a list of the accounts matching specific type and feature set which are
+ * visible to the caller (see {@link #getAccountsByType} for details);
+ * if there is exactly one already visible account, it is used; if there are some
+ * accounts for which user grant visibility, the user is prompted to pick one; if there are
+ * none, the user is prompted to add one. Finally, an auth token is acquired for the chosen
+ * account.
+ *
+ * <p>
+ * This method may be called from any thread, but the returned {@link AccountManagerFuture} must
+ * not be used on the main thread.
+ *
+ * <p>
+ * <b>NOTE:</b> If targeting your app to work on API level 22 and before, MANAGE_ACCOUNTS
+ * permission is needed for those platforms. See docs for this function in API level 22.
+ *
+ * @param accountType The account type required (see {@link #getAccountsByType}), must not be
+ * null
+ * @param authTokenType The desired auth token type (see {@link #getAuthToken}), must not be
+ * null
+ * @param features Required features for the account (see
+ * {@link #getAccountsByTypeAndFeatures}), may be null or empty
+ * @param activity The {@link Activity} context to use for launching new sub-Activities to
+ * prompt to add an account, select an account, and/or enter a password, as necessary;
+ * used only to call startActivity(); should not be null
+ * @param addAccountOptions Authenticator-specific options to use for adding new accounts; may
+ * be null or empty
+ * @param getAuthTokenOptions Authenticator-specific options to use for getting auth tokens; may
+ * be null or empty
+ * @param callback Callback to invoke when the request completes, null for no callback
+ * @param handler {@link Handler} identifying the callback thread, null for the main thread
+ * @return An {@link AccountManagerFuture} which resolves to a Bundle with at least the
+ * following fields:
+ * <ul>
+ * <li>{@link #KEY_ACCOUNT_NAME} - the name of the account
+ * <li>{@link #KEY_ACCOUNT_TYPE} - the type of the account
+ * <li>{@link #KEY_AUTHTOKEN} - the auth token you wanted
+ * </ul>
+ *
+ * If an error occurred, {@link AccountManagerFuture#getResult()} throws:
+ * <ul>
+ * <li>{@link AuthenticatorException} if no authenticator was registered for this
+ * account type or the authenticator failed to respond
+ * <li>{@link OperationCanceledException} if the operation was canceled for any reason,
+ * including the user canceling any operation
+ * <li>{@link IOException} if the authenticator experienced an I/O problem updating
+ * settings, usually because of network trouble
+ * </ul>
+ */
+ public AccountManagerFuture<Bundle> getAuthTokenByFeatures(
+ final String accountType, final String authTokenType, final String[] features,
+ final Activity activity, final Bundle addAccountOptions,
+ final Bundle getAuthTokenOptions, final AccountManagerCallback<Bundle> callback,
+ final Handler handler) {
+ if (accountType == null) throw new IllegalArgumentException("account type is null");
+ if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
+ final GetAuthTokenByTypeAndFeaturesTask task =
+ new GetAuthTokenByTypeAndFeaturesTask(accountType, authTokenType, features,
+ activity, addAccountOptions, getAuthTokenOptions, callback, handler);
+ task.start();
+ return task;
+ }
+
+ /**
+ * Deprecated in favor of {@link #newChooseAccountIntent(Account, List, String[], String,
+ * String, String[], Bundle)}.
+ *
+ * Returns an intent to an {@link Activity} that prompts the user to choose from a list of
+ * accounts.
+ * The caller will then typically start the activity by calling
+ * <code>startActivityForResult(intent, ...);</code>.
+ * <p>
+ * On success the activity returns a Bundle with the account name and type specified using
+ * keys {@link #KEY_ACCOUNT_NAME} and {@link #KEY_ACCOUNT_TYPE}.
+ * Chosen account is marked as {@link #VISIBILITY_USER_MANAGED_VISIBLE} to the caller
+ * (see {@link #setAccountVisibility}) and will be returned to it in consequent
+ * {@link #getAccountsByType}) calls.
+ * <p>
+ * The most common case is to call this with one account type, e.g.:
+ * <p>
+ * <pre> newChooseAccountIntent(null, null, new String[]{"com.google"}, false, null,
+ * null, null, null);</pre>
+ * @param selectedAccount if specified, indicates that the {@link Account} is the currently
+ * selected one, according to the caller's definition of selected.
+ * @param allowableAccounts an optional {@link List} of accounts that are allowed to be
+ * shown. If not specified then this field will not limit the displayed accounts.
+ * @param allowableAccountTypes an optional string array of account types. These are used
+ * both to filter the shown accounts and to filter the list of account types that are shown
+ * when adding an account. If not specified then this field will not limit the displayed
+ * account types when adding an account.
+ * @param alwaysPromptForAccount boolean that is ignored.
+ * @param descriptionOverrideText if non-null this string is used as the description in the
+ * accounts chooser screen rather than the default
+ * @param addAccountAuthTokenType this string is passed as the {@link #addAccount}
+ * authTokenType parameter
+ * @param addAccountRequiredFeatures this string array is passed as the {@link #addAccount}
+ * requiredFeatures parameter
+ * @param addAccountOptions This {@link Bundle} is passed as the {@link #addAccount} options
+ * parameter
+ * @return an {@link Intent} that can be used to launch the ChooseAccount activity flow.
+ */
+ @Deprecated
+ static public Intent newChooseAccountIntent(
+ Account selectedAccount,
+ ArrayList<Account> allowableAccounts,
+ String[] allowableAccountTypes,
+ boolean alwaysPromptForAccount,
+ String descriptionOverrideText,
+ String addAccountAuthTokenType,
+ String[] addAccountRequiredFeatures,
+ Bundle addAccountOptions) {
+ return newChooseAccountIntent(
+ selectedAccount,
+ allowableAccounts,
+ allowableAccountTypes,
+ descriptionOverrideText,
+ addAccountAuthTokenType,
+ addAccountRequiredFeatures,
+ addAccountOptions);
+ }
+
+ /**
+ * Returns an intent to an {@link Activity} that prompts the user to choose from a list of
+ * accounts.
+ * The caller will then typically start the activity by calling
+ * <code>startActivityForResult(intent, ...);</code>.
+ * <p>
+ * On success the activity returns a Bundle with the account name and type specified using
+ * keys {@link #KEY_ACCOUNT_NAME} and {@link #KEY_ACCOUNT_TYPE}.
+ * Chosen account is marked as {@link #VISIBILITY_USER_MANAGED_VISIBLE} to the caller
+ * (see {@link #setAccountVisibility}) and will be returned to it in consequent
+ * {@link #getAccountsByType}) calls.
+ * <p>
+ * The most common case is to call this with one account type, e.g.:
+ * <p>
+ * <pre> newChooseAccountIntent(null, null, new String[]{"com.google"}, null, null, null,
+ * null);</pre>
+ * @param selectedAccount if specified, indicates that the {@link Account} is the currently
+ * selected one, according to the caller's definition of selected.
+ * @param allowableAccounts an optional {@link List} of accounts that are allowed to be
+ * shown. If not specified then this field will not limit the displayed accounts.
+ * @param allowableAccountTypes an optional string array of account types. These are used
+ * both to filter the shown accounts and to filter the list of account types that are shown
+ * when adding an account. If not specified then this field will not limit the displayed
+ * account types when adding an account.
+ * @param descriptionOverrideText if non-null this string is used as the description in the
+ * accounts chooser screen rather than the default
+ * @param addAccountAuthTokenType this string is passed as the {@link #addAccount}
+ * authTokenType parameter
+ * @param addAccountRequiredFeatures this string array is passed as the {@link #addAccount}
+ * requiredFeatures parameter
+ * @param addAccountOptions This {@link Bundle} is passed as the {@link #addAccount} options
+ * parameter
+ * @return an {@link Intent} that can be used to launch the ChooseAccount activity flow.
+ */
+ static public Intent newChooseAccountIntent(
+ Account selectedAccount,
+ List<Account> allowableAccounts,
+ String[] allowableAccountTypes,
+ String descriptionOverrideText,
+ String addAccountAuthTokenType,
+ String[] addAccountRequiredFeatures,
+ Bundle addAccountOptions) {
+ Intent intent = new Intent();
+ ComponentName componentName = ComponentName.unflattenFromString(
+ Resources.getSystem().getString(R.string.config_chooseTypeAndAccountActivity));
+ intent.setClassName(componentName.getPackageName(),
+ componentName.getClassName());
+ intent.putExtra(ChooseTypeAndAccountActivity.EXTRA_ALLOWABLE_ACCOUNTS_ARRAYLIST,
+ allowableAccounts == null ? null : new ArrayList<Account>(allowableAccounts));
+ intent.putExtra(ChooseTypeAndAccountActivity.EXTRA_ALLOWABLE_ACCOUNT_TYPES_STRING_ARRAY,
+ allowableAccountTypes);
+ intent.putExtra(ChooseTypeAndAccountActivity.EXTRA_ADD_ACCOUNT_OPTIONS_BUNDLE,
+ addAccountOptions);
+ intent.putExtra(ChooseTypeAndAccountActivity.EXTRA_SELECTED_ACCOUNT, selectedAccount);
+ intent.putExtra(ChooseTypeAndAccountActivity.EXTRA_DESCRIPTION_TEXT_OVERRIDE,
+ descriptionOverrideText);
+ intent.putExtra(ChooseTypeAndAccountActivity.EXTRA_ADD_ACCOUNT_AUTH_TOKEN_TYPE_STRING,
+ addAccountAuthTokenType);
+ intent.putExtra(
+ ChooseTypeAndAccountActivity.EXTRA_ADD_ACCOUNT_REQUIRED_FEATURES_STRING_ARRAY,
+ addAccountRequiredFeatures);
+ return intent;
+ }
+
+ private final HashMap<OnAccountsUpdateListener, Handler> mAccountsUpdatedListeners =
+ Maps.newHashMap();
+
+ private final HashMap<OnAccountsUpdateListener, Set<String> > mAccountsUpdatedListenersTypes =
+ Maps.newHashMap();
+
+ /**
+ * BroadcastReceiver that listens for the ACTION_VISIBLE_ACCOUNTS_CHANGED intent
+ * so that it can read the updated list of accounts and send them to the listener
+ * in mAccountsUpdatedListeners.
+ */
+ private final BroadcastReceiver mAccountsChangedBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(final Context context, final Intent intent) {
+ final Account[] accounts = getAccounts();
+ // send the result to the listeners
+ synchronized (mAccountsUpdatedListeners) {
+ for (Map.Entry<OnAccountsUpdateListener, Handler> entry :
+ mAccountsUpdatedListeners.entrySet()) {
+ postToHandler(entry.getValue(), entry.getKey(), accounts);
+ }
+ }
+ }
+ };
+
+ /**
+ * Adds an {@link OnAccountsUpdateListener} to this instance of the {@link AccountManager}. This
+ * listener will be notified whenever user or AbstractAccountAuthenticator made changes to
+ * accounts of any type related to the caller. This method is equivalent to
+ * addOnAccountsUpdatedListener(listener, handler, updateImmediately, null)
+ *
+ * @see #addOnAccountsUpdatedListener(OnAccountsUpdateListener, Handler, boolean,
+ * String[])
+ */
+ public void addOnAccountsUpdatedListener(final OnAccountsUpdateListener listener,
+ Handler handler, boolean updateImmediately) {
+ addOnAccountsUpdatedListener(listener, handler,updateImmediately, null);
+ }
+
+ /**
+ * Adds an {@link OnAccountsUpdateListener} to this instance of the {@link AccountManager}. This
+ * listener will be notified whenever user or AbstractAccountAuthenticator made changes to
+ * accounts of given types related to the caller -
+ * either list of accounts returned by {@link #getAccounts()}
+ * was changed, or new account was added for which user can grant access to the caller.
+ * <p>
+ * As long as this listener is present, the AccountManager instance will not be
+ * garbage-collected, and neither will the {@link Context} used to retrieve it, which may be a
+ * large Activity instance. To avoid memory leaks, you must remove this listener before then.
+ * Normally listeners are added in an Activity or Service's {@link Activity#onCreate} and
+ * removed in {@link Activity#onDestroy}.
+ * <p>
+ * It is safe to call this method from the main thread.
+ *
+ * @param listener The listener to send notifications to
+ * @param handler {@link Handler} identifying the thread to use for notifications, null for the
+ * main thread
+ * @param updateImmediately If true, the listener will be invoked (on the handler thread) right
+ * away with the current account list
+ * @param accountTypes If set, only changes to accounts of given types will be reported.
+ * @throws IllegalArgumentException if listener is null
+ * @throws IllegalStateException if listener was already added
+ */
+ public void addOnAccountsUpdatedListener(final OnAccountsUpdateListener listener,
+ Handler handler, boolean updateImmediately, String[] accountTypes) {
+ if (listener == null) {
+ throw new IllegalArgumentException("the listener is null");
+ }
+ synchronized (mAccountsUpdatedListeners) {
+ if (mAccountsUpdatedListeners.containsKey(listener)) {
+ throw new IllegalStateException("this listener is already added");
+ }
+ final boolean wasEmpty = mAccountsUpdatedListeners.isEmpty();
+
+ mAccountsUpdatedListeners.put(listener, handler);
+ if (accountTypes != null) {
+ mAccountsUpdatedListenersTypes.put(listener,
+ new HashSet<String>(Arrays.asList(accountTypes)));
+ } else {
+ mAccountsUpdatedListenersTypes.put(listener, null);
+ }
+
+ if (wasEmpty) {
+ // Register a broadcast receiver to monitor account changes
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(ACTION_VISIBLE_ACCOUNTS_CHANGED);
+ // To recover from disk-full.
+ intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
+ mContext.registerReceiver(mAccountsChangedBroadcastReceiver, intentFilter);
+ }
+
+ try {
+ // Notify AccountManagedService about new receiver.
+ // The receiver must be unregistered later exactly one time
+ mService.registerAccountListener(accountTypes, mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ if (updateImmediately) {
+ postToHandler(handler, listener, getAccounts());
+ }
+ }
+
+ /**
+ * Removes an {@link OnAccountsUpdateListener} previously registered with
+ * {@link #addOnAccountsUpdatedListener}. The listener will no longer
+ * receive notifications of account changes.
+ *
+ * <p>It is safe to call this method from the main thread.
+ *
+ * <p>No permission is required to call this method.
+ *
+ * @param listener The previously added listener to remove
+ * @throws IllegalArgumentException if listener is null
+ * @throws IllegalStateException if listener was not already added
+ */
+ public void removeOnAccountsUpdatedListener(OnAccountsUpdateListener listener) {
+ if (listener == null) throw new IllegalArgumentException("listener is null");
+ synchronized (mAccountsUpdatedListeners) {
+ if (!mAccountsUpdatedListeners.containsKey(listener)) {
+ Log.e(TAG, "Listener was not previously added");
+ return;
+ }
+ Set<String> accountTypes = mAccountsUpdatedListenersTypes.get(listener);
+ String[] accountsArray;
+ if (accountTypes != null) {
+ accountsArray = accountTypes.toArray(new String[accountTypes.size()]);
+ } else {
+ accountsArray = null;
+ }
+ mAccountsUpdatedListeners.remove(listener);
+ mAccountsUpdatedListenersTypes.remove(listener);
+ if (mAccountsUpdatedListeners.isEmpty()) {
+ mContext.unregisterReceiver(mAccountsChangedBroadcastReceiver);
+ }
+ try {
+ mService.unregisterAccountListener(accountsArray, mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Asks the user to authenticate with an account of a specified type. The
+ * authenticator for this account type processes this request with the
+ * appropriate user interface. If the user does elect to authenticate with a
+ * new account, a bundle of session data for installing the account later is
+ * returned with optional account password and account status token.
+ * <p>
+ * This method may be called from any thread, but the returned
+ * {@link AccountManagerFuture} must not be used on the main thread.
+ * <p>
+ * <p>
+ * <b>NOTE:</b> The account will not be installed to the device by calling
+ * this api alone. #finishSession should be called after this to install the
+ * account on device.
+ *
+ * @param accountType The type of account to add; must not be null
+ * @param authTokenType The type of auth token (see {@link #getAuthToken})
+ * this account will need to be able to generate, null for none
+ * @param requiredFeatures The features (see {@link #hasFeatures}) this
+ * account must have, null for none
+ * @param options Authenticator-specific options for the request, may be
+ * null or empty
+ * @param activity The {@link Activity} context to use for launching a new
+ * authenticator-defined sub-Activity to prompt the user to
+ * create an account; used only to call startActivity(); if null,
+ * the prompt will not be launched directly, but the necessary
+ * {@link Intent} will be returned to the caller instead
+ * @param callback Callback to invoke when the request completes, null for
+ * no callback
+ * @param handler {@link Handler} identifying the callback thread, null for
+ * the main thread
+ * @return An {@link AccountManagerFuture} which resolves to a Bundle with
+ * these fields if activity was specified and user was authenticated
+ * with an account:
+ * <ul>
+ * <li>{@link #KEY_ACCOUNT_SESSION_BUNDLE} - encrypted Bundle for
+ * adding the the to the device later.
+ * <li>{@link #KEY_ACCOUNT_STATUS_TOKEN} - optional, token to check
+ * status of the account
+ * </ul>
+ * If no activity was specified, the returned Bundle contains only
+ * {@link #KEY_INTENT} with the {@link Intent} needed to launch the
+ * actual account creation process. If authenticator doesn't support
+ * this method, the returned Bundle contains only
+ * {@link #KEY_ACCOUNT_SESSION_BUNDLE} with encrypted
+ * {@code options} needed to add account later. If an error
+ * occurred, {@link AccountManagerFuture#getResult()} throws:
+ * <ul>
+ * <li>{@link AuthenticatorException} if no authenticator was
+ * registered for this account type or the authenticator failed to
+ * respond
+ * <li>{@link OperationCanceledException} if the operation was
+ * canceled for any reason, including the user canceling the
+ * creation process or adding accounts (of this type) has been
+ * disabled by policy
+ * <li>{@link IOException} if the authenticator experienced an I/O
+ * problem creating a new account, usually because of network
+ * trouble
+ * </ul>
+ * @see #finishSession
+ */
+ public AccountManagerFuture<Bundle> startAddAccountSession(
+ final String accountType,
+ final String authTokenType,
+ final String[] requiredFeatures,
+ final Bundle options,
+ final Activity activity,
+ AccountManagerCallback<Bundle> callback,
+ Handler handler) {
+ if (accountType == null) throw new IllegalArgumentException("accountType is null");
+ final Bundle optionsIn = new Bundle();
+ if (options != null) {
+ optionsIn.putAll(options);
+ }
+ optionsIn.putString(KEY_ANDROID_PACKAGE_NAME, mContext.getPackageName());
+
+ return new AmsTask(activity, handler, callback) {
+ @Override
+ public void doWork() throws RemoteException {
+ mService.startAddAccountSession(
+ mResponse,
+ accountType,
+ authTokenType,
+ requiredFeatures,
+ activity != null,
+ optionsIn);
+ }
+ }.start();
+ }
+
+ /**
+ * Asks the user to enter a new password for the account but not updating the
+ * saved credentials for the account until {@link #finishSession} is called.
+ * <p>
+ * This method may be called from any thread, but the returned
+ * {@link AccountManagerFuture} must not be used on the main thread.
+ * <p>
+ * <b>NOTE:</b> The saved credentials for the account alone will not be
+ * updated by calling this API alone. #finishSession should be called after
+ * this to update local credentials
+ *
+ * @param account The account to update credentials for
+ * @param authTokenType The credentials entered must allow an auth token of
+ * this type to be created (but no actual auth token is
+ * returned); may be null
+ * @param options Authenticator-specific options for the request; may be
+ * null or empty
+ * @param activity The {@link Activity} context to use for launching a new
+ * authenticator-defined sub-Activity to prompt the user to enter
+ * a password; used only to call startActivity(); if null, the
+ * prompt will not be launched directly, but the necessary
+ * {@link Intent} will be returned to the caller instead
+ * @param callback Callback to invoke when the request completes, null for
+ * no callback
+ * @param handler {@link Handler} identifying the callback thread, null for
+ * the main thread
+ * @return An {@link AccountManagerFuture} which resolves to a Bundle with
+ * these fields if an activity was supplied and user was
+ * successfully re-authenticated to the account:
+ * <ul>
+ * <li>{@link #KEY_ACCOUNT_SESSION_BUNDLE} - encrypted Bundle for
+ * updating the local credentials on device later.
+ * <li>{@link #KEY_ACCOUNT_STATUS_TOKEN} - optional, token to check
+ * status of the account
+ * </ul>
+ * If no activity was specified, the returned Bundle contains
+ * {@link #KEY_INTENT} with the {@link Intent} needed to launch the
+ * password prompt. If an error occurred,
+ * {@link AccountManagerFuture#getResult()} throws:
+ * <ul>
+ * <li>{@link AuthenticatorException} if the authenticator failed to
+ * respond
+ * <li>{@link OperationCanceledException} if the operation was
+ * canceled for any reason, including the user canceling the
+ * password prompt
+ * <li>{@link IOException} if the authenticator experienced an I/O
+ * problem verifying the password, usually because of network
+ * trouble
+ * </ul>
+ * @see #finishSession
+ */
+ public AccountManagerFuture<Bundle> startUpdateCredentialsSession(
+ final Account account,
+ final String authTokenType,
+ final Bundle options,
+ final Activity activity,
+ final AccountManagerCallback<Bundle> callback,
+ final Handler handler) {
+ if (account == null) {
+ throw new IllegalArgumentException("account is null");
+ }
+
+ // Always include the calling package name. This just makes life easier
+ // down stream.
+ final Bundle optionsIn = new Bundle();
+ if (options != null) {
+ optionsIn.putAll(options);
+ }
+ optionsIn.putString(KEY_ANDROID_PACKAGE_NAME, mContext.getPackageName());
+
+ return new AmsTask(activity, handler, callback) {
+ @Override
+ public void doWork() throws RemoteException {
+ mService.startUpdateCredentialsSession(
+ mResponse,
+ account,
+ authTokenType,
+ activity != null,
+ optionsIn);
+ }
+ }.start();
+ }
+
+ /**
+ * Finishes the session started by {@link #startAddAccountSession} or
+ * {@link #startUpdateCredentialsSession}. This will either add the account
+ * to AccountManager or update the local credentials stored.
+ * <p>
+ * This method may be called from any thread, but the returned
+ * {@link AccountManagerFuture} must not be used on the main thread.
+ *
+ * @param sessionBundle a {@link Bundle} created by {@link #startAddAccountSession} or
+ * {@link #startUpdateCredentialsSession}
+ * @param activity The {@link Activity} context to use for launching a new
+ * authenticator-defined sub-Activity to prompt the user to
+ * create an account or reauthenticate existing account; used
+ * only to call startActivity(); if null, the prompt will not
+ * be launched directly, but the necessary {@link Intent} will
+ * be returned to the caller instead
+ * @param callback Callback to invoke when the request completes, null for
+ * no callback
+ * @param handler {@link Handler} identifying the callback thread, null for
+ * the main thread
+ * @return An {@link AccountManagerFuture} which resolves to a Bundle with
+ * these fields if an activity was supplied and an account was added
+ * to device or local credentials were updated::
+ * <ul>
+ * <li>{@link #KEY_ACCOUNT_NAME} - the name of the account created
+ * <li>{@link #KEY_ACCOUNT_TYPE} - the type of the account
+ * <li>{@link #KEY_ACCOUNT_STATUS_TOKEN} - optional, token to check
+ * status of the account
+ * </ul>
+ * If no activity was specified and additional information is needed
+ * from user, the returned Bundle may only contain
+ * {@link #KEY_INTENT} with the {@link Intent} needed to launch the
+ * actual account creation process. If an error occurred,
+ * {@link AccountManagerFuture#getResult()} throws:
+ * <ul>
+ * <li>{@link AuthenticatorException} if no authenticator was
+ * registered for this account type or the authenticator failed to
+ * respond
+ * <li>{@link OperationCanceledException} if the operation was
+ * canceled for any reason, including the user canceling the
+ * creation process or adding accounts (of this type) has been
+ * disabled by policy
+ * <li>{@link IOException} if the authenticator experienced an I/O
+ * problem creating a new account, usually because of network
+ * trouble
+ * </ul>
+ * @see #startAddAccountSession and #startUpdateCredentialsSession
+ */
+ @UserHandleAware
+ public AccountManagerFuture<Bundle> finishSession(
+ final Bundle sessionBundle,
+ final Activity activity,
+ AccountManagerCallback<Bundle> callback,
+ Handler handler) {
+ return finishSessionAsUser(
+ sessionBundle,
+ activity,
+ mContext.getUser(),
+ callback,
+ handler);
+ }
+
+ /**
+ * @see #finishSession
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(INTERACT_ACROSS_USERS_FULL)
+ public AccountManagerFuture<Bundle> finishSessionAsUser(
+ final Bundle sessionBundle,
+ final Activity activity,
+ final UserHandle userHandle,
+ AccountManagerCallback<Bundle> callback,
+ Handler handler) {
+ if (sessionBundle == null) {
+ throw new IllegalArgumentException("sessionBundle is null");
+ }
+
+ /* Add information required by add account flow */
+ final Bundle appInfo = new Bundle();
+ appInfo.putString(KEY_ANDROID_PACKAGE_NAME, mContext.getPackageName());
+
+ return new AmsTask(activity, handler, callback) {
+ @Override
+ public void doWork() throws RemoteException {
+ mService.finishSessionAsUser(
+ mResponse,
+ sessionBundle,
+ activity != null,
+ appInfo,
+ userHandle.getIdentifier());
+ }
+ }.start();
+ }
+
+ /**
+ * Checks whether {@link #updateCredentials} or {@link #startUpdateCredentialsSession} should be
+ * called with respect to the specified account.
+ * <p>
+ * This method may be called from any thread, but the returned {@link AccountManagerFuture} must
+ * not be used on the main thread.
+ *
+ * @param account The {@link Account} to be checked whether {@link #updateCredentials} or
+ * {@link #startUpdateCredentialsSession} should be called
+ * @param statusToken a String of token to check account staus
+ * @param callback Callback to invoke when the request completes, null for no callback
+ * @param handler {@link Handler} identifying the callback thread, null for the main thread
+ * @return An {@link AccountManagerFuture} which resolves to a Boolean, true if the credentials
+ * of the account should be updated.
+ */
+ public AccountManagerFuture<Boolean> isCredentialsUpdateSuggested(
+ final Account account,
+ final String statusToken,
+ AccountManagerCallback<Boolean> callback,
+ Handler handler) {
+ if (account == null) {
+ throw new IllegalArgumentException("account is null");
+ }
+
+ if (TextUtils.isEmpty(statusToken)) {
+ throw new IllegalArgumentException("status token is empty");
+ }
+
+ return new Future2Task<Boolean>(handler, callback) {
+ @Override
+ public void doWork() throws RemoteException {
+ mService.isCredentialsUpdateSuggested(
+ mResponse,
+ account,
+ statusToken);
+ }
+ @Override
+ public Boolean bundleToResult(Bundle bundle) throws AuthenticatorException {
+ if (!bundle.containsKey(KEY_BOOLEAN_RESULT)) {
+ throw new AuthenticatorException("no result in response");
+ }
+ return bundle.getBoolean(KEY_BOOLEAN_RESULT);
+ }
+ }.start();
+ }
+
+ /**
+ * Gets whether a given package under a user has access to an account.
+ * Can be called only from the system UID.
+ *
+ * @param account The account for which to check.
+ * @param packageName The package for which to check.
+ * @param userHandle The user for which to check.
+ * @return True if the package can access the account.
+ *
+ * @hide
+ */
+ public boolean hasAccountAccess(@NonNull Account account, @NonNull String packageName,
+ @NonNull UserHandle userHandle) {
+ try {
+ return mService.hasAccountAccess(account, packageName, userHandle);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Creates an intent to request access to a given account for a UID.
+ * The returned intent should be stated for a result where {@link
+ * Activity#RESULT_OK} result means access was granted whereas {@link
+ * Activity#RESULT_CANCELED} result means access wasn't granted. Can
+ * be called only from the system UID.
+ *
+ * @param account The account for which to request.
+ * @param packageName The package name which to request.
+ * @param userHandle The user for which to request.
+ * @return The intent to request account access or null if the package
+ * doesn't exist.
+ *
+ * @hide
+ */
+ public IntentSender createRequestAccountAccessIntentSenderAsUser(@NonNull Account account,
+ @NonNull String packageName, @NonNull UserHandle userHandle) {
+ try {
+ return mService.createRequestAccountAccessIntentSenderAsUser(account, packageName,
+ userHandle);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @hide
+ * Calling this will invalidate Local Accounts Data Cache which
+ * forces the next query in any process to recompute the cache
+ */
+ public static void invalidateLocalAccountsDataCaches() {
+ PropertyInvalidatedCache.invalidateCache(CACHE_KEY_ACCOUNTS_DATA_PROPERTY);
+ }
+
+ /**
+ * @hide
+ * Calling this will disable account data caching.
+ */
+ public void disableLocalAccountCaches() {
+ mAccountsForUserCache.disableLocal();
+ }
+
+ /**
+ * @hide
+ * Calling this will invalidate Local Account User Data Cache which
+ * forces the next query in any process to recompute the cache
+ */
+ public static void invalidateLocalAccountUserDataCaches() {
+ PropertyInvalidatedCache.invalidateCache(CACHE_KEY_USER_DATA_PROPERTY);
+ }
+
+ /**
+ * @hide
+ * Calling this will disable user info caching.
+ */
+ public void disableLocalUserInfoCaches() {
+ mUserDataCache.disableLocal();
+ }
+}
diff --git a/android-35/android/accounts/AccountManagerCallback.java b/android-35/android/accounts/AccountManagerCallback.java
new file mode 100644
index 0000000..4aa7169
--- /dev/null
+++ b/android-35/android/accounts/AccountManagerCallback.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2009 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.accounts;
+
+public interface AccountManagerCallback<V> {
+ void run(AccountManagerFuture<V> future);
+}
\ No newline at end of file
diff --git a/android-35/android/accounts/AccountManagerFuture.java b/android-35/android/accounts/AccountManagerFuture.java
new file mode 100644
index 0000000..77670d9
--- /dev/null
+++ b/android-35/android/accounts/AccountManagerFuture.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2009 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.accounts;
+
+import java.util.concurrent.TimeUnit;
+import java.io.IOException;
+
+/**
+ * A <tt>AccountManagerFuture</tt> represents the result of an asynchronous
+ * {@link AccountManager} call. Methods are provided to check if the computation is
+ * complete, to wait for its completion, and to retrieve the result of
+ * the computation. The result can only be retrieved using method
+ * <tt>get</tt> when the computation has completed, blocking if
+ * necessary until it is ready. Cancellation is performed by the
+ * <tt>cancel</tt> method. Additional methods are provided to
+ * determine if the task completed normally or was cancelled. Once a
+ * computation has completed, the computation cannot be cancelled.
+ * If you would like to use a <tt>Future</tt> for the sake
+ * of cancellability but not provide a usable result, you can
+ * declare types of the form <tt>Future<?></tt> and
+ * return <tt>null</tt> as a result of the underlying task.
+ */
+public interface AccountManagerFuture<V> {
+ /**
+ * Attempts to cancel execution of this task. This attempt will
+ * fail if the task has already completed, has already been cancelled,
+ * or could not be cancelled for some other reason. If successful,
+ * and this task has not started when <tt>cancel</tt> is called,
+ * this task should never run. If the task has already started,
+ * then the <tt>mayInterruptIfRunning</tt> parameter determines
+ * whether the thread executing this task should be interrupted in
+ * an attempt to stop the task.
+ *
+ * <p>After this method returns, subsequent calls to {@link #isDone} will
+ * always return <tt>true</tt>. Subsequent calls to {@link #isCancelled}
+ * will always return <tt>true</tt> if this method returned <tt>true</tt>.
+ *
+ * @param mayInterruptIfRunning <tt>true</tt> if the thread executing this
+ * task should be interrupted; otherwise, in-progress tasks are allowed
+ * to complete
+ * @return <tt>false</tt> if the task could not be cancelled,
+ * typically because it has already completed normally;
+ * <tt>true</tt> otherwise
+ */
+ boolean cancel(boolean mayInterruptIfRunning);
+
+ /**
+ * Returns <tt>true</tt> if this task was cancelled before it completed
+ * normally.
+ *
+ * @return <tt>true</tt> if this task was cancelled before it completed
+ */
+ boolean isCancelled();
+
+ /**
+ * Returns <tt>true</tt> if this task completed.
+ *
+ * Completion may be due to normal termination, an exception, or
+ * cancellation -- in all of these cases, this method will return
+ * <tt>true</tt>.
+ *
+ * @return <tt>true</tt> if this task completed
+ */
+ boolean isDone();
+
+ /**
+ * Accessor for the future result the {@link AccountManagerFuture} represents. This
+ * call will block until the result is available. In order to check if the result is
+ * available without blocking, one may call {@link #isDone()} and {@link #isCancelled()}.
+ * If the request that generated this result fails or is canceled then an exception
+ * will be thrown rather than the call returning normally.
+ * @return the actual result
+ * @throws android.accounts.OperationCanceledException if the request was canceled for any
+ * reason (including if it is forbidden
+ * by policy to modify an account (of that type))
+ * @throws android.accounts.AuthenticatorException if there was an error communicating with
+ * the authenticator or if the authenticator returned an invalid response
+ * @throws java.io.IOException if the authenticator returned an error response that indicates
+ * that it encountered an IOException while communicating with the authentication server
+ */
+ V getResult() throws OperationCanceledException, IOException, AuthenticatorException;
+
+ /**
+ * Accessor for the future result the {@link AccountManagerFuture} represents. This
+ * call will block until the result is available. In order to check if the result is
+ * available without blocking, one may call {@link #isDone()} and {@link #isCancelled()}.
+ * If the request that generated this result fails or is canceled then an exception
+ * will be thrown rather than the call returning normally. If a timeout is specified then
+ * the request will automatically be canceled if it does not complete in that amount of time.
+ * @param timeout the maximum time to wait
+ * @param unit the time unit of the timeout argument. This must not be null.
+ * @return the actual result
+ * @throws android.accounts.OperationCanceledException if the request was canceled for any
+ * reason
+ * @throws android.accounts.AuthenticatorException if there was an error communicating with
+ * the authenticator or if the authenticator returned an invalid response
+ * @throws java.io.IOException if the authenticator returned an error response that indicates
+ * that it encountered an IOException while communicating with the authentication server
+ */
+ V getResult(long timeout, TimeUnit unit)
+ throws OperationCanceledException, IOException, AuthenticatorException;
+}
\ No newline at end of file
diff --git a/android-35/android/accounts/AccountManagerInternal.java b/android-35/android/accounts/AccountManagerInternal.java
new file mode 100644
index 0000000..68c17c3
--- /dev/null
+++ b/android-35/android/accounts/AccountManagerInternal.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2016 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.accounts;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.os.RemoteCallback;
+
+/**
+ * Account manager local system service interface.
+ *
+ * @hide Only for use within the system server.
+ */
+public abstract class AccountManagerInternal {
+
+ /**
+ * Listener for explicit UID account access grant changes.
+ */
+ public interface OnAppPermissionChangeListener {
+
+ /**
+ * Called when the explicit grant state for a given UID to
+ * access an account changes.
+ *
+ * @param account The account
+ * @param uid The UID for which the grant changed
+ */
+ public void onAppPermissionChanged(Account account, int uid);
+ }
+
+ /**
+ * Requests that a given package is given access to an account.
+ * The provided callback will be invoked with a {@link android.os.Bundle}
+ * containing the result which will be a boolean value mapped to the
+ * {@link AccountManager#KEY_BOOLEAN_RESULT} key.
+ *
+ * @param account The account for which to request.
+ * @param packageName The package name for which to request.
+ * @param userId Concrete user id for which to request.
+ * @param callback A callback for receiving the result.
+ */
+ public abstract void requestAccountAccess(@NonNull Account account,
+ @NonNull String packageName, @IntRange(from = 0) int userId,
+ @NonNull RemoteCallback callback);
+
+ /**
+ * Check whether the given UID has access to the account.
+ *
+ * @param account The account
+ * @param uid The UID
+ * @return Whether the UID can access the account
+ */
+ public abstract boolean hasAccountAccess(@NonNull Account account, @IntRange(from = 0) int uid);
+
+ /**
+ * Adds a listener for explicit UID account access grant changes.
+ *
+ * @param listener The listener
+ */
+ public abstract void addOnAppPermissionChangeListener(
+ @NonNull OnAppPermissionChangeListener listener);
+
+ /**
+ * Backups the account access permissions.
+ * @param userId The user for which to backup.
+ * @return The backup data.
+ */
+ public abstract byte[] backupAccountAccessPermissions(int userId);
+
+ /**
+ * Restores the account access permissions.
+ * @param data The restore data.
+ * @param userId The user for which to restore.
+ */
+ public abstract void restoreAccountAccessPermissions(byte[] data, int userId);
+}
diff --git a/android-35/android/accounts/AccountManagerResponse.java b/android-35/android/accounts/AccountManagerResponse.java
new file mode 100644
index 0000000..369a7c3
--- /dev/null
+++ b/android-35/android/accounts/AccountManagerResponse.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2009 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.accounts;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+
+/**
+ * Used to return a response to the AccountManager.
+ * @hide
+ */
+public class AccountManagerResponse implements Parcelable {
+ private IAccountManagerResponse mResponse;
+
+ /** @hide */
+ public AccountManagerResponse(IAccountManagerResponse response) {
+ mResponse = response;
+ }
+
+ /** @hide */
+ public AccountManagerResponse(Parcel parcel) {
+ mResponse =
+ IAccountManagerResponse.Stub.asInterface(parcel.readStrongBinder());
+ }
+
+ public void onResult(Bundle result) {
+ try {
+ mResponse.onResult(result);
+ } catch (RemoteException e) {
+ // this should never happen
+ }
+ }
+
+ public void onError(int errorCode, String errorMessage) {
+ try {
+ mResponse.onError(errorCode, errorMessage);
+ } catch (RemoteException e) {
+ // this should never happen
+ }
+ }
+
+ /** @hide */
+ public int describeContents() {
+ return 0;
+ }
+
+ /** @hide */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeStrongBinder(mResponse.asBinder());
+ }
+
+ /** @hide */
+ public static final @android.annotation.NonNull Creator<AccountManagerResponse> CREATOR =
+ new Creator<AccountManagerResponse>() {
+ public AccountManagerResponse createFromParcel(Parcel source) {
+ return new AccountManagerResponse(source);
+ }
+
+ public AccountManagerResponse[] newArray(int size) {
+ return new AccountManagerResponse[size];
+ }
+ };
+}
diff --git a/android-35/android/accounts/AccountsException.java b/android-35/android/accounts/AccountsException.java
new file mode 100644
index 0000000..b997390
--- /dev/null
+++ b/android-35/android/accounts/AccountsException.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2009 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.accounts;
+
+public class AccountsException extends Exception {
+ public AccountsException() {
+ super();
+ }
+ public AccountsException(String message) {
+ super(message);
+ }
+ public AccountsException(String message, Throwable cause) {
+ super(message, cause);
+ }
+ public AccountsException(Throwable cause) {
+ super(cause);
+ }
+}
\ No newline at end of file
diff --git a/android-35/android/accounts/AuthenticatorDescription.java b/android-35/android/accounts/AuthenticatorDescription.java
new file mode 100644
index 0000000..4be3538
--- /dev/null
+++ b/android-35/android/accounts/AuthenticatorDescription.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2009 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.accounts;
+
+import android.annotation.Nullable;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A {@link Parcelable} value type that contains information about an account authenticator.
+ */
+public class AuthenticatorDescription implements Parcelable {
+ /** The string that uniquely identifies an authenticator */
+ final public String type;
+
+ /** A resource id of a label for the authenticator that is suitable for displaying */
+ final public int labelId;
+
+ /** A resource id of a icon for the authenticator */
+ final public int iconId;
+
+ /** A resource id of a smaller icon for the authenticator */
+ final public int smallIconId;
+
+ /**
+ * A resource id for a hierarchy of PreferenceScreen to be added to the settings page for the
+ * account. See {@link AbstractAccountAuthenticator} for an example.
+ */
+ final public int accountPreferencesId;
+
+ /** The package name that can be used to lookup the resources from above. */
+ final public String packageName;
+
+ /** Authenticator handles its own token caching and permission screen */
+ final public boolean customTokens;
+
+ /** A constructor for a full AuthenticatorDescription */
+ public AuthenticatorDescription(String type, String packageName, int labelId, int iconId,
+ int smallIconId, int prefId, boolean customTokens) {
+ if (type == null) throw new IllegalArgumentException("type cannot be null");
+ if (packageName == null) throw new IllegalArgumentException("packageName cannot be null");
+ this.type = type;
+ this.packageName = packageName;
+ this.labelId = labelId;
+ this.iconId = iconId;
+ this.smallIconId = smallIconId;
+ this.accountPreferencesId = prefId;
+ this.customTokens = customTokens;
+ }
+
+ public AuthenticatorDescription(String type, String packageName, int labelId, int iconId,
+ int smallIconId, int prefId) {
+ this(type, packageName, labelId, iconId, smallIconId, prefId, false);
+ }
+
+ /**
+ * A factory method for creating an AuthenticatorDescription that can be used as a key
+ * to identify the authenticator by its type.
+ */
+
+ public static AuthenticatorDescription newKey(String type) {
+ if (type == null) throw new IllegalArgumentException("type cannot be null");
+ return new AuthenticatorDescription(type);
+ }
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ private AuthenticatorDescription(String type) {
+ this.type = type;
+ this.packageName = null;
+ this.labelId = 0;
+ this.iconId = 0;
+ this.smallIconId = 0;
+ this.accountPreferencesId = 0;
+ this.customTokens = false;
+ }
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ private AuthenticatorDescription(Parcel source) {
+ this.type = source.readString();
+ this.packageName = source.readString();
+ this.labelId = source.readInt();
+ this.iconId = source.readInt();
+ this.smallIconId = source.readInt();
+ this.accountPreferencesId = source.readInt();
+ this.customTokens = source.readByte() == 1;
+ }
+
+ /** @inheritDoc */
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Returns the hashcode of the type only. */
+ public int hashCode() {
+ return type.hashCode();
+ }
+
+ /** Compares the type only, suitable for key comparisons. */
+ public boolean equals(@Nullable Object o) {
+ if (o == this) return true;
+ if (!(o instanceof AuthenticatorDescription)) return false;
+ final AuthenticatorDescription other = (AuthenticatorDescription) o;
+ return type.equals(other.type);
+ }
+
+ public String toString() {
+ return "AuthenticatorDescription {type=" + type + "}";
+ }
+
+ /** @inheritDoc */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(type);
+ dest.writeString(packageName);
+ dest.writeInt(labelId);
+ dest.writeInt(iconId);
+ dest.writeInt(smallIconId);
+ dest.writeInt(accountPreferencesId);
+ dest.writeByte((byte) (customTokens ? 1 : 0));
+ }
+
+ /** Used to create the object from a parcel. */
+ public static final @android.annotation.NonNull Creator<AuthenticatorDescription> CREATOR =
+ new Creator<AuthenticatorDescription>() {
+ /** @inheritDoc */
+ public AuthenticatorDescription createFromParcel(Parcel source) {
+ return new AuthenticatorDescription(source);
+ }
+
+ /** @inheritDoc */
+ public AuthenticatorDescription[] newArray(int size) {
+ return new AuthenticatorDescription[size];
+ }
+ };
+}
diff --git a/android-35/android/accounts/AuthenticatorException.java b/android-35/android/accounts/AuthenticatorException.java
new file mode 100644
index 0000000..f778d7d
--- /dev/null
+++ b/android-35/android/accounts/AuthenticatorException.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2009 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.accounts;
+
+public class AuthenticatorException extends AccountsException {
+ public AuthenticatorException() {
+ super();
+ }
+ public AuthenticatorException(String message) {
+ super(message);
+ }
+ public AuthenticatorException(String message, Throwable cause) {
+ super(message, cause);
+ }
+ public AuthenticatorException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/android-35/android/accounts/CantAddAccountActivity.java b/android-35/android/accounts/CantAddAccountActivity.java
new file mode 100644
index 0000000..3fac1a0
--- /dev/null
+++ b/android-35/android/accounts/CantAddAccountActivity.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2013 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.accounts;
+
+
+import static android.app.admin.DevicePolicyResources.Strings.Core.CANT_ADD_ACCOUNT_MESSAGE;
+
+import android.app.Activity;
+import android.app.admin.DevicePolicyManager;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.TextView;
+
+import com.android.internal.R;
+
+/**
+ * @hide
+ * Just shows an error message about the account restrictions for the limited user.
+ */
+public class CantAddAccountActivity extends Activity {
+ public static final String EXTRA_ERROR_CODE = "android.accounts.extra.ERROR_CODE";
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.app_not_authorized);
+
+ TextView view = findViewById(R.id.description);
+ String text = getSystemService(DevicePolicyManager.class).getResources().getString(
+ CANT_ADD_ACCOUNT_MESSAGE,
+ () -> getString(R.string.error_message_change_not_allowed));
+ view.setText(text);
+ }
+
+ public void onCancelButtonClicked(View view) {
+ onBackPressed();
+ }
+}
diff --git a/android-35/android/accounts/ChooseAccountActivity.java b/android-35/android/accounts/ChooseAccountActivity.java
new file mode 100644
index 0000000..20142a6
--- /dev/null
+++ b/android-35/android/accounts/ChooseAccountActivity.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2009 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.accounts;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.os.Process;
+import android.os.UserHandle;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import com.android.internal.R;
+
+import java.util.HashMap;
+
+/**
+ * @hide
+ */
+public class ChooseAccountActivity extends Activity {
+
+ private static final String TAG = "AccountManager";
+
+ private Parcelable[] mAccounts = null;
+ private AccountManagerResponse mAccountManagerResponse = null;
+ private Bundle mResult;
+ private int mCallingUid;
+ private String mCallingPackage;
+
+ private HashMap<String, AuthenticatorDescription> mTypeToAuthDescription
+ = new HashMap<String, AuthenticatorDescription>();
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ getWindow().addSystemFlags(
+ android.view.WindowManager.LayoutParams
+ .SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
+ mAccounts = getIntent().getParcelableArrayExtra(AccountManager.KEY_ACCOUNTS);
+ mAccountManagerResponse =
+ getIntent().getParcelableExtra(AccountManager.KEY_ACCOUNT_MANAGER_RESPONSE, android.accounts.AccountManagerResponse.class);
+
+ // KEY_ACCOUNTS is a required parameter
+ if (mAccounts == null) {
+ setResult(RESULT_CANCELED);
+ finish();
+ return;
+ }
+
+ mCallingUid = getLaunchedFromUid();
+ mCallingPackage = getLaunchedFromPackage();
+
+ if (UserHandle.isSameApp(mCallingUid, Process.SYSTEM_UID) &&
+ getIntent().getStringExtra(AccountManager.KEY_ANDROID_PACKAGE_NAME) != null) {
+ mCallingPackage = getIntent().getStringExtra(
+ AccountManager.KEY_ANDROID_PACKAGE_NAME);
+ }
+
+ if (!UserHandle.isSameApp(mCallingUid, Process.SYSTEM_UID) &&
+ getIntent().getStringExtra(AccountManager.KEY_ANDROID_PACKAGE_NAME) != null) {
+ Log.w(getClass().getSimpleName(),
+ "Non-system Uid: " + mCallingUid + " tried to override packageName \n");
+ }
+
+ getAuthDescriptions();
+
+ AccountInfo[] mAccountInfos = new AccountInfo[mAccounts.length];
+ for (int i = 0; i < mAccounts.length; i++) {
+ mAccountInfos[i] = new AccountInfo(((Account) mAccounts[i]).name,
+ getDrawableForType(((Account) mAccounts[i]).type));
+ }
+
+ setContentView(R.layout.choose_account);
+
+ // Setup the list
+ ListView list = findViewById(android.R.id.list);
+ // Use an existing ListAdapter that will map an array of strings to TextViews
+ list.setAdapter(new AccountArrayAdapter(this,
+ android.R.layout.simple_list_item_1, mAccountInfos));
+ list.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
+ list.setTextFilterEnabled(true);
+ list.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+ public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
+ onListItemClick((ListView)parent, v, position, id);
+ }
+ });
+ }
+
+ private void getAuthDescriptions() {
+ for(AuthenticatorDescription desc : AccountManager.get(this).getAuthenticatorTypes()) {
+ mTypeToAuthDescription.put(desc.type, desc);
+ }
+ }
+
+ private Drawable getDrawableForType(String accountType) {
+ Drawable icon = null;
+ if(mTypeToAuthDescription.containsKey(accountType)) {
+ try {
+ AuthenticatorDescription desc = mTypeToAuthDescription.get(accountType);
+ Context authContext = createPackageContext(desc.packageName, 0);
+ icon = authContext.getDrawable(desc.iconId);
+ } catch (PackageManager.NameNotFoundException e) {
+ // Nothing we can do much here, just log
+ if (Log.isLoggable(TAG, Log.WARN)) {
+ Log.w(TAG, "No icon name for account type " + accountType);
+ }
+ } catch (Resources.NotFoundException e) {
+ // Nothing we can do much here, just log
+ if (Log.isLoggable(TAG, Log.WARN)) {
+ Log.w(TAG, "No icon resource for account type " + accountType);
+ }
+ }
+ }
+ return icon;
+ }
+
+ protected void onListItemClick(ListView l, View v, int position, long id) {
+ Account account = (Account) mAccounts[position];
+ // Mark account as visible since user chose it.
+ AccountManager am = AccountManager.get(this);
+ Integer oldVisibility = am.getAccountVisibility(account, mCallingPackage);
+ if (oldVisibility != null
+ && oldVisibility == AccountManager.VISIBILITY_USER_MANAGED_NOT_VISIBLE) {
+ am.setAccountVisibility(account, mCallingPackage,
+ AccountManager.VISIBILITY_USER_MANAGED_VISIBLE);
+ }
+ Log.d(TAG, "selected account " + account.toSafeString());
+ Bundle bundle = new Bundle();
+ bundle.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
+ bundle.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type);
+ mResult = bundle;
+ finish();
+ }
+
+ public void finish() {
+ if (mAccountManagerResponse != null) {
+ if (mResult != null) {
+ mAccountManagerResponse.onResult(mResult);
+ } else {
+ mAccountManagerResponse.onError(AccountManager.ERROR_CODE_CANCELED, "canceled");
+ }
+ }
+ super.finish();
+ }
+
+ private static class AccountInfo {
+ final String name;
+ final Drawable drawable;
+
+ AccountInfo(String name, Drawable drawable) {
+ this.name = name;
+ this.drawable = drawable;
+ }
+ }
+
+ private static class ViewHolder {
+ ImageView icon;
+ TextView text;
+ }
+
+ private static class AccountArrayAdapter extends ArrayAdapter<AccountInfo> {
+ private LayoutInflater mLayoutInflater;
+ private AccountInfo[] mInfos;
+
+ public AccountArrayAdapter(Context context, int textViewResourceId, AccountInfo[] infos) {
+ super(context, textViewResourceId, infos);
+ mInfos = infos;
+ mLayoutInflater = (LayoutInflater) context.getSystemService(
+ Context.LAYOUT_INFLATER_SERVICE);
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ ViewHolder holder;
+
+ if (convertView == null) {
+ convertView = mLayoutInflater.inflate(R.layout.choose_account_row, null);
+ holder = new ViewHolder();
+ holder.text = (TextView) convertView.findViewById(R.id.account_row_text);
+ holder.icon = (ImageView) convertView.findViewById(R.id.account_row_icon);
+ convertView.setTag(holder);
+ } else {
+ holder = (ViewHolder) convertView.getTag();
+ }
+
+ holder.text.setText(mInfos[position].name);
+ holder.icon.setImageDrawable(mInfos[position].drawable);
+
+ return convertView;
+ }
+ }
+}
diff --git a/android-35/android/accounts/ChooseAccountTypeActivity.java b/android-35/android/accounts/ChooseAccountTypeActivity.java
new file mode 100644
index 0000000..983dcd8
--- /dev/null
+++ b/android-35/android/accounts/ChooseAccountTypeActivity.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2011 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.accounts;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import com.android.internal.R;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @hide
+ */
+public class ChooseAccountTypeActivity extends Activity {
+ private static final String TAG = "AccountChooser";
+
+ private HashMap<String, AuthInfo> mTypeToAuthenticatorInfo = new HashMap<String, AuthInfo>();
+ private ArrayList<AuthInfo> mAuthenticatorInfosToDisplay;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ getWindow().addSystemFlags(
+ android.view.WindowManager.LayoutParams
+ .SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
+
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "ChooseAccountTypeActivity.onCreate(savedInstanceState="
+ + savedInstanceState + ")");
+ }
+
+ // Read the validAccountTypes, if present, and add them to the setOfAllowableAccountTypes
+ Set<String> setOfAllowableAccountTypes = null;
+ String[] validAccountTypes = getIntent().getStringArrayExtra(
+ ChooseTypeAndAccountActivity.EXTRA_ALLOWABLE_ACCOUNT_TYPES_STRING_ARRAY);
+ if (validAccountTypes != null) {
+ setOfAllowableAccountTypes = new HashSet<String>(validAccountTypes.length);
+ for (String type : validAccountTypes) {
+ setOfAllowableAccountTypes.add(type);
+ }
+ }
+
+ // create a map of account authenticators
+ buildTypeToAuthDescriptionMap();
+
+ // Create a list of authenticators that are allowable. Filter out those that
+ // don't match the allowable account types, if provided.
+ mAuthenticatorInfosToDisplay = new ArrayList<AuthInfo>(mTypeToAuthenticatorInfo.size());
+ for (Map.Entry<String, AuthInfo> entry: mTypeToAuthenticatorInfo.entrySet()) {
+ final String type = entry.getKey();
+ final AuthInfo info = entry.getValue();
+ if (setOfAllowableAccountTypes != null
+ && !setOfAllowableAccountTypes.contains(type)) {
+ continue;
+ }
+ mAuthenticatorInfosToDisplay.add(info);
+ }
+
+ if (mAuthenticatorInfosToDisplay.isEmpty()) {
+ Bundle bundle = new Bundle();
+ bundle.putString(AccountManager.KEY_ERROR_MESSAGE, "no allowable account types");
+ setResult(Activity.RESULT_OK, new Intent().putExtras(bundle));
+ finish();
+ return;
+ }
+
+ if (mAuthenticatorInfosToDisplay.size() == 1) {
+ setResultAndFinish(mAuthenticatorInfosToDisplay.get(0).desc.type);
+ return;
+ }
+
+ setContentView(R.layout.choose_account_type);
+ // Setup the list
+ ListView list = findViewById(android.R.id.list);
+ // Use an existing ListAdapter that will map an array of strings to TextViews
+ list.setAdapter(new AccountArrayAdapter(this,
+ android.R.layout.simple_list_item_1, mAuthenticatorInfosToDisplay));
+ list.setChoiceMode(ListView.CHOICE_MODE_NONE);
+ list.setTextFilterEnabled(false);
+ list.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+ public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
+ setResultAndFinish(mAuthenticatorInfosToDisplay.get(position).desc.type);
+ }
+ });
+ }
+
+ private void setResultAndFinish(final String type) {
+ Bundle bundle = new Bundle();
+ bundle.putString(AccountManager.KEY_ACCOUNT_TYPE, type);
+ setResult(Activity.RESULT_OK, new Intent().putExtras(bundle));
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "ChooseAccountTypeActivity.setResultAndFinish: "
+ + "selected account type " + type);
+ }
+ finish();
+ }
+
+ private void buildTypeToAuthDescriptionMap() {
+ for(AuthenticatorDescription desc : AccountManager.get(this).getAuthenticatorTypes()) {
+ String name = null;
+ Drawable icon = null;
+ try {
+ Context authContext = createPackageContext(desc.packageName, 0);
+ icon = authContext.getDrawable(desc.iconId);
+ final CharSequence sequence = authContext.getResources().getText(desc.labelId);
+ if (sequence != null) {
+ name = sequence.toString();
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ // Nothing we can do much here, just log
+ if (Log.isLoggable(TAG, Log.WARN)) {
+ Log.w(TAG, "No icon name for account type " + desc.type);
+ }
+ } catch (Resources.NotFoundException e) {
+ // Nothing we can do much here, just log
+ if (Log.isLoggable(TAG, Log.WARN)) {
+ Log.w(TAG, "No icon resource for account type " + desc.type);
+ }
+ }
+ AuthInfo authInfo = new AuthInfo(desc, name, icon);
+ mTypeToAuthenticatorInfo.put(desc.type, authInfo);
+ }
+ }
+
+ private static class AuthInfo {
+ final AuthenticatorDescription desc;
+ final String name;
+ final Drawable drawable;
+
+ AuthInfo(AuthenticatorDescription desc, String name, Drawable drawable) {
+ this.desc = desc;
+ this.name = name;
+ this.drawable = drawable;
+ }
+ }
+
+ private static class ViewHolder {
+ ImageView icon;
+ TextView text;
+ }
+
+ private static class AccountArrayAdapter extends ArrayAdapter<AuthInfo> {
+ private LayoutInflater mLayoutInflater;
+ private ArrayList<AuthInfo> mInfos;
+
+ public AccountArrayAdapter(Context context, int textViewResourceId,
+ ArrayList<AuthInfo> infos) {
+ super(context, textViewResourceId, infos);
+ mInfos = infos;
+ mLayoutInflater = (LayoutInflater) context.getSystemService(
+ Context.LAYOUT_INFLATER_SERVICE);
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ ViewHolder holder;
+
+ if (convertView == null) {
+ convertView = mLayoutInflater.inflate(R.layout.choose_account_row, null);
+ holder = new ViewHolder();
+ holder.text = (TextView) convertView.findViewById(R.id.account_row_text);
+ holder.icon = (ImageView) convertView.findViewById(R.id.account_row_icon);
+ convertView.setTag(holder);
+ } else {
+ holder = (ViewHolder) convertView.getTag();
+ }
+
+ holder.text.setText(mInfos.get(position).name);
+ holder.icon.setImageDrawable(mInfos.get(position).drawable);
+
+ return convertView;
+ }
+ }
+}
diff --git a/android-35/android/accounts/ChooseTypeAndAccountActivity.java b/android-35/android/accounts/ChooseTypeAndAccountActivity.java
new file mode 100644
index 0000000..e447d86
--- /dev/null
+++ b/android-35/android/accounts/ChooseTypeAndAccountActivity.java
@@ -0,0 +1,624 @@
+/*
+ * Copyright (C) 2011 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.accounts;
+
+import static android.app.admin.DevicePolicyResources.Strings.Core.CANT_ADD_ACCOUNT_MESSAGE;
+
+import android.app.Activity;
+import android.app.admin.DevicePolicyManager;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.View;
+import android.view.Window;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import com.android.internal.R;
+
+import com.google.android.collect.Sets;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @hide
+ */
+public class ChooseTypeAndAccountActivity extends Activity
+ implements AccountManagerCallback<Bundle> {
+ private static final String TAG = "AccountChooser";
+
+ /**
+ * A Parcelable ArrayList of Account objects that limits the choosable accounts to those
+ * in this list, if this parameter is supplied.
+ */
+ public static final String EXTRA_ALLOWABLE_ACCOUNTS_ARRAYLIST = "allowableAccounts";
+
+ /**
+ * A Parcelable ArrayList of String objects that limits the accounts to choose to those
+ * that match the types in this list, if this parameter is supplied. This list is also
+ * used to filter the allowable account types if add account is selected.
+ */
+ public static final String EXTRA_ALLOWABLE_ACCOUNT_TYPES_STRING_ARRAY = "allowableAccountTypes";
+
+ /**
+ * This is passed as the addAccountOptions parameter in AccountManager.addAccount()
+ * if it is called.
+ */
+ public static final String EXTRA_ADD_ACCOUNT_OPTIONS_BUNDLE = "addAccountOptions";
+
+ /**
+ * This is passed as the requiredFeatures parameter in AccountManager.addAccount()
+ * if it is called.
+ */
+ public static final String EXTRA_ADD_ACCOUNT_REQUIRED_FEATURES_STRING_ARRAY =
+ "addAccountRequiredFeatures";
+
+ /**
+ * This is passed as the authTokenType string in AccountManager.addAccount()
+ * if it is called.
+ */
+ public static final String EXTRA_ADD_ACCOUNT_AUTH_TOKEN_TYPE_STRING = "authTokenType";
+
+ /**
+ * If set then the specified account is already "selected".
+ */
+ public static final String EXTRA_SELECTED_ACCOUNT = "selectedAccount";
+
+ /**
+ * Deprecated. Providing this extra to {@link ChooseTypeAndAccountActivity}
+ * will have no effect.
+ */
+ @Deprecated
+ public static final String EXTRA_ALWAYS_PROMPT_FOR_ACCOUNT =
+ "alwaysPromptForAccount";
+
+ /**
+ * If set then this string will be used as the description rather than
+ * the default.
+ */
+ public static final String EXTRA_DESCRIPTION_TEXT_OVERRIDE = "descriptionTextOverride";
+
+ public static final int REQUEST_NULL = 0;
+ public static final int REQUEST_CHOOSE_TYPE = 1;
+ public static final int REQUEST_ADD_ACCOUNT = 2;
+
+ private static final String KEY_INSTANCE_STATE_PENDING_REQUEST = "pendingRequest";
+ private static final String KEY_INSTANCE_STATE_EXISTING_ACCOUNTS = "existingAccounts";
+ private static final String KEY_INSTANCE_STATE_SELECTED_ACCOUNT_NAME = "selectedAccountName";
+ private static final String KEY_INSTANCE_STATE_SELECTED_ADD_ACCOUNT = "selectedAddAccount";
+ private static final String KEY_INSTANCE_STATE_ACCOUNTS_LIST = "accountsList";
+ private static final String KEY_INSTANCE_STATE_VISIBILITY_LIST = "visibilityList";
+
+ private static final int SELECTED_ITEM_NONE = -1;
+
+ private Set<Account> mSetOfAllowableAccounts;
+ private Set<String> mSetOfRelevantAccountTypes;
+ private String mSelectedAccountName = null;
+ private boolean mSelectedAddNewAccount = false;
+ private String mDescriptionOverride;
+
+ private LinkedHashMap<Account, Integer> mAccounts;
+ // TODO Redesign flow to show NOT_VISIBLE accounts
+ // and display a warning if they are selected.
+ // Currently NOT_VISBILE accounts are not shown at all.
+ private ArrayList<Account> mPossiblyVisibleAccounts;
+ private int mPendingRequest = REQUEST_NULL;
+ private Parcelable[] mExistingAccounts = null;
+ private int mSelectedItemIndex;
+ private Button mOkButton;
+ private int mCallingUid;
+ private String mCallingPackage;
+ private boolean mDisallowAddAccounts;
+ private boolean mDontShowPicker;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "ChooseTypeAndAccountActivity.onCreate(savedInstanceState="
+ + savedInstanceState + ")");
+ }
+ getWindow().addSystemFlags(
+ android.view.WindowManager.LayoutParams
+ .SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
+
+ mCallingUid = getLaunchedFromUid();
+ mCallingPackage = getLaunchedFromPackage();
+ if (mCallingUid != 0 && mCallingPackage != null) {
+ Bundle restrictions = UserManager.get(this)
+ .getUserRestrictions(new UserHandle(UserHandle.getUserId(mCallingUid)));
+ mDisallowAddAccounts =
+ restrictions.getBoolean(UserManager.DISALLOW_MODIFY_ACCOUNTS, false);
+ }
+
+ // save some items we use frequently
+ final Intent intent = getIntent();
+
+ mSetOfAllowableAccounts = getAllowableAccountSet(intent);
+ mSetOfRelevantAccountTypes = getReleventAccountTypes(intent);
+ mDescriptionOverride = intent.getStringExtra(EXTRA_DESCRIPTION_TEXT_OVERRIDE);
+
+ if (savedInstanceState != null) {
+ mPendingRequest = savedInstanceState.getInt(KEY_INSTANCE_STATE_PENDING_REQUEST);
+ mExistingAccounts =
+ savedInstanceState.getParcelableArray(KEY_INSTANCE_STATE_EXISTING_ACCOUNTS);
+
+ // Makes sure that any user selection is preserved across orientation changes.
+ mSelectedAccountName =
+ savedInstanceState.getString(KEY_INSTANCE_STATE_SELECTED_ACCOUNT_NAME);
+ mSelectedAddNewAccount =
+ savedInstanceState.getBoolean(KEY_INSTANCE_STATE_SELECTED_ADD_ACCOUNT, false);
+ // restore mAccounts
+ Parcelable[] accounts =
+ savedInstanceState.getParcelableArray(KEY_INSTANCE_STATE_ACCOUNTS_LIST);
+ ArrayList<Integer> visibility =
+ savedInstanceState.getIntegerArrayList(KEY_INSTANCE_STATE_VISIBILITY_LIST);
+ mAccounts = new LinkedHashMap<>();
+ for (int i = 0; i < accounts.length; i++) {
+ mAccounts.put((Account) accounts[i], visibility.get(i));
+ }
+ } else {
+ mPendingRequest = REQUEST_NULL;
+ mExistingAccounts = null;
+ // If the selected account as specified in the intent matches one in the list we will
+ // show is as pre-selected.
+ Account selectedAccount = (Account) intent.getParcelableExtra(EXTRA_SELECTED_ACCOUNT, android.accounts.Account.class);
+ if (selectedAccount != null) {
+ mSelectedAccountName = selectedAccount.name;
+ }
+ mAccounts = getAcceptableAccountChoices(AccountManager.get(this));
+ }
+
+ mPossiblyVisibleAccounts = new ArrayList<>(mAccounts.size());
+ for (Map.Entry<Account, Integer> entry : mAccounts.entrySet()) {
+ if (AccountManager.VISIBILITY_NOT_VISIBLE != entry.getValue()) {
+ mPossiblyVisibleAccounts.add(entry.getKey());
+ }
+ }
+
+ if (mPossiblyVisibleAccounts.isEmpty() && mDisallowAddAccounts) {
+ requestWindowFeature(Window.FEATURE_NO_TITLE);
+
+ setContentView(R.layout.app_not_authorized);
+ TextView view = findViewById(R.id.description);
+ String text = getSystemService(DevicePolicyManager.class).getResources().getString(
+ CANT_ADD_ACCOUNT_MESSAGE,
+ () -> getString(R.string.error_message_change_not_allowed));
+ view.setText(text);
+
+ mDontShowPicker = true;
+ }
+
+ if (mDontShowPicker) {
+ super.onCreate(savedInstanceState);
+ return;
+ }
+
+ // In cases where the activity does not need to show an account picker, cut the chase
+ // and return the result directly. Eg:
+ // Single account -> select it directly
+ // No account -> launch add account activity directly
+ if (mPendingRequest == REQUEST_NULL) {
+ // If there are no relevant accounts and only one relevant account type go directly to
+ // add account. Otherwise let the user choose.
+ if (mPossiblyVisibleAccounts.isEmpty()) {
+ setNonLabelThemeAndCallSuperCreate(savedInstanceState);
+ if (mSetOfRelevantAccountTypes.size() == 1) {
+ runAddAccountForAuthenticator(mSetOfRelevantAccountTypes.iterator().next());
+ } else {
+ startChooseAccountTypeActivity();
+ }
+ }
+ }
+
+ String[] listItems = getListOfDisplayableOptions(mPossiblyVisibleAccounts);
+ mSelectedItemIndex = getItemIndexToSelect(mPossiblyVisibleAccounts, mSelectedAccountName,
+ mSelectedAddNewAccount);
+
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.choose_type_and_account);
+ overrideDescriptionIfSupplied(mDescriptionOverride);
+ populateUIAccountList(listItems);
+
+ // Only enable "OK" button if something has been selected.
+ mOkButton = findViewById(android.R.id.button2);
+ mOkButton.setEnabled(mSelectedItemIndex != SELECTED_ITEM_NONE);
+ }
+
+ @Override
+ protected void onDestroy() {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "ChooseTypeAndAccountActivity.onDestroy()");
+ }
+ super.onDestroy();
+ }
+
+ @Override
+ protected void onSaveInstanceState(final Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putInt(KEY_INSTANCE_STATE_PENDING_REQUEST, mPendingRequest);
+ if (mPendingRequest == REQUEST_ADD_ACCOUNT) {
+ outState.putParcelableArray(KEY_INSTANCE_STATE_EXISTING_ACCOUNTS, mExistingAccounts);
+ }
+ if (mSelectedItemIndex != SELECTED_ITEM_NONE) {
+ if (mSelectedItemIndex == mPossiblyVisibleAccounts.size()) {
+ outState.putBoolean(KEY_INSTANCE_STATE_SELECTED_ADD_ACCOUNT, true);
+ } else {
+ outState.putBoolean(KEY_INSTANCE_STATE_SELECTED_ADD_ACCOUNT, false);
+ outState.putString(KEY_INSTANCE_STATE_SELECTED_ACCOUNT_NAME,
+ mPossiblyVisibleAccounts.get(mSelectedItemIndex).name);
+ }
+ }
+ // save mAccounts
+ Parcelable[] accounts = new Parcelable[mAccounts.size()];
+ ArrayList<Integer> visibility = new ArrayList<>(mAccounts.size());
+ int i = 0;
+ for (Map.Entry<Account, Integer> e : mAccounts.entrySet()) {
+ accounts[i++] = e.getKey();
+ visibility.add(e.getValue());
+ }
+ outState.putParcelableArray(KEY_INSTANCE_STATE_ACCOUNTS_LIST, accounts);
+ outState.putIntegerArrayList(KEY_INSTANCE_STATE_VISIBILITY_LIST, visibility);
+ }
+
+ public void onCancelButtonClicked(View view) {
+ onBackPressed();
+ }
+
+ public void onOkButtonClicked(View view) {
+ if (mSelectedItemIndex == mPossiblyVisibleAccounts.size()) {
+ // Selected "Add New Account" option
+ startChooseAccountTypeActivity();
+ } else if (mSelectedItemIndex != SELECTED_ITEM_NONE) {
+ onAccountSelected(mPossiblyVisibleAccounts.get(mSelectedItemIndex));
+ }
+ }
+
+ // Called when the choose account type activity (for adding an account) returns.
+ // If it was a success read the account and set it in the result. In all cases
+ // return the result and finish this activity.
+ @Override
+ protected void onActivityResult(final int requestCode, final int resultCode,
+ final Intent data) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ if (data != null && data.getExtras() != null) data.getExtras().keySet();
+ Bundle extras = data != null ? data.getExtras() : null;
+ Log.v(TAG, "ChooseTypeAndAccountActivity.onActivityResult(reqCode=" + requestCode
+ + ", resCode=" + resultCode + ")");
+ }
+
+ // we got our result, so clear the fact that we had a pending request
+ mPendingRequest = REQUEST_NULL;
+
+ if (resultCode == RESULT_CANCELED) {
+ // if canceling out of addAccount and the original state caused us to skip this,
+ // finish this activity
+ if (mPossiblyVisibleAccounts.isEmpty()) {
+ setResult(Activity.RESULT_CANCELED);
+ finish();
+ }
+ return;
+ }
+
+ if (resultCode == RESULT_OK) {
+ if (requestCode == REQUEST_CHOOSE_TYPE) {
+ if (data != null) {
+ String accountType = data.getStringExtra(AccountManager.KEY_ACCOUNT_TYPE);
+ if (accountType != null) {
+ runAddAccountForAuthenticator(accountType);
+ return;
+ }
+ }
+ Log.d(TAG, "ChooseTypeAndAccountActivity.onActivityResult: unable to find account "
+ + "type, pretending the request was canceled");
+ } else if (requestCode == REQUEST_ADD_ACCOUNT) {
+ String accountName = null;
+ String accountType = null;
+
+ if (data != null) {
+ accountName = data.getStringExtra(AccountManager.KEY_ACCOUNT_NAME);
+ accountType = data.getStringExtra(AccountManager.KEY_ACCOUNT_TYPE);
+ }
+
+ if (accountName == null || accountType == null) {
+ // new account was added.
+ Account[] currentAccounts = AccountManager.get(this).getAccountsForPackage(
+ mCallingPackage, mCallingUid);
+ Set<Account> preExistingAccounts = new HashSet<Account>();
+ for (Parcelable accountParcel : mExistingAccounts) {
+ preExistingAccounts.add((Account) accountParcel);
+ }
+ for (Account account : currentAccounts) {
+ // New account is visible to the app - return it.
+ if (!preExistingAccounts.contains(account)) {
+ accountName = account.name;
+ accountType = account.type;
+ break;
+ }
+ }
+ }
+
+ if (accountName != null || accountType != null) {
+ setResultAndFinish(accountName, accountType);
+ return;
+ }
+ }
+ Log.d(TAG, "ChooseTypeAndAccountActivity.onActivityResult: unable to find added "
+ + "account, pretending the request was canceled");
+ }
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "ChooseTypeAndAccountActivity.onActivityResult: canceled");
+ }
+ setResult(Activity.RESULT_CANCELED);
+ finish();
+ }
+
+ protected void runAddAccountForAuthenticator(String type) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "runAddAccountForAuthenticator: " + type);
+ }
+ final Bundle options = getIntent().getBundleExtra(
+ ChooseTypeAndAccountActivity.EXTRA_ADD_ACCOUNT_OPTIONS_BUNDLE);
+ final String[] requiredFeatures = getIntent().getStringArrayExtra(
+ ChooseTypeAndAccountActivity.EXTRA_ADD_ACCOUNT_REQUIRED_FEATURES_STRING_ARRAY);
+ final String authTokenType = getIntent().getStringExtra(
+ ChooseTypeAndAccountActivity.EXTRA_ADD_ACCOUNT_AUTH_TOKEN_TYPE_STRING);
+ AccountManager.get(this).addAccount(type, authTokenType, requiredFeatures,
+ options, null /* activity */, this /* callback */, null /* Handler */);
+ }
+
+ @Override
+ public void run(final AccountManagerFuture<Bundle> accountManagerFuture) {
+ try {
+ final Bundle accountManagerResult = accountManagerFuture.getResult();
+ final Intent intent = (Intent)accountManagerResult.getParcelable(
+ AccountManager.KEY_INTENT, android.content.Intent.class);
+ if (intent != null) {
+ mPendingRequest = REQUEST_ADD_ACCOUNT;
+ mExistingAccounts = AccountManager.get(this).getAccountsForPackage(mCallingPackage,
+ mCallingUid);
+ intent.setFlags(intent.getFlags() & ~Intent.FLAG_ACTIVITY_NEW_TASK);
+ startActivityForResult(new Intent(intent), REQUEST_ADD_ACCOUNT);
+ return;
+ }
+ } catch (OperationCanceledException e) {
+ setResult(Activity.RESULT_CANCELED);
+ finish();
+ return;
+ } catch (IOException e) {
+ } catch (AuthenticatorException e) {
+ }
+ Bundle bundle = new Bundle();
+ bundle.putString(AccountManager.KEY_ERROR_MESSAGE, "error communicating with server");
+ setResult(Activity.RESULT_OK, new Intent().putExtras(bundle));
+ finish();
+ }
+
+ /**
+ * The default activity theme shows label at the top. Set a theme which does
+ * not show label, which effectively makes the activity invisible. Note that
+ * no content is being set. If something gets set, using this theme may be
+ * useless.
+ */
+ private void setNonLabelThemeAndCallSuperCreate(Bundle savedInstanceState) {
+ setTheme(R.style.Theme_DeviceDefault_Light_Dialog_NoActionBar);
+ super.onCreate(savedInstanceState);
+ }
+
+ private void onAccountSelected(Account account) {
+ Log.d(TAG, "selected account " + account.toSafeString());
+ setResultAndFinish(account.name, account.type);
+ }
+
+ private void setResultAndFinish(final String accountName, final String accountType) {
+ // Mark account as visible since user chose it.
+ Account account = new Account(accountName, accountType);
+ Integer oldVisibility =
+ AccountManager.get(this).getAccountVisibility(account, mCallingPackage);
+ if (oldVisibility != null
+ && oldVisibility == AccountManager.VISIBILITY_USER_MANAGED_NOT_VISIBLE) {
+ AccountManager.get(this).setAccountVisibility(account, mCallingPackage,
+ AccountManager.VISIBILITY_USER_MANAGED_VISIBLE);
+ }
+
+ if (oldVisibility != null && oldVisibility == AccountManager.VISIBILITY_NOT_VISIBLE) {
+ // Added account is not visible to caller.
+ setResult(Activity.RESULT_CANCELED);
+ finish();
+ return;
+ }
+ Bundle bundle = new Bundle();
+ bundle.putString(AccountManager.KEY_ACCOUNT_NAME, accountName);
+ bundle.putString(AccountManager.KEY_ACCOUNT_TYPE, accountType);
+ setResult(Activity.RESULT_OK, new Intent().putExtras(bundle));
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "ChooseTypeAndAccountActivity.setResultAndFinish: selected account "
+ + account.toSafeString());
+ }
+ finish();
+ }
+
+ private void startChooseAccountTypeActivity() {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "ChooseAccountTypeActivity.startChooseAccountTypeActivity()");
+ }
+ final Intent intent = new Intent(this, ChooseAccountTypeActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
+ intent.putExtra(EXTRA_ALLOWABLE_ACCOUNT_TYPES_STRING_ARRAY,
+ getIntent().getStringArrayExtra(EXTRA_ALLOWABLE_ACCOUNT_TYPES_STRING_ARRAY));
+ intent.putExtra(EXTRA_ADD_ACCOUNT_OPTIONS_BUNDLE,
+ getIntent().getBundleExtra(EXTRA_ADD_ACCOUNT_OPTIONS_BUNDLE));
+ intent.putExtra(EXTRA_ADD_ACCOUNT_REQUIRED_FEATURES_STRING_ARRAY,
+ getIntent().getStringArrayExtra(EXTRA_ADD_ACCOUNT_REQUIRED_FEATURES_STRING_ARRAY));
+ intent.putExtra(EXTRA_ADD_ACCOUNT_AUTH_TOKEN_TYPE_STRING,
+ getIntent().getStringExtra(EXTRA_ADD_ACCOUNT_AUTH_TOKEN_TYPE_STRING));
+ startActivityForResult(intent, REQUEST_CHOOSE_TYPE);
+ mPendingRequest = REQUEST_CHOOSE_TYPE;
+ }
+
+ /**
+ * @return a value between 0 (inclusive) and accounts.size() (inclusive) or SELECTED_ITEM_NONE.
+ * An index value of accounts.size() indicates 'Add account' option.
+ */
+ private int getItemIndexToSelect(ArrayList<Account> accounts, String selectedAccountName,
+ boolean selectedAddNewAccount) {
+ // If "Add account" option was previously selected by user, preserve it across
+ // orientation changes.
+ if (selectedAddNewAccount) {
+ return accounts.size();
+ }
+ // search for the selected account name if present
+ for (int i = 0; i < accounts.size(); i++) {
+ if (accounts.get(i).name.equals(selectedAccountName)) {
+ return i;
+ }
+ }
+ // no account selected.
+ return SELECTED_ITEM_NONE;
+ }
+
+ private String[] getListOfDisplayableOptions(ArrayList<Account> accounts) {
+ // List of options includes all accounts found together with "Add new account" as the
+ // last item in the list.
+ String[] listItems = new String[accounts.size() + (mDisallowAddAccounts ? 0 : 1)];
+ for (int i = 0; i < accounts.size(); i++) {
+ listItems[i] = accounts.get(i).name;
+ }
+ if (!mDisallowAddAccounts) {
+ listItems[accounts.size()] = getResources().getString(
+ R.string.add_account_button_label);
+ }
+ return listItems;
+ }
+
+ /**
+ * Create a list of Account objects for each account that is acceptable. Filter out accounts
+ * that don't match the allowable types, if provided, or that don't match the allowable
+ * accounts, if provided.
+ */
+ private LinkedHashMap<Account, Integer> getAcceptableAccountChoices(AccountManager accountManager) {
+ Map<Account, Integer> accountsAndVisibilityForCaller =
+ accountManager.getAccountsAndVisibilityForPackage(mCallingPackage, null);
+ Account[] allAccounts = accountManager.getAccounts();
+ LinkedHashMap<Account, Integer> accountsToPopulate =
+ new LinkedHashMap<>(accountsAndVisibilityForCaller.size());
+ for (Account account : allAccounts) {
+ if (mSetOfAllowableAccounts != null
+ && !mSetOfAllowableAccounts.contains(account)) {
+ continue;
+ }
+ if (mSetOfRelevantAccountTypes != null
+ && !mSetOfRelevantAccountTypes.contains(account.type)) {
+ continue;
+ }
+ if (accountsAndVisibilityForCaller.get(account) != null) {
+ accountsToPopulate.put(account, accountsAndVisibilityForCaller.get(account));
+ }
+ }
+ return accountsToPopulate;
+ }
+
+ /**
+ * Return a set of account types specified by the intent as well as supported by the
+ * AccountManager.
+ */
+ private Set<String> getReleventAccountTypes(final Intent intent) {
+ // An account type is relevant iff it is allowed by the caller and supported by the account
+ // manager.
+ Set<String> setOfRelevantAccountTypes = null;
+ final String[] allowedAccountTypes =
+ intent.getStringArrayExtra(EXTRA_ALLOWABLE_ACCOUNT_TYPES_STRING_ARRAY);
+ AuthenticatorDescription[] descs = AccountManager.get(this).getAuthenticatorTypes();
+ Set<String> supportedAccountTypes = new HashSet<String>(descs.length);
+ for (AuthenticatorDescription desc : descs) {
+ supportedAccountTypes.add(desc.type);
+ }
+ if (allowedAccountTypes != null) {
+ setOfRelevantAccountTypes = Sets.newHashSet(allowedAccountTypes);
+ setOfRelevantAccountTypes.retainAll(supportedAccountTypes);
+ } else {
+ setOfRelevantAccountTypes = supportedAccountTypes;
+ }
+ return setOfRelevantAccountTypes;
+ }
+
+ /**
+ * Returns a set of allowlisted accounts given by the intent or null if none specified by the
+ * intent.
+ */
+ private Set<Account> getAllowableAccountSet(final Intent intent) {
+ Set<Account> setOfAllowableAccounts = null;
+ final ArrayList<Parcelable> validAccounts =
+ intent.getParcelableArrayListExtra(EXTRA_ALLOWABLE_ACCOUNTS_ARRAYLIST);
+ if (validAccounts != null) {
+ setOfAllowableAccounts = new HashSet<Account>(validAccounts.size());
+ for (Parcelable parcelable : validAccounts) {
+ setOfAllowableAccounts.add((Account)parcelable);
+ }
+ }
+ return setOfAllowableAccounts;
+ }
+
+ /**
+ * Overrides the description text view for the picker activity if specified by the intent.
+ * If not specified then makes the description invisible.
+ */
+ private void overrideDescriptionIfSupplied(String descriptionOverride) {
+ TextView descriptionView = findViewById(R.id.description);
+ if (!TextUtils.isEmpty(descriptionOverride)) {
+ descriptionView.setText(descriptionOverride);
+ } else {
+ descriptionView.setVisibility(View.GONE);
+ }
+ }
+
+ /**
+ * Populates the UI ListView with the given list of items and selects an item
+ * based on {@code mSelectedItemIndex} member variable.
+ */
+ private final void populateUIAccountList(String[] listItems) {
+ ListView list = findViewById(android.R.id.list);
+ list.setAdapter(new ArrayAdapter<String>(this,
+ android.R.layout.simple_list_item_single_choice, listItems));
+ list.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
+ list.setItemsCanFocus(false);
+ list.setOnItemClickListener(
+ new AdapterView.OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
+ mSelectedItemIndex = position;
+ mOkButton.setEnabled(true);
+ }
+ });
+ if (mSelectedItemIndex != SELECTED_ITEM_NONE) {
+ list.setItemChecked(mSelectedItemIndex, true);
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "List item " + mSelectedItemIndex + " should be selected");
+ }
+ }
+ }
+}
diff --git a/android-35/android/accounts/GrantCredentialsPermissionActivity.java b/android-35/android/accounts/GrantCredentialsPermissionActivity.java
new file mode 100644
index 0000000..a89fae7
--- /dev/null
+++ b/android-35/android/accounts/GrantCredentialsPermissionActivity.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2009 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.accounts;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.os.Process;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.internal.R;
+
+import java.io.IOException;
+
+/**
+ * @hide
+ */
+public class GrantCredentialsPermissionActivity extends Activity implements View.OnClickListener {
+ public static final String EXTRAS_ACCOUNT = "account";
+ public static final String EXTRAS_AUTH_TOKEN_TYPE = "authTokenType";
+ public static final String EXTRAS_RESPONSE = "response";
+ public static final String EXTRAS_REQUESTING_UID = "uid";
+
+ private Account mAccount;
+ private String mAuthTokenType;
+ private int mUid;
+ private int mCallingUid;
+ private Bundle mResultBundle = null;
+ protected LayoutInflater mInflater;
+
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ getWindow().addSystemFlags(
+ android.view.WindowManager.LayoutParams
+ .SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
+ setContentView(R.layout.grant_credentials_permission);
+ setTitle(R.string.grant_permissions_header_text);
+
+ mInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+
+ final Bundle extras = getIntent().getExtras();
+ if (extras == null) {
+ // we were somehow started with bad parameters. abort the activity.
+ setResult(Activity.RESULT_CANCELED);
+ finish();
+ return;
+ }
+
+ // Grant 'account'/'type' to mUID
+ mAccount = extras.getParcelable(EXTRAS_ACCOUNT, android.accounts.Account.class);
+ mAuthTokenType = extras.getString(EXTRAS_AUTH_TOKEN_TYPE);
+ mUid = extras.getInt(EXTRAS_REQUESTING_UID);
+ final PackageManager pm = getPackageManager();
+ final String[] packages = pm.getPackagesForUid(mUid);
+
+ if (mAccount == null || mAuthTokenType == null || packages == null) {
+ // we were somehow started with bad parameters. abort the activity.
+ setResult(Activity.RESULT_CANCELED);
+ finish();
+ return;
+ }
+
+ mCallingUid = getLaunchedFromUid();
+
+ if (!UserHandle.isSameApp(mCallingUid, Process.SYSTEM_UID) && mCallingUid != mUid) {
+ setResult(Activity.RESULT_CANCELED);
+ finish();
+ return;
+ }
+
+ String accountTypeLabel;
+ try {
+ accountTypeLabel = getAccountLabel(mAccount);
+ } catch (IllegalArgumentException e) {
+ // label or resource was missing. abort the activity.
+ setResult(Activity.RESULT_CANCELED);
+ finish();
+ return;
+ }
+
+ final TextView authTokenTypeView = findViewById(R.id.authtoken_type);
+ authTokenTypeView.setVisibility(View.GONE);
+
+ final AccountManagerCallback<String> callback = new AccountManagerCallback<String>() {
+ public void run(AccountManagerFuture<String> future) {
+ try {
+ final String authTokenLabel = future.getResult();
+ if (!TextUtils.isEmpty(authTokenLabel)) {
+ runOnUiThread(new Runnable() {
+ public void run() {
+ if (!isFinishing()) {
+ authTokenTypeView.setText(authTokenLabel);
+ authTokenTypeView.setVisibility(View.VISIBLE);
+ }
+ }
+ });
+ }
+ } catch (OperationCanceledException e) {
+ } catch (IOException e) {
+ } catch (AuthenticatorException e) {
+ }
+ }
+ };
+
+ if (!AccountManager.ACCOUNT_ACCESS_TOKEN_TYPE.equals(mAuthTokenType)) {
+ AccountManager.get(this).getAuthTokenLabel(mAccount.type,
+ mAuthTokenType, callback, null);
+ }
+
+ findViewById(R.id.allow_button).setOnClickListener(this);
+ findViewById(R.id.deny_button).setOnClickListener(this);
+
+ LinearLayout packagesListView = findViewById(R.id.packages_list);
+
+ for (String pkg : packages) {
+ String packageLabel;
+ try {
+ packageLabel = pm.getApplicationLabel(pm.getApplicationInfo(pkg, 0)).toString();
+ } catch (PackageManager.NameNotFoundException e) {
+ packageLabel = pkg;
+ }
+ packagesListView.addView(newPackageView(packageLabel));
+ }
+
+ ((TextView) findViewById(R.id.account_name)).setText(mAccount.name);
+ ((TextView) findViewById(R.id.account_type)).setText(accountTypeLabel);
+ }
+
+ private String getAccountLabel(Account account) {
+ final AuthenticatorDescription[] authenticatorTypes =
+ AccountManager.get(this).getAuthenticatorTypes();
+ for (int i = 0, N = authenticatorTypes.length; i < N; i++) {
+ final AuthenticatorDescription desc = authenticatorTypes[i];
+ if (desc.type.equals(account.type)) {
+ try {
+ return createPackageContext(desc.packageName, 0).getString(desc.labelId);
+ } catch (PackageManager.NameNotFoundException e) {
+ return account.type;
+ } catch (Resources.NotFoundException e) {
+ return account.type;
+ }
+ }
+ }
+ return account.type;
+ }
+
+ private View newPackageView(String packageLabel) {
+ View view = mInflater.inflate(R.layout.permissions_package_list_item, null);
+ ((TextView) view.findViewById(R.id.package_label)).setText(packageLabel);
+ return view;
+ }
+
+ public void onClick(View v) {
+ switch (v.getId()) {
+ case R.id.allow_button:
+ AccountManager.get(this).updateAppPermission(mAccount, mAuthTokenType, mUid, true);
+ Intent result = new Intent();
+ result.putExtra("retry", true);
+ setResult(RESULT_OK, result);
+ setAccountAuthenticatorResult(result.getExtras());
+ break;
+
+ case R.id.deny_button:
+ AccountManager.get(this).updateAppPermission(mAccount, mAuthTokenType, mUid, false);
+ setResult(RESULT_CANCELED);
+ break;
+ }
+ finish();
+ }
+
+ public final void setAccountAuthenticatorResult(Bundle result) {
+ mResultBundle = result;
+ }
+
+ /**
+ * Sends the result or a {@link AccountManager#ERROR_CODE_CANCELED} error if a
+ * result isn't present.
+ */
+ public void finish() {
+ Intent intent = getIntent();
+ AccountAuthenticatorResponse response = intent.getParcelableExtra(EXTRAS_RESPONSE, android.accounts.AccountAuthenticatorResponse.class);
+ if (response != null) {
+ // send the result bundle back if set, otherwise send an error.
+ if (mResultBundle != null) {
+ response.onResult(mResultBundle);
+ } else {
+ response.onError(AccountManager.ERROR_CODE_CANCELED, "canceled");
+ }
+ }
+ super.finish();
+ }
+}
diff --git a/android-35/android/accounts/NetworkErrorException.java b/android-35/android/accounts/NetworkErrorException.java
new file mode 100644
index 0000000..07f4ce9
--- /dev/null
+++ b/android-35/android/accounts/NetworkErrorException.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2009 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.accounts;
+
+public class NetworkErrorException extends AccountsException {
+ public NetworkErrorException() {
+ super();
+ }
+ public NetworkErrorException(String message) {
+ super(message);
+ }
+ public NetworkErrorException(String message, Throwable cause) {
+ super(message, cause);
+ }
+ public NetworkErrorException(Throwable cause) {
+ super(cause);
+ }
+}
\ No newline at end of file
diff --git a/android-35/android/accounts/OnAccountsUpdateListener.java b/android-35/android/accounts/OnAccountsUpdateListener.java
new file mode 100644
index 0000000..2b4ee50
--- /dev/null
+++ b/android-35/android/accounts/OnAccountsUpdateListener.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2007 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.accounts;
+
+/**
+ * An interface that contains the callback used by the AccountManager
+ */
+public interface OnAccountsUpdateListener {
+ /**
+ * This invoked when the AccountManager starts up and whenever the account
+ * set changes.
+ * @param accounts the current accounts
+ */
+ void onAccountsUpdated(Account[] accounts);
+}
diff --git a/android-35/android/accounts/OperationCanceledException.java b/android-35/android/accounts/OperationCanceledException.java
new file mode 100644
index 0000000..896d194
--- /dev/null
+++ b/android-35/android/accounts/OperationCanceledException.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2009 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.accounts;
+
+public class OperationCanceledException extends AccountsException {
+ public OperationCanceledException() {
+ super();
+ }
+ public OperationCanceledException(String message) {
+ super(message);
+ }
+ public OperationCanceledException(String message, Throwable cause) {
+ super(message, cause);
+ }
+ public OperationCanceledException(Throwable cause) {
+ super(cause);
+ }
+}
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]\n@com.android.ondevicepersonalization.internal.util.DataClass(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]\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/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]\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/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 []\n@com.android.ondevicepersonalization.internal.util.DataClass(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 []\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/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]\n@com.android.ondevicepersonalization.internal.util.DataClass(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]\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/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 []\n@com.android.ondevicepersonalization.internal.util.DataClass(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]\n@com.android.ondevicepersonalization.internal.util.DataClass(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 []\n@com.android.ondevicepersonalization.internal.util.DataClass(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 []\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/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]\n@com.android.ondevicepersonalization.internal.util.DataClass(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]\n@com.android.ondevicepersonalization.internal.util.DataClass(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 []\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/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]\n@com.android.ondevicepersonalization.internal.util.DataClass(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 []\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/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 []\n@com.android.ondevicepersonalization.internal.util.DataClass(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 []\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/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]\n@com.android.ondevicepersonalization.internal.util.DataClass(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 []\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/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]\n@com.android.ondevicepersonalization.internal.util.DataClass(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]\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/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 []\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/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]\n@com.android.ondevicepersonalization.internal.util.DataClass(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]\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/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 []\n@com.android.ondevicepersonalization.internal.util.DataClass(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]\n@com.android.ondevicepersonalization.internal.util.DataClass(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 []\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/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]\n@com.android.ondevicepersonalization.internal.util.DataClass(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]\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/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 []\n@com.android.ondevicepersonalization.internal.util.DataClass(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]\n@com.android.ondevicepersonalization.internal.util.DataClass(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 []\n@com.android.ondevicepersonalization.internal.util.DataClass(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]\n@com.android.ondevicepersonalization.internal.util.DataClass(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 []\n@com.android.ondevicepersonalization.internal.util.DataClass(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]\n@com.android.ondevicepersonalization.internal.util.DataClass(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 []\n@com.android.ondevicepersonalization.internal.util.DataClass(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]\n@com.android.ondevicepersonalization.internal.util.DataClass(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 []\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/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]\n@com.android.ondevicepersonalization.internal.util.DataClass(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();
+ }
+}
diff --git a/android-35/android/animation/AnimationHandler.java b/android-35/android/animation/AnimationHandler.java
new file mode 100644
index 0000000..4fc90ae
--- /dev/null
+++ b/android-35/android/animation/AnimationHandler.java
@@ -0,0 +1,519 @@
+/*
+ * Copyright (C) 2015 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.animation;
+
+import android.annotation.Nullable;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.view.Choreographer;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+
+/**
+ * This custom, static handler handles the timing pulse that is shared by all active
+ * ValueAnimators. This approach ensures that the setting of animation values will happen on the
+ * same thread that animations start on, and that all animations will share the same times for
+ * calculating their values, which makes synchronizing animations possible.
+ *
+ * The handler uses the Choreographer by default for doing periodic callbacks. A custom
+ * AnimationFrameCallbackProvider can be set on the handler to provide timing pulse that
+ * may be independent of UI frame update. This could be useful in testing.
+ *
+ * @hide
+ */
+public class AnimationHandler {
+
+ private static final String TAG = "AnimationHandler";
+ private static final boolean LOCAL_LOGV = false;
+
+ /**
+ * Internal per-thread collections used to avoid set collisions as animations start and end
+ * while being processed.
+ */
+ private final ArrayMap<AnimationFrameCallback, Long> mDelayedCallbackStartTime =
+ new ArrayMap<>();
+ private final ArrayList<AnimationFrameCallback> mAnimationCallbacks =
+ new ArrayList<>();
+ private final ArrayList<AnimationFrameCallback> mCommitCallbacks =
+ new ArrayList<>();
+ private AnimationFrameCallbackProvider mProvider;
+
+ // Static flag which allows the pausing behavior to be globally disabled/enabled.
+ private static boolean sAnimatorPausingEnabled = isPauseBgAnimationsEnabledInSystemProperties();
+
+ // Static flag which prevents the system property from overriding sAnimatorPausingEnabled field.
+ private static boolean sOverrideAnimatorPausingSystemProperty = false;
+
+ /**
+ * This paused list is used to store animators forcibly paused when the activity
+ * went into the background (to avoid unnecessary background processing work).
+ * These animators should be resume()'d when the activity returns to the foreground.
+ */
+ private final ArrayList<Animator> mPausedAnimators = new ArrayList<>();
+
+ /**
+ * This structure is used to store the currently active objects (ViewRootImpls or
+ * WallpaperService.Engines) in the process. Each of these objects sends a request to
+ * AnimationHandler when it goes into the background (request to pause) or foreground
+ * (request to resume). Because all animators are managed by AnimationHandler on the same
+ * thread, it should only ever pause animators when *all* requestors are in the background.
+ * This list tracks the background/foreground state of all requestors and only ever
+ * pauses animators when all items are in the background (false). To simplify, we only ever
+ * store visible (foreground) requestors; if the set size reaches zero, there are no
+ * objects in the foreground and it is time to pause animators.
+ */
+ private final ArrayList<WeakReference<Object>> mAnimatorRequestors = new ArrayList<>();
+
+ private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
+ @Override
+ public void doFrame(long frameTimeNanos) {
+ doAnimationFrame(getProvider().getFrameTime());
+ if (mAnimationCallbacks.size() > 0) {
+ getProvider().postFrameCallback(this);
+ }
+ }
+ };
+
+ public final static ThreadLocal<AnimationHandler> sAnimatorHandler = new ThreadLocal<>();
+ private static AnimationHandler sTestHandler = null;
+ private boolean mListDirty = false;
+
+ public static AnimationHandler getInstance() {
+ if (sTestHandler != null) {
+ return sTestHandler;
+ }
+ if (sAnimatorHandler.get() == null) {
+ sAnimatorHandler.set(new AnimationHandler());
+ }
+ return sAnimatorHandler.get();
+ }
+
+ /**
+ * Sets an instance that will be returned by {@link #getInstance()} on every thread.
+ * @return the previously active test handler, if any.
+ * @hide
+ */
+ public static @Nullable AnimationHandler setTestHandler(@Nullable AnimationHandler handler) {
+ AnimationHandler oldHandler = sTestHandler;
+ sTestHandler = handler;
+ return oldHandler;
+ }
+
+ /**
+ * System property that controls the behavior of pausing infinite animators when an app
+ * is moved to the background.
+ *
+ * @return the value of 'framework.pause_bg_animations.enabled' system property
+ */
+ private static boolean isPauseBgAnimationsEnabledInSystemProperties() {
+ if (sOverrideAnimatorPausingSystemProperty) return sAnimatorPausingEnabled;
+ return SystemProperties
+ .getBoolean("framework.pause_bg_animations.enabled", true);
+ }
+
+ /**
+ * Disable the default behavior of pausing infinite animators when
+ * apps go into the background.
+ *
+ * @param enable Enable (default behavior) or disable background pausing behavior.
+ */
+ public static void setAnimatorPausingEnabled(boolean enable) {
+ sAnimatorPausingEnabled = enable;
+ }
+
+ /**
+ * Prevents the setAnimatorPausingEnabled behavior from being overridden
+ * by the 'framework.pause_bg_animations.enabled' system property value.
+ *
+ * This is for testing purposes only.
+ *
+ * @param enable Enable or disable (default behavior) overriding the system
+ * property.
+ */
+ public static void setOverrideAnimatorPausingSystemProperty(boolean enable) {
+ sOverrideAnimatorPausingSystemProperty = enable;
+ }
+
+ /**
+ * This is called when a window goes away. We should remove
+ * it from the requestors list to ensure that we are counting requests correctly and not
+ * tracking obsolete+enabled requestors.
+ */
+ public static void removeRequestor(Object requestor) {
+ getInstance().requestAnimatorsEnabledImpl(false, requestor);
+ if (LOCAL_LOGV) {
+ Log.v(TAG, "removeRequestor for " + requestor);
+ }
+ }
+
+ /**
+ * This method is called from ViewRootImpl or WallpaperService when either a window is no
+ * longer visible (enable == false) or when a window becomes visible (enable == true).
+ * If animators are not properly disabled when activities are backgrounded, it can lead to
+ * unnecessary processing, particularly for infinite animators, as the system will continue
+ * to pulse timing events even though the results are not visible. As a workaround, we
+ * pause all un-paused infinite animators, and resume them when any window in the process
+ * becomes visible.
+ */
+ public static void requestAnimatorsEnabled(boolean enable, Object requestor) {
+ getInstance().requestAnimatorsEnabledImpl(enable, requestor);
+ }
+
+ private void requestAnimatorsEnabledImpl(boolean enable, Object requestor) {
+ boolean wasEmpty = mAnimatorRequestors.isEmpty();
+ setAnimatorPausingEnabled(isPauseBgAnimationsEnabledInSystemProperties());
+ synchronized (mAnimatorRequestors) {
+ // Only store WeakRef objects to avoid leaks
+ if (enable) {
+ // First, check whether such a reference is already on the list
+ WeakReference<Object> weakRef = null;
+ for (int i = mAnimatorRequestors.size() - 1; i >= 0; --i) {
+ WeakReference<Object> ref = mAnimatorRequestors.get(i);
+ Object referent = ref.get();
+ if (referent == requestor) {
+ weakRef = ref;
+ } else if (referent == null) {
+ // Remove any reference that has been cleared
+ mAnimatorRequestors.remove(i);
+ }
+ }
+ if (weakRef == null) {
+ weakRef = new WeakReference<>(requestor);
+ mAnimatorRequestors.add(weakRef);
+ }
+ } else {
+ for (int i = mAnimatorRequestors.size() - 1; i >= 0; --i) {
+ WeakReference<Object> ref = mAnimatorRequestors.get(i);
+ Object referent = ref.get();
+ if (referent == requestor || referent == null) {
+ // remove requested item or item that has been cleared
+ mAnimatorRequestors.remove(i);
+ }
+ }
+ // If a reference to the requestor wasn't in the list, nothing to remove
+ }
+ }
+ if (!sAnimatorPausingEnabled) {
+ // Resume any animators that have been paused in the meantime, otherwise noop
+ // Leave logic above so that if pausing gets re-enabled, the state of the requestors
+ // list is valid
+ resumeAnimators();
+ return;
+ }
+ boolean isEmpty = mAnimatorRequestors.isEmpty();
+ if (wasEmpty != isEmpty) {
+ // only paused/resume animators if there was a visibility change
+ if (!isEmpty) {
+ // If any requestors are enabled, resume currently paused animators
+ resumeAnimators();
+ } else {
+ // Wait before pausing to avoid thrashing animator state for temporary backgrounding
+ Choreographer.getInstance().postFrameCallbackDelayed(mPauser,
+ Animator.getBackgroundPauseDelay());
+ }
+ }
+ if (LOCAL_LOGV) {
+ Log.v(TAG, (enable ? "enable" : "disable") + " animators for " + requestor
+ + " with pauseDelay of " + Animator.getBackgroundPauseDelay());
+ for (int i = 0; i < mAnimatorRequestors.size(); ++i) {
+ Log.v(TAG, "animatorRequestors " + i + " = "
+ + mAnimatorRequestors.get(i) + " with referent "
+ + mAnimatorRequestors.get(i).get());
+ }
+ }
+ }
+
+ private void resumeAnimators() {
+ Choreographer.getInstance().removeFrameCallback(mPauser);
+ for (int i = mPausedAnimators.size() - 1; i >= 0; --i) {
+ mPausedAnimators.get(i).resume();
+ }
+ mPausedAnimators.clear();
+ }
+
+ private Choreographer.FrameCallback mPauser = frameTimeNanos -> {
+ if (mAnimatorRequestors.size() > 0) {
+ // something enabled animators since this callback was scheduled - bail
+ return;
+ }
+ for (int i = 0; i < mAnimationCallbacks.size(); ++i) {
+ AnimationFrameCallback callback = mAnimationCallbacks.get(i);
+ if (callback instanceof Animator) {
+ Animator animator = ((Animator) callback);
+ if (animator.getTotalDuration() == Animator.DURATION_INFINITE
+ && !animator.isPaused()) {
+ mPausedAnimators.add(animator);
+ animator.pause();
+ }
+ }
+ }
+ };
+
+ /**
+ * By default, the Choreographer is used to provide timing for frame callbacks. A custom
+ * provider can be used here to provide different timing pulse.
+ */
+ public void setProvider(AnimationFrameCallbackProvider provider) {
+ if (provider == null) {
+ mProvider = new MyFrameCallbackProvider();
+ } else {
+ mProvider = provider;
+ }
+ }
+
+ private AnimationFrameCallbackProvider getProvider() {
+ if (mProvider == null) {
+ mProvider = new MyFrameCallbackProvider();
+ }
+ return mProvider;
+ }
+
+ /**
+ * Register to get a callback on the next frame after the delay.
+ */
+ public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
+ if (mAnimationCallbacks.size() == 0) {
+ getProvider().postFrameCallback(mFrameCallback);
+ }
+ if (!mAnimationCallbacks.contains(callback)) {
+ mAnimationCallbacks.add(callback);
+ }
+
+ if (delay > 0) {
+ mDelayedCallbackStartTime.put(callback, (SystemClock.uptimeMillis() + delay));
+ }
+ }
+
+ /**
+ * Register to get a one shot callback for frame commit timing. Frame commit timing is the
+ * time *after* traversals are done, as opposed to the animation frame timing, which is
+ * before any traversals. This timing can be used to adjust the start time of an animation
+ * when expensive traversals create big delta between the animation frame timing and the time
+ * that animation is first shown on screen.
+ *
+ * Note this should only be called when the animation has already registered to receive
+ * animation frame callbacks. This callback will be guaranteed to happen *after* the next
+ * animation frame callback.
+ */
+ public void addOneShotCommitCallback(final AnimationFrameCallback callback) {
+ if (!mCommitCallbacks.contains(callback)) {
+ mCommitCallbacks.add(callback);
+ }
+ }
+
+ /**
+ * Removes the given callback from the list, so it will no longer be called for frame related
+ * timing.
+ */
+ public void removeCallback(AnimationFrameCallback callback) {
+ mCommitCallbacks.remove(callback);
+ mDelayedCallbackStartTime.remove(callback);
+ int id = mAnimationCallbacks.indexOf(callback);
+ if (id >= 0) {
+ mAnimationCallbacks.set(id, null);
+ mListDirty = true;
+ }
+ }
+
+ private void doAnimationFrame(long frameTime) {
+ long currentTime = SystemClock.uptimeMillis();
+ final int size = mAnimationCallbacks.size();
+ for (int i = 0; i < size; i++) {
+ final AnimationFrameCallback callback = mAnimationCallbacks.get(i);
+ if (callback == null) {
+ continue;
+ }
+ if (isCallbackDue(callback, currentTime)) {
+ callback.doAnimationFrame(frameTime);
+ if (mCommitCallbacks.contains(callback)) {
+ getProvider().postCommitCallback(new Runnable() {
+ @Override
+ public void run() {
+ commitAnimationFrame(callback, getProvider().getFrameTime());
+ }
+ });
+ }
+ }
+ }
+ cleanUpList();
+ }
+
+ private void commitAnimationFrame(AnimationFrameCallback callback, long frameTime) {
+ if (!mDelayedCallbackStartTime.containsKey(callback) &&
+ mCommitCallbacks.contains(callback)) {
+ callback.commitAnimationFrame(frameTime);
+ mCommitCallbacks.remove(callback);
+ }
+ }
+
+ /**
+ * Remove the callbacks from mDelayedCallbackStartTime once they have passed the initial delay
+ * so that they can start getting frame callbacks.
+ *
+ * @return true if they have passed the initial delay or have no delay, false otherwise.
+ */
+ private boolean isCallbackDue(AnimationFrameCallback callback, long currentTime) {
+ Long startTime = mDelayedCallbackStartTime.get(callback);
+ if (startTime == null) {
+ return true;
+ }
+ if (startTime < currentTime) {
+ mDelayedCallbackStartTime.remove(callback);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Return the number of callbacks that have registered for frame callbacks.
+ */
+ public static int getAnimationCount() {
+ AnimationHandler handler = sTestHandler;
+ if (handler == null) {
+ handler = sAnimatorHandler.get();
+ }
+ if (handler == null) {
+ return 0;
+ }
+ return handler.getCallbackSize();
+ }
+
+ public static void setFrameDelay(long delay) {
+ getInstance().getProvider().setFrameDelay(delay);
+ }
+
+ public static long getFrameDelay() {
+ return getInstance().getProvider().getFrameDelay();
+ }
+
+ void autoCancelBasedOn(ObjectAnimator objectAnimator) {
+ for (int i = mAnimationCallbacks.size() - 1; i >= 0; i--) {
+ AnimationFrameCallback cb = mAnimationCallbacks.get(i);
+ if (cb == null) {
+ continue;
+ }
+ if (objectAnimator.shouldAutoCancel(cb)) {
+ ((Animator) mAnimationCallbacks.get(i)).cancel();
+ }
+ }
+ }
+
+ private void cleanUpList() {
+ if (mListDirty) {
+ for (int i = mAnimationCallbacks.size() - 1; i >= 0; i--) {
+ if (mAnimationCallbacks.get(i) == null) {
+ mAnimationCallbacks.remove(i);
+ }
+ }
+ mListDirty = false;
+ }
+ }
+
+ private int getCallbackSize() {
+ int count = 0;
+ int size = mAnimationCallbacks.size();
+ for (int i = size - 1; i >= 0; i--) {
+ if (mAnimationCallbacks.get(i) != null) {
+ count++;
+ }
+ }
+ return count;
+ }
+
+ /**
+ * Default provider of timing pulse that uses Choreographer for frame callbacks.
+ */
+ private class MyFrameCallbackProvider implements AnimationFrameCallbackProvider {
+
+ final Choreographer mChoreographer = Choreographer.getInstance();
+
+ @Override
+ public void postFrameCallback(Choreographer.FrameCallback callback) {
+ mChoreographer.postFrameCallback(callback);
+ }
+
+ @Override
+ public void postCommitCallback(Runnable runnable) {
+ mChoreographer.postCallback(Choreographer.CALLBACK_COMMIT, runnable, null);
+ }
+
+ @Override
+ public long getFrameTime() {
+ return mChoreographer.getFrameTime();
+ }
+
+ @Override
+ public long getFrameDelay() {
+ return Choreographer.getFrameDelay();
+ }
+
+ @Override
+ public void setFrameDelay(long delay) {
+ Choreographer.setFrameDelay(delay);
+ }
+ }
+
+ /**
+ * Callbacks that receives notifications for animation timing and frame commit timing.
+ * @hide
+ */
+ public interface AnimationFrameCallback {
+ /**
+ * Run animation based on the frame time.
+ * @param frameTime The frame start time, in the {@link SystemClock#uptimeMillis()} time
+ * base.
+ * @return if the animation has finished.
+ */
+ boolean doAnimationFrame(long frameTime);
+
+ /**
+ * This notifies the callback of frame commit time. Frame commit time is the time after
+ * traversals happen, as opposed to the normal animation frame time that is before
+ * traversals. This is used to compensate expensive traversals that happen as the
+ * animation starts. When traversals take a long time to complete, the rendering of the
+ * initial frame will be delayed (by a long time). But since the startTime of the
+ * animation is set before the traversal, by the time of next frame, a lot of time would
+ * have passed since startTime was set, the animation will consequently skip a few frames
+ * to respect the new frameTime. By having the commit time, we can adjust the start time to
+ * when the first frame was drawn (after any expensive traversals) so that no frames
+ * will be skipped.
+ *
+ * @param frameTime The frame time after traversals happen, if any, in the
+ * {@link SystemClock#uptimeMillis()} time base.
+ */
+ void commitAnimationFrame(long frameTime);
+ }
+
+ /**
+ * The intention for having this interface is to increase the testability of ValueAnimator.
+ * Specifically, we can have a custom implementation of the interface below and provide
+ * timing pulse without using Choreographer. That way we could use any arbitrary interval for
+ * our timing pulse in the tests.
+ *
+ * @hide
+ */
+ public interface AnimationFrameCallbackProvider {
+ void postFrameCallback(Choreographer.FrameCallback callback);
+ void postCommitCallback(Runnable runnable);
+ long getFrameTime();
+ long getFrameDelay();
+ void setFrameDelay(long delay);
+ }
+}
diff --git a/android-35/android/animation/Animator.java b/android-35/android/animation/Animator.java
new file mode 100644
index 0000000..c58624e
--- /dev/null
+++ b/android-35/android/animation/Animator.java
@@ -0,0 +1,857 @@
+/*
+ * Copyright (C) 2010 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.animation;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.pm.ActivityInfo.Config;
+import android.content.res.ConstantState;
+import android.os.Build;
+import android.util.LongArray;
+
+import java.util.ArrayList;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * This is the superclass for classes which provide basic support for animations which can be
+ * started, ended, and have <code>AnimatorListeners</code> added to them.
+ */
+public abstract class Animator implements Cloneable {
+
+ /**
+ * The value used to indicate infinite duration (e.g. when Animators repeat infinitely).
+ */
+ public static final long DURATION_INFINITE = -1;
+ /**
+ * The set of listeners to be sent events through the life of an animation.
+ */
+ ArrayList<AnimatorListener> mListeners = null;
+
+ /**
+ * The set of listeners to be sent pause/resume events through the life
+ * of an animation.
+ */
+ ArrayList<AnimatorPauseListener> mPauseListeners = null;
+
+ /**
+ * Whether this animator is currently in a paused state.
+ */
+ boolean mPaused = false;
+
+ /**
+ * A set of flags which identify the type of configuration changes that can affect this
+ * Animator. Used by the Animator cache.
+ */
+ @Config int mChangingConfigurations = 0;
+
+ /**
+ * If this animator is inflated from a constant state, keep a reference to it so that
+ * ConstantState will not be garbage collected until this animator is collected
+ */
+ private AnimatorConstantState mConstantState;
+
+ /**
+ * backing field for backgroundPauseDelay property. This could be simply a hardcoded
+ * value in AnimationHandler, but it is useful to be able to change the value in tests.
+ */
+ private static long sBackgroundPauseDelay = 1000;
+
+ /**
+ * A cache of the values in a list. Used so that when calling the list, we have a copy
+ * of it in case the list is modified while iterating. The array can be reused to avoid
+ * allocation on every notification.
+ */
+ private AtomicReference<Object[]> mCachedList = new AtomicReference<>();
+
+ /**
+ * Tracks whether we've notified listeners of the onAnimationStart() event. This can be
+ * complex to keep track of since we notify listeners at different times depending on
+ * startDelay and whether start() was called before end().
+ */
+ boolean mStartListenersCalled = false;
+
+ /**
+ * Sets the duration for delaying pausing animators when apps go into the background.
+ * Used by AnimationHandler when requested to pause animators.
+ *
+ * @hide
+ */
+ @TestApi
+ public static void setBackgroundPauseDelay(long value) {
+ sBackgroundPauseDelay = value;
+ }
+
+ /**
+ * Gets the duration for delaying pausing animators when apps go into the background.
+ * Used by AnimationHandler when requested to pause animators.
+ *
+ * @hide
+ */
+ @TestApi
+ public static long getBackgroundPauseDelay() {
+ return sBackgroundPauseDelay;
+ }
+
+ /**
+ * Sets the behavior of animator pausing when apps go into the background.
+ * This is exposed as a test API for verification, but is intended for use by internal/
+ * platform code, potentially for use by a system property that could disable it
+ * system wide.
+ *
+ * @param enable Enable (default behavior) or disable background pausing behavior.
+ * @hide
+ */
+ @TestApi
+ public static void setAnimatorPausingEnabled(boolean enable) {
+ AnimationHandler.setAnimatorPausingEnabled(enable);
+ AnimationHandler.setOverrideAnimatorPausingSystemProperty(!enable);
+ }
+
+ /**
+ * Starts this animation. If the animation has a nonzero startDelay, the animation will start
+ * running after that delay elapses. A non-delayed animation will have its initial
+ * value(s) set immediately, followed by calls to
+ * {@link AnimatorListener#onAnimationStart(Animator)} for any listeners of this animator.
+ *
+ * <p>The animation started by calling this method will be run on the thread that called
+ * this method. This thread should have a Looper on it (a runtime exception will be thrown if
+ * this is not the case). Also, if the animation will animate
+ * properties of objects in the view hierarchy, then the calling thread should be the UI
+ * thread for that view hierarchy.</p>
+ *
+ */
+ public void start() {
+ }
+
+ /**
+ * Cancels the animation. Unlike {@link #end()}, <code>cancel()</code> causes the animation to
+ * stop in its tracks, sending an
+ * {@link android.animation.Animator.AnimatorListener#onAnimationCancel(Animator)} to
+ * its listeners, followed by an
+ * {@link android.animation.Animator.AnimatorListener#onAnimationEnd(Animator)} message.
+ *
+ * <p>This method must be called on the thread that is running the animation.</p>
+ */
+ public void cancel() {
+ }
+
+ /**
+ * Ends the animation. This causes the animation to assign the end value of the property being
+ * animated, then calling the
+ * {@link android.animation.Animator.AnimatorListener#onAnimationEnd(Animator)} method on
+ * its listeners.
+ *
+ * <p>This method must be called on the thread that is running the animation.</p>
+ */
+ public void end() {
+ }
+
+ /**
+ * Pauses a running animation. This method should only be called on the same thread on
+ * which the animation was started. If the animation has not yet been {@link
+ * #isStarted() started} or has since ended, then the call is ignored. Paused
+ * animations can be resumed by calling {@link #resume()}.
+ *
+ * @see #resume()
+ * @see #isPaused()
+ * @see AnimatorPauseListener
+ */
+ public void pause() {
+ // We only want to pause started Animators or animators that setCurrentPlayTime()
+ // have been called on. mStartListenerCalled will be true if seek has happened.
+ if ((isStarted() || mStartListenersCalled) && !mPaused) {
+ mPaused = true;
+ notifyPauseListeners(AnimatorCaller.ON_PAUSE);
+ }
+ }
+
+ /**
+ * Resumes a paused animation, causing the animator to pick up where it left off
+ * when it was paused. This method should only be called on the same thread on
+ * which the animation was started. Calls to resume() on an animator that is
+ * not currently paused will be ignored.
+ *
+ * @see #pause()
+ * @see #isPaused()
+ * @see AnimatorPauseListener
+ */
+ public void resume() {
+ if (mPaused) {
+ mPaused = false;
+ notifyPauseListeners(AnimatorCaller.ON_RESUME);
+ }
+ }
+
+ /**
+ * Returns whether this animator is currently in a paused state.
+ *
+ * @return True if the animator is currently paused, false otherwise.
+ *
+ * @see #pause()
+ * @see #resume()
+ */
+ public boolean isPaused() {
+ return mPaused;
+ }
+
+ /**
+ * The amount of time, in milliseconds, to delay processing the animation
+ * after {@link #start()} is called.
+ *
+ * @return the number of milliseconds to delay running the animation
+ */
+ public abstract long getStartDelay();
+
+ /**
+ * The amount of time, in milliseconds, to delay processing the animation
+ * after {@link #start()} is called.
+
+ * @param startDelay The amount of the delay, in milliseconds
+ */
+ public abstract void setStartDelay(long startDelay);
+
+ /**
+ * Sets the duration of the animation.
+ *
+ * @param duration The length of the animation, in milliseconds.
+ */
+ public abstract Animator setDuration(long duration);
+
+ /**
+ * Gets the duration of the animation.
+ *
+ * @return The length of the animation, in milliseconds.
+ */
+ public abstract long getDuration();
+
+ /**
+ * Gets the total duration of the animation, accounting for animation sequences, start delay,
+ * and repeating. Return {@link #DURATION_INFINITE} if the duration is infinite.
+ *
+ * @return Total time an animation takes to finish, starting from the time {@link #start()}
+ * is called. {@link #DURATION_INFINITE} will be returned if the animation or any
+ * child animation repeats infinite times.
+ */
+ public long getTotalDuration() {
+ long duration = getDuration();
+ if (duration == DURATION_INFINITE) {
+ return DURATION_INFINITE;
+ } else {
+ return getStartDelay() + duration;
+ }
+ }
+
+ /**
+ * The time interpolator used in calculating the elapsed fraction of the
+ * animation. The interpolator determines whether the animation runs with
+ * linear or non-linear motion, such as acceleration and deceleration. The
+ * default value is {@link android.view.animation.AccelerateDecelerateInterpolator}.
+ *
+ * @param value the interpolator to be used by this animation
+ */
+ public abstract void setInterpolator(TimeInterpolator value);
+
+ /**
+ * Returns the timing interpolator that this animation uses.
+ *
+ * @return The timing interpolator for this animation.
+ */
+ public TimeInterpolator getInterpolator() {
+ return null;
+ }
+
+ /**
+ * Returns whether this Animator is currently running (having been started and gone past any
+ * initial startDelay period and not yet ended).
+ *
+ * @return Whether the Animator is running.
+ */
+ public abstract boolean isRunning();
+
+ /**
+ * Returns whether this Animator has been started and not yet ended. For reusable
+ * Animators (which most Animators are, apart from the one-shot animator produced by
+ * {@link android.view.ViewAnimationUtils#createCircularReveal(
+ * android.view.View, int, int, float, float) createCircularReveal()}),
+ * this state is a superset of {@link #isRunning()}, because an Animator with a
+ * nonzero {@link #getStartDelay() startDelay} will return true for {@link #isStarted()} during
+ * the delay phase, whereas {@link #isRunning()} will return true only after the delay phase
+ * is complete. Non-reusable animators will always return true after they have been
+ * started, because they cannot return to a non-started state.
+ *
+ * @return Whether the Animator has been started and not yet ended.
+ */
+ public boolean isStarted() {
+ // Default method returns value for isRunning(). Subclasses should override to return a
+ // real value.
+ return isRunning();
+ }
+
+ /**
+ * Adds a listener to the set of listeners that are sent events through the life of an
+ * animation, such as start, repeat, and end.
+ *
+ * @param listener the listener to be added to the current set of listeners for this animation.
+ */
+ public void addListener(AnimatorListener listener) {
+ if (mListeners == null) {
+ mListeners = new ArrayList<AnimatorListener>();
+ }
+ mListeners.add(listener);
+ }
+
+ /**
+ * Removes a listener from the set listening to this animation.
+ *
+ * @param listener the listener to be removed from the current set of listeners for this
+ * animation.
+ */
+ public void removeListener(AnimatorListener listener) {
+ if (mListeners == null) {
+ return;
+ }
+ mListeners.remove(listener);
+ if (mListeners.size() == 0) {
+ mListeners = null;
+ }
+ }
+
+ /**
+ * Gets the set of {@link android.animation.Animator.AnimatorListener} objects that are currently
+ * listening for events on this <code>Animator</code> object.
+ *
+ * @return ArrayList<AnimatorListener> The set of listeners.
+ */
+ public ArrayList<AnimatorListener> getListeners() {
+ return mListeners;
+ }
+
+ /**
+ * Adds a pause listener to this animator.
+ *
+ * @param listener the listener to be added to the current set of pause listeners
+ * for this animation.
+ */
+ public void addPauseListener(AnimatorPauseListener listener) {
+ if (mPauseListeners == null) {
+ mPauseListeners = new ArrayList<AnimatorPauseListener>();
+ }
+ mPauseListeners.add(listener);
+ }
+
+ /**
+ * Removes a pause listener from the set listening to this animation.
+ *
+ * @param listener the listener to be removed from the current set of pause
+ * listeners for this animation.
+ */
+ public void removePauseListener(AnimatorPauseListener listener) {
+ if (mPauseListeners == null) {
+ return;
+ }
+ mPauseListeners.remove(listener);
+ if (mPauseListeners.size() == 0) {
+ mPauseListeners = null;
+ }
+ }
+
+ /**
+ * Removes all {@link #addListener(android.animation.Animator.AnimatorListener) listeners}
+ * and {@link #addPauseListener(android.animation.Animator.AnimatorPauseListener)
+ * pauseListeners} from this object.
+ */
+ public void removeAllListeners() {
+ if (mListeners != null) {
+ mListeners.clear();
+ mListeners = null;
+ }
+ if (mPauseListeners != null) {
+ mPauseListeners.clear();
+ mPauseListeners = null;
+ }
+ }
+
+ /**
+ * Return a mask of the configuration parameters for which this animator may change, requiring
+ * that it should be re-created from Resources. The default implementation returns whatever
+ * value was provided through setChangingConfigurations(int) or 0 by default.
+ *
+ * @return Returns a mask of the changing configuration parameters, as defined by
+ * {@link android.content.pm.ActivityInfo}.
+ * @see android.content.pm.ActivityInfo
+ * @hide
+ */
+ public @Config int getChangingConfigurations() {
+ return mChangingConfigurations;
+ }
+
+ /**
+ * Set a mask of the configuration parameters for which this animator may change, requiring
+ * that it be re-created from resource.
+ *
+ * @param configs A mask of the changing configuration parameters, as
+ * defined by {@link android.content.pm.ActivityInfo}.
+ *
+ * @see android.content.pm.ActivityInfo
+ * @hide
+ */
+ public void setChangingConfigurations(@Config int configs) {
+ mChangingConfigurations = configs;
+ }
+
+ /**
+ * Sets the changing configurations value to the union of the current changing configurations
+ * and the provided configs.
+ * This method is called while loading the animator.
+ * @hide
+ */
+ public void appendChangingConfigurations(@Config int configs) {
+ mChangingConfigurations |= configs;
+ }
+
+ /**
+ * Return a {@link android.content.res.ConstantState} instance that holds the shared state of
+ * this Animator.
+ * <p>
+ * This constant state is used to create new instances of this animator when needed, instead
+ * of re-loading it from resources. Default implementation creates a new
+ * {@link AnimatorConstantState}. You can override this method to provide your custom logic or
+ * return null if you don't want this animator to be cached.
+ *
+ * @return The ConfigurationBoundResourceCache.BaseConstantState associated to this Animator.
+ * @see android.content.res.ConstantState
+ * @see #clone()
+ * @hide
+ */
+ public ConstantState<Animator> createConstantState() {
+ return new AnimatorConstantState(this);
+ }
+
+ @Override
+ public Animator clone() {
+ try {
+ final Animator anim = (Animator) super.clone();
+ if (mListeners != null) {
+ anim.mListeners = new ArrayList<AnimatorListener>(mListeners);
+ }
+ if (mPauseListeners != null) {
+ anim.mPauseListeners = new ArrayList<AnimatorPauseListener>(mPauseListeners);
+ }
+ anim.mCachedList.set(null);
+ anim.mStartListenersCalled = false;
+ return anim;
+ } catch (CloneNotSupportedException e) {
+ throw new AssertionError();
+ }
+ }
+
+ /**
+ * This method tells the object to use appropriate information to extract
+ * starting values for the animation. For example, a AnimatorSet object will pass
+ * this call to its child objects to tell them to set up the values. A
+ * ObjectAnimator object will use the information it has about its target object
+ * and PropertyValuesHolder objects to get the start values for its properties.
+ * A ValueAnimator object will ignore the request since it does not have enough
+ * information (such as a target object) to gather these values.
+ */
+ public void setupStartValues() {
+ }
+
+ /**
+ * This method tells the object to use appropriate information to extract
+ * ending values for the animation. For example, a AnimatorSet object will pass
+ * this call to its child objects to tell them to set up the values. A
+ * ObjectAnimator object will use the information it has about its target object
+ * and PropertyValuesHolder objects to get the start values for its properties.
+ * A ValueAnimator object will ignore the request since it does not have enough
+ * information (such as a target object) to gather these values.
+ */
+ public void setupEndValues() {
+ }
+
+ /**
+ * Sets the target object whose property will be animated by this animation. Not all subclasses
+ * operate on target objects (for example, {@link ValueAnimator}, but this method
+ * is on the superclass for the convenience of dealing generically with those subclasses
+ * that do handle targets.
+ * <p>
+ * <strong>Note:</strong> The target is stored as a weak reference internally to avoid leaking
+ * resources by having animators directly reference old targets. Therefore, you should
+ * ensure that animator targets always have a hard reference elsewhere.
+ *
+ * @param target The object being animated
+ */
+ public void setTarget(@Nullable Object target) {
+ }
+
+ // Hide reverse() and canReverse() for now since reverse() only work for simple
+ // cases, like we don't support sequential, neither startDelay.
+ // TODO: make reverse() works for all the Animators.
+ /**
+ * @hide
+ */
+ public boolean canReverse() {
+ return false;
+ }
+
+ /**
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public void reverse() {
+ throw new IllegalStateException("Reverse is not supported");
+ }
+
+ // Pulse an animation frame into the animation.
+ boolean pulseAnimationFrame(long frameTime) {
+ // TODO: Need to find a better signal than this. There's a bug in SystemUI that's preventing
+ // returning !isStarted() from working.
+ return false;
+ }
+
+ /**
+ * Internal use only.
+ * This call starts the animation in regular or reverse direction without requiring them to
+ * register frame callbacks. The caller will be responsible for all the subsequent animation
+ * pulses. Specifically, the caller needs to call doAnimationFrame(...) for the animation on
+ * every frame.
+ *
+ * @param inReverse whether the animation should play in reverse direction
+ */
+ void startWithoutPulsing(boolean inReverse) {
+ if (inReverse) {
+ reverse();
+ } else {
+ start();
+ }
+ }
+
+ /**
+ * Internal use only.
+ * Skips the animation value to end/start, depending on whether the play direction is forward
+ * or backward.
+ *
+ * @param inReverse whether the end value is based on a reverse direction. If yes, this is
+ * equivalent to skip to start value in a forward playing direction.
+ */
+ void skipToEndValue(boolean inReverse) {}
+
+ /**
+ * Internal use only.
+ *
+ * Returns whether the animation has start/end values setup. For most of the animations, this
+ * should always be true. For ObjectAnimators, the start values are setup in the initialization
+ * of the animation.
+ */
+ boolean isInitialized() {
+ return true;
+ }
+
+ /**
+ * Internal use only. Changes the value of the animator as if currentPlayTime has passed since
+ * the start of the animation. Therefore, currentPlayTime includes the start delay, and any
+ * repetition. lastPlayTime is similar and is used to calculate how many repeats have been
+ * done between the two times.
+ */
+ void animateValuesInRange(long currentPlayTime, long lastPlayTime) {}
+
+ /**
+ * Internal use only. This animates any animation that has ended since lastPlayTime.
+ * If an animation hasn't been finished, no change will be made.
+ */
+ void animateSkipToEnds(long currentPlayTime, long lastPlayTime) {}
+
+ /**
+ * Internal use only. Adds all start times (after delay) to and end times to times.
+ * The value must include offset.
+ */
+ void getStartAndEndTimes(LongArray times, long offset) {
+ long startTime = offset + getStartDelay();
+ if (times.indexOf(startTime) < 0) {
+ times.add(startTime);
+ }
+ long duration = getTotalDuration();
+ if (duration != DURATION_INFINITE) {
+ long endTime = duration + offset;
+ if (times.indexOf(endTime) < 0) {
+ times.add(endTime);
+ }
+ }
+ }
+
+ /**
+ * Calls notification for each AnimatorListener.
+ *
+ * @param notification The notification method to call on each listener.
+ * @param isReverse When this is used with start/end, this is the isReverse parameter. For
+ * other calls, this is ignored.
+ */
+ void notifyListeners(
+ AnimatorCaller<AnimatorListener, Animator> notification,
+ boolean isReverse
+ ) {
+ callOnList(mListeners, notification, this, isReverse);
+ }
+
+ /**
+ * Call pause/resume on each AnimatorPauseListener.
+ *
+ * @param notification Either ON_PAUSE or ON_RESUME to call onPause or onResume on each
+ * listener.
+ */
+ void notifyPauseListeners(AnimatorCaller<AnimatorPauseListener, Animator> notification) {
+ callOnList(mPauseListeners, notification, this, false);
+ }
+
+ void notifyStartListeners(boolean isReversing) {
+ boolean startListenersCalled = mStartListenersCalled;
+ mStartListenersCalled = true;
+ if (mListeners != null && !startListenersCalled) {
+ notifyListeners(AnimatorCaller.ON_START, isReversing);
+ }
+ }
+
+ void notifyEndListeners(boolean isReversing) {
+ boolean startListenersCalled = mStartListenersCalled;
+ mStartListenersCalled = false;
+ if (mListeners != null && startListenersCalled) {
+ notifyListeners(AnimatorCaller.ON_END, isReversing);
+ }
+ }
+
+ /**
+ * Calls <code>call</code> for every item in <code>list</code> with <code>animator</code> and
+ * <code>isReverse</code> as parameters.
+ *
+ * @param list The list of items to make calls on.
+ * @param call The method to call for each item in list.
+ * @param animator The animator parameter of call.
+ * @param isReverse The isReverse parameter of call.
+ * @param <T> The item type of list
+ * @param <A> The Animator type of animator.
+ */
+ <T, A> void callOnList(
+ ArrayList<T> list,
+ AnimatorCaller<T, A> call,
+ A animator,
+ boolean isReverse
+ ) {
+ int size = list == null ? 0 : list.size();
+ if (size > 0) {
+ // Try to reuse mCacheList to store the items of list.
+ Object[] array = mCachedList.getAndSet(null);
+ if (array == null || array.length < size) {
+ array = new Object[size];
+ }
+ list.toArray(array);
+ for (int i = 0; i < size; i++) {
+ //noinspection unchecked
+ T item = (T) array[i];
+ call.call(item, animator, isReverse);
+ array[i] = null;
+ }
+ // Store it for the next call so we can reuse this array, if needed.
+ mCachedList.compareAndSet(null, array);
+ }
+ }
+
+ /**
+ * <p>An animation listener receives notifications from an animation.
+ * Notifications indicate animation related events, such as the end or the
+ * repetition of the animation.</p>
+ */
+ public static interface AnimatorListener {
+
+ /**
+ * <p>Notifies the start of the animation as well as the animation's overall play direction.
+ * This method's default behavior is to call {@link #onAnimationStart(Animator)}. This
+ * method can be overridden, though not required, to get the additional play direction info
+ * when an animation starts. Skipping calling super when overriding this method results in
+ * {@link #onAnimationStart(Animator)} not getting called.
+ *
+ * @param animation The started animation.
+ * @param isReverse Whether the animation is playing in reverse.
+ */
+ default void onAnimationStart(@NonNull Animator animation, boolean isReverse) {
+ onAnimationStart(animation);
+ }
+
+ /**
+ * <p>Notifies the end of the animation. This callback is not invoked
+ * for animations with repeat count set to INFINITE.</p>
+ *
+ * <p>This method's default behavior is to call {@link #onAnimationEnd(Animator)}. This
+ * method can be overridden, though not required, to get the additional play direction info
+ * when an animation ends. Skipping calling super when overriding this method results in
+ * {@link #onAnimationEnd(Animator)} not getting called.
+ *
+ * @param animation The animation which reached its end.
+ * @param isReverse Whether the animation is playing in reverse.
+ */
+ default void onAnimationEnd(@NonNull Animator animation, boolean isReverse) {
+ onAnimationEnd(animation);
+ }
+
+ /**
+ * <p>Notifies the start of the animation.</p>
+ *
+ * @param animation The started animation.
+ */
+ void onAnimationStart(@NonNull Animator animation);
+
+ /**
+ * <p>Notifies the end of the animation. This callback is not invoked
+ * for animations with repeat count set to INFINITE.</p>
+ *
+ * @param animation The animation which reached its end.
+ */
+ void onAnimationEnd(@NonNull Animator animation);
+
+ /**
+ * <p>Notifies the cancellation of the animation. This callback is not invoked
+ * for animations with repeat count set to INFINITE.</p>
+ *
+ * @param animation The animation which was canceled.
+ */
+ void onAnimationCancel(@NonNull Animator animation);
+
+ /**
+ * <p>Notifies the repetition of the animation.</p>
+ *
+ * @param animation The animation which was repeated.
+ */
+ void onAnimationRepeat(@NonNull Animator animation);
+ }
+
+ /**
+ * A pause listener receives notifications from an animation when the
+ * animation is {@link #pause() paused} or {@link #resume() resumed}.
+ *
+ * @see #addPauseListener(AnimatorPauseListener)
+ */
+ public static interface AnimatorPauseListener {
+ /**
+ * <p>Notifies that the animation was paused.</p>
+ *
+ * @param animation The animaton being paused.
+ * @see #pause()
+ */
+ void onAnimationPause(@NonNull Animator animation);
+
+ /**
+ * <p>Notifies that the animation was resumed, after being
+ * previously paused.</p>
+ *
+ * @param animation The animation being resumed.
+ * @see #resume()
+ */
+ void onAnimationResume(@NonNull Animator animation);
+ }
+
+ /**
+ * <p>Whether or not the Animator is allowed to run asynchronously off of
+ * the UI thread. This is a hint that informs the Animator that it is
+ * OK to run the animation off-thread, however the Animator may decide
+ * that it must run the animation on the UI thread anyway.
+ *
+ * <p>Regardless of whether or not the animation runs asynchronously, all
+ * listener callbacks will be called on the UI thread.</p>
+ *
+ * <p>To be able to use this hint the following must be true:</p>
+ * <ol>
+ * <li>The animator is immutable while {@link #isStarted()} is true. Requests
+ * to change duration, delay, etc... may be ignored.</li>
+ * <li>Lifecycle callback events may be asynchronous. Events such as
+ * {@link Animator.AnimatorListener#onAnimationEnd(Animator)} or
+ * {@link Animator.AnimatorListener#onAnimationRepeat(Animator)} may end up delayed
+ * as they must be posted back to the UI thread, and any actions performed
+ * by those callbacks (such as starting new animations) will not happen
+ * in the same frame.</li>
+ * <li>State change requests ({@link #cancel()}, {@link #end()}, {@link #reverse()}, etc...)
+ * may be asynchronous. It is guaranteed that all state changes that are
+ * performed on the UI thread in the same frame will be applied as a single
+ * atomic update, however that frame may be the current frame,
+ * the next frame, or some future frame. This will also impact the observed
+ * state of the Animator. For example, {@link #isStarted()} may still return true
+ * after a call to {@link #end()}. Using the lifecycle callbacks is preferred over
+ * queries to {@link #isStarted()}, {@link #isRunning()}, and {@link #isPaused()}
+ * for this reason.</li>
+ * </ol>
+ * @hide
+ */
+ public void setAllowRunningAsynchronously(boolean mayRunAsync) {
+ // It is up to subclasses to support this, if they can.
+ }
+
+ /**
+ * Creates a {@link ConstantState} which holds changing configurations information associated
+ * with the given Animator.
+ * <p>
+ * When {@link #newInstance()} is called, default implementation clones the Animator.
+ */
+ private static class AnimatorConstantState extends ConstantState<Animator> {
+
+ final Animator mAnimator;
+ @Config int mChangingConf;
+
+ public AnimatorConstantState(Animator animator) {
+ mAnimator = animator;
+ // ensure a reference back to here so that constante state is not gc'ed.
+ mAnimator.mConstantState = this;
+ mChangingConf = mAnimator.getChangingConfigurations();
+ }
+
+ @Override
+ public @Config int getChangingConfigurations() {
+ return mChangingConf;
+ }
+
+ @Override
+ public Animator newInstance() {
+ final Animator clone = mAnimator.clone();
+ clone.mConstantState = this;
+ return clone;
+ }
+ }
+
+ /**
+ * Internally used by {@link #callOnList(ArrayList, AnimatorCaller, Object, boolean)} to
+ * make a call on all children of a list. This can be for start, stop, pause, cancel, update,
+ * etc notifications.
+ *
+ * @param <T> The type of listener to make the call on
+ * @param <A> The type of animator that is passed as a parameter
+ */
+ interface AnimatorCaller<T, A> {
+ void call(T listener, A animator, boolean isReverse);
+
+ AnimatorCaller<AnimatorListener, Animator> ON_START = AnimatorListener::onAnimationStart;
+ AnimatorCaller<AnimatorListener, Animator> ON_END = AnimatorListener::onAnimationEnd;
+ AnimatorCaller<AnimatorListener, Animator> ON_CANCEL =
+ (listener, animator, isReverse) -> listener.onAnimationCancel(animator);
+ AnimatorCaller<AnimatorListener, Animator> ON_REPEAT =
+ (listener, animator, isReverse) -> listener.onAnimationRepeat(animator);
+ AnimatorCaller<AnimatorPauseListener, Animator> ON_PAUSE =
+ (listener, animator, isReverse) -> listener.onAnimationPause(animator);
+ AnimatorCaller<AnimatorPauseListener, Animator> ON_RESUME =
+ (listener, animator, isReverse) -> listener.onAnimationResume(animator);
+ AnimatorCaller<ValueAnimator.AnimatorUpdateListener, ValueAnimator> ON_UPDATE =
+ (listener, animator, isReverse) -> listener.onAnimationUpdate(animator);
+ }
+}
diff --git a/android-35/android/animation/AnimatorInflater.java b/android-35/android/animation/AnimatorInflater.java
new file mode 100644
index 0000000..f67c68e
--- /dev/null
+++ b/android-35/android/animation/AnimatorInflater.java
@@ -0,0 +1,1085 @@
+/*
+ * Copyright (C) 2010 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.animation;
+
+import android.annotation.AnimatorRes;
+import android.annotation.AnyRes;
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.pm.ActivityInfo.Config;
+import android.content.res.ConfigurationBoundResourceCache;
+import android.content.res.ConstantState;
+import android.content.res.Resources;
+import android.content.res.Resources.NotFoundException;
+import android.content.res.Resources.Theme;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.graphics.Path;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.PathParser;
+import android.util.StateSet;
+import android.util.TypedValue;
+import android.util.Xml;
+import android.view.InflateException;
+import android.view.animation.AnimationUtils;
+import android.view.animation.BaseInterpolator;
+import android.view.animation.Interpolator;
+
+import com.android.internal.R;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+
+/**
+ * This class is used to instantiate animator XML files into Animator objects.
+ * <p>
+ * For performance reasons, inflation relies heavily on pre-processing of
+ * XML files that is done at build time. Therefore, it is not currently possible
+ * to use this inflater with an XmlPullParser over a plain XML file at runtime;
+ * it only works with an XmlPullParser returned from a compiled resource (R.
+ * <em>something</em> file.)
+ */
+public class AnimatorInflater {
+ private static final String TAG = "AnimatorInflater";
+ /**
+ * These flags are used when parsing AnimatorSet objects
+ */
+ private static final int TOGETHER = 0;
+ private static final int SEQUENTIALLY = 1;
+
+ /**
+ * Enum values used in XML attributes to indicate the value for mValueType
+ */
+ private static final int VALUE_TYPE_FLOAT = 0;
+ private static final int VALUE_TYPE_INT = 1;
+ private static final int VALUE_TYPE_PATH = 2;
+ private static final int VALUE_TYPE_COLOR = 3;
+ private static final int VALUE_TYPE_UNDEFINED = 4;
+
+ private static final boolean DBG_ANIMATOR_INFLATER = false;
+
+ // used to calculate changing configs for resource references
+ private static final TypedValue sTmpTypedValue = new TypedValue();
+
+ /**
+ * Loads an {@link Animator} object from a resource
+ *
+ * @param context Application context used to access resources
+ * @param id The resource id of the animation to load
+ * @return The animator object reference by the specified id
+ * @throws android.content.res.Resources.NotFoundException when the animation cannot be loaded
+ */
+ public static Animator loadAnimator(Context context, @AnimatorRes int id)
+ throws NotFoundException {
+ return loadAnimator(context.getResources(), context.getTheme(), id);
+ }
+
+ /**
+ * Loads an {@link Animator} object from a resource
+ *
+ * @param resources The resources
+ * @param theme The theme
+ * @param id The resource id of the animation to load
+ * @return The animator object reference by the specified id
+ * @throws android.content.res.Resources.NotFoundException when the animation cannot be loaded
+ * @hide
+ */
+ public static Animator loadAnimator(Resources resources, Theme theme, int id)
+ throws NotFoundException {
+ return loadAnimator(resources, theme, id, 1);
+ }
+
+ /** @hide */
+ public static Animator loadAnimator(Resources resources, Theme theme, int id,
+ float pathErrorScale) throws NotFoundException {
+ final ConfigurationBoundResourceCache<Animator> animatorCache = resources
+ .getAnimatorCache();
+ Animator animator = animatorCache.getInstance(id, resources, theme);
+ if (animator != null) {
+ if (DBG_ANIMATOR_INFLATER) {
+ Log.d(TAG, "loaded animator from cache, " + resources.getResourceName(id));
+ }
+ return animator;
+ } else if (DBG_ANIMATOR_INFLATER) {
+ Log.d(TAG, "cache miss for animator " + resources.getResourceName(id));
+ }
+ int cacheGeneration = animatorCache.getGeneration();
+ XmlResourceParser parser = null;
+ try {
+ parser = resources.getAnimation(id);
+ animator = createAnimatorFromXml(resources, theme, parser, pathErrorScale);
+ if (animator != null) {
+ animator.appendChangingConfigurations(getChangingConfigs(resources, id));
+ final ConstantState<Animator> constantState = animator.createConstantState();
+ if (constantState != null) {
+ if (DBG_ANIMATOR_INFLATER) {
+ Log.d(TAG, "caching animator for res " + resources.getResourceName(id));
+ }
+ animatorCache.put(id, theme, constantState, cacheGeneration);
+ // create a new animator so that cached version is never used by the user
+ animator = constantState.newInstance(resources, theme);
+ }
+ }
+ return animator;
+ } catch (XmlPullParserException ex) {
+ Resources.NotFoundException rnf =
+ new Resources.NotFoundException("Can't load animation resource ID #0x" +
+ Integer.toHexString(id));
+ rnf.initCause(ex);
+ throw rnf;
+ } catch (IOException ex) {
+ Resources.NotFoundException rnf =
+ new Resources.NotFoundException("Can't load animation resource ID #0x" +
+ Integer.toHexString(id));
+ rnf.initCause(ex);
+ throw rnf;
+ } finally {
+ if (parser != null) parser.close();
+ }
+ }
+
+ public static StateListAnimator loadStateListAnimator(Context context, int id)
+ throws NotFoundException {
+ final Resources resources = context.getResources();
+ final ConfigurationBoundResourceCache<StateListAnimator> cache = resources
+ .getStateListAnimatorCache();
+ final Theme theme = context.getTheme();
+ StateListAnimator animator = cache.getInstance(id, resources, theme);
+ if (animator != null) {
+ return animator;
+ }
+ int cacheGeneration = cache.getGeneration();
+ XmlResourceParser parser = null;
+ try {
+ parser = resources.getAnimation(id);
+ animator =
+ createStateListAnimatorFromXml(context, parser, Xml.asAttributeSet(parser));
+ if (animator != null) {
+ animator.appendChangingConfigurations(getChangingConfigs(resources, id));
+ final ConstantState<StateListAnimator> constantState = animator
+ .createConstantState();
+ if (constantState != null) {
+ cache.put(id, theme, constantState, cacheGeneration);
+ // return a clone so that the animator in constant state is never used.
+ animator = constantState.newInstance(resources, theme);
+ }
+ }
+ return animator;
+ } catch (XmlPullParserException ex) {
+ Resources.NotFoundException rnf =
+ new Resources.NotFoundException(
+ "Can't load state list animator resource ID #0x" +
+ Integer.toHexString(id)
+ );
+ rnf.initCause(ex);
+ throw rnf;
+ } catch (IOException ex) {
+ Resources.NotFoundException rnf =
+ new Resources.NotFoundException(
+ "Can't load state list animator resource ID #0x" +
+ Integer.toHexString(id)
+ );
+ rnf.initCause(ex);
+ throw rnf;
+ } finally {
+ if (parser != null) {
+ parser.close();
+ }
+ }
+ }
+
+ private static StateListAnimator createStateListAnimatorFromXml(Context context,
+ XmlPullParser parser, AttributeSet attributeSet)
+ throws IOException, XmlPullParserException {
+ int type;
+ StateListAnimator stateListAnimator = new StateListAnimator();
+
+ while (true) {
+ type = parser.next();
+ switch (type) {
+ case XmlPullParser.END_DOCUMENT:
+ case XmlPullParser.END_TAG:
+ return stateListAnimator;
+
+ case XmlPullParser.START_TAG:
+ // parse item
+ Animator animator = null;
+ if ("item".equals(parser.getName())) {
+ int attributeCount = parser.getAttributeCount();
+ int[] states = new int[attributeCount];
+ int stateIndex = 0;
+ for (int i = 0; i < attributeCount; i++) {
+ int attrName = attributeSet.getAttributeNameResource(i);
+ if (attrName == R.attr.animation) {
+ final int animId = attributeSet.getAttributeResourceValue(i, 0);
+ animator = loadAnimator(context, animId);
+ } else {
+ states[stateIndex++] =
+ attributeSet.getAttributeBooleanValue(i, false) ?
+ attrName : -attrName;
+ }
+ }
+ if (animator == null) {
+ animator = createAnimatorFromXml(context.getResources(),
+ context.getTheme(), parser, 1f);
+ }
+
+ if (animator == null) {
+ throw new Resources.NotFoundException(
+ "animation state item must have a valid animation");
+ }
+ stateListAnimator
+ .addState(StateSet.trimStateSet(states, stateIndex), animator);
+ }
+ break;
+ }
+ }
+ }
+
+ /**
+ * PathDataEvaluator is used to interpolate between two paths which are
+ * represented in the same format but different control points' values.
+ * The path is represented as verbs and points for each of the verbs.
+ */
+ private static class PathDataEvaluator implements TypeEvaluator<PathParser.PathData> {
+ private final PathParser.PathData mPathData = new PathParser.PathData();
+
+ @Override
+ public PathParser.PathData evaluate(float fraction, PathParser.PathData startPathData,
+ PathParser.PathData endPathData) {
+ if (!PathParser.interpolatePathData(mPathData, startPathData, endPathData, fraction)) {
+ throw new IllegalArgumentException("Can't interpolate between"
+ + " two incompatible pathData");
+ }
+ return mPathData;
+ }
+ }
+
+ private static PropertyValuesHolder getPVH(TypedArray styledAttributes, int valueType,
+ int valueFromId, int valueToId, String propertyName) {
+
+ TypedValue tvFrom = styledAttributes.peekValue(valueFromId);
+ boolean hasFrom = (tvFrom != null);
+ int fromType = hasFrom ? tvFrom.type : 0;
+ TypedValue tvTo = styledAttributes.peekValue(valueToId);
+ boolean hasTo = (tvTo != null);
+ int toType = hasTo ? tvTo.type : 0;
+
+ if (valueType == VALUE_TYPE_UNDEFINED) {
+ // Check whether it's color type. If not, fall back to default type (i.e. float type)
+ if ((hasFrom && isColorType(fromType)) || (hasTo && isColorType(toType))) {
+ valueType = VALUE_TYPE_COLOR;
+ } else {
+ valueType = VALUE_TYPE_FLOAT;
+ }
+ }
+
+ boolean getFloats = (valueType == VALUE_TYPE_FLOAT);
+
+ PropertyValuesHolder returnValue = null;
+
+ if (valueType == VALUE_TYPE_PATH) {
+ String fromString = styledAttributes.getString(valueFromId);
+ String toString = styledAttributes.getString(valueToId);
+ PathParser.PathData nodesFrom = fromString == null
+ ? null : new PathParser.PathData(fromString);
+ PathParser.PathData nodesTo = toString == null
+ ? null : new PathParser.PathData(toString);
+
+ if (nodesFrom != null || nodesTo != null) {
+ if (nodesFrom != null) {
+ TypeEvaluator evaluator = new PathDataEvaluator();
+ if (nodesTo != null) {
+ if (!PathParser.canMorph(nodesFrom, nodesTo)) {
+ throw new InflateException(" Can't morph from " + fromString + " to " +
+ toString);
+ }
+ returnValue = PropertyValuesHolder.ofObject(propertyName, evaluator,
+ nodesFrom, nodesTo);
+ } else {
+ returnValue = PropertyValuesHolder.ofObject(propertyName, evaluator,
+ (Object) nodesFrom);
+ }
+ } else if (nodesTo != null) {
+ TypeEvaluator evaluator = new PathDataEvaluator();
+ returnValue = PropertyValuesHolder.ofObject(propertyName, evaluator,
+ (Object) nodesTo);
+ }
+ }
+ } else {
+ TypeEvaluator evaluator = null;
+ // Integer and float value types are handled here.
+ if (valueType == VALUE_TYPE_COLOR) {
+ // special case for colors: ignore valueType and get ints
+ evaluator = ArgbEvaluator.getInstance();
+ }
+ if (getFloats) {
+ float valueFrom;
+ float valueTo;
+ if (hasFrom) {
+ if (fromType == TypedValue.TYPE_DIMENSION) {
+ valueFrom = styledAttributes.getDimension(valueFromId, 0f);
+ } else {
+ valueFrom = styledAttributes.getFloat(valueFromId, 0f);
+ }
+ if (hasTo) {
+ if (toType == TypedValue.TYPE_DIMENSION) {
+ valueTo = styledAttributes.getDimension(valueToId, 0f);
+ } else {
+ valueTo = styledAttributes.getFloat(valueToId, 0f);
+ }
+ returnValue = PropertyValuesHolder.ofFloat(propertyName,
+ valueFrom, valueTo);
+ } else {
+ returnValue = PropertyValuesHolder.ofFloat(propertyName, valueFrom);
+ }
+ } else {
+ if (toType == TypedValue.TYPE_DIMENSION) {
+ valueTo = styledAttributes.getDimension(valueToId, 0f);
+ } else {
+ valueTo = styledAttributes.getFloat(valueToId, 0f);
+ }
+ returnValue = PropertyValuesHolder.ofFloat(propertyName, valueTo);
+ }
+ } else {
+ int valueFrom;
+ int valueTo;
+ if (hasFrom) {
+ if (fromType == TypedValue.TYPE_DIMENSION) {
+ valueFrom = (int) styledAttributes.getDimension(valueFromId, 0f);
+ } else if (isColorType(fromType)) {
+ valueFrom = styledAttributes.getColor(valueFromId, 0);
+ } else {
+ valueFrom = styledAttributes.getInt(valueFromId, 0);
+ }
+ if (hasTo) {
+ if (toType == TypedValue.TYPE_DIMENSION) {
+ valueTo = (int) styledAttributes.getDimension(valueToId, 0f);
+ } else if (isColorType(toType)) {
+ valueTo = styledAttributes.getColor(valueToId, 0);
+ } else {
+ valueTo = styledAttributes.getInt(valueToId, 0);
+ }
+ returnValue = PropertyValuesHolder.ofInt(propertyName, valueFrom, valueTo);
+ } else {
+ returnValue = PropertyValuesHolder.ofInt(propertyName, valueFrom);
+ }
+ } else {
+ if (hasTo) {
+ if (toType == TypedValue.TYPE_DIMENSION) {
+ valueTo = (int) styledAttributes.getDimension(valueToId, 0f);
+ } else if (isColorType(toType)) {
+ valueTo = styledAttributes.getColor(valueToId, 0);
+ } else {
+ valueTo = styledAttributes.getInt(valueToId, 0);
+ }
+ returnValue = PropertyValuesHolder.ofInt(propertyName, valueTo);
+ }
+ }
+ }
+ if (returnValue != null && evaluator != null) {
+ returnValue.setEvaluator(evaluator);
+ }
+ }
+
+ return returnValue;
+ }
+
+ /**
+ * @param anim The animator, must not be null
+ * @param arrayAnimator Incoming typed array for Animator's attributes.
+ * @param arrayObjectAnimator Incoming typed array for Object Animator's
+ * attributes.
+ * @param pixelSize The relative pixel size, used to calculate the
+ * maximum error for path animations.
+ */
+ private static void parseAnimatorFromTypeArray(ValueAnimator anim,
+ TypedArray arrayAnimator, TypedArray arrayObjectAnimator, float pixelSize) {
+ long duration = arrayAnimator.getInt(R.styleable.Animator_duration, 300);
+
+ long startDelay = arrayAnimator.getInt(R.styleable.Animator_startOffset, 0);
+
+ int valueType = arrayAnimator.getInt(R.styleable.Animator_valueType, VALUE_TYPE_UNDEFINED);
+
+ if (valueType == VALUE_TYPE_UNDEFINED) {
+ valueType = inferValueTypeFromValues(arrayAnimator, R.styleable.Animator_valueFrom,
+ R.styleable.Animator_valueTo);
+ }
+ PropertyValuesHolder pvh = getPVH(arrayAnimator, valueType,
+ R.styleable.Animator_valueFrom, R.styleable.Animator_valueTo, "");
+ if (pvh != null) {
+ anim.setValues(pvh);
+ }
+
+ anim.setDuration(duration);
+ anim.setStartDelay(startDelay);
+
+ if (arrayAnimator.hasValue(R.styleable.Animator_repeatCount)) {
+ anim.setRepeatCount(
+ arrayAnimator.getInt(R.styleable.Animator_repeatCount, 0));
+ }
+ if (arrayAnimator.hasValue(R.styleable.Animator_repeatMode)) {
+ anim.setRepeatMode(
+ arrayAnimator.getInt(R.styleable.Animator_repeatMode,
+ ValueAnimator.RESTART));
+ }
+
+ if (arrayObjectAnimator != null) {
+ setupObjectAnimator(anim, arrayObjectAnimator, valueType, pixelSize);
+ }
+ }
+
+ /**
+ * Setup the Animator to achieve path morphing.
+ *
+ * @param anim The target Animator which will be updated.
+ * @param arrayAnimator TypedArray for the ValueAnimator.
+ * @return the PathDataEvaluator.
+ */
+ private static TypeEvaluator setupAnimatorForPath(ValueAnimator anim,
+ TypedArray arrayAnimator) {
+ TypeEvaluator evaluator = null;
+ String fromString = arrayAnimator.getString(R.styleable.Animator_valueFrom);
+ String toString = arrayAnimator.getString(R.styleable.Animator_valueTo);
+ PathParser.PathData pathDataFrom = fromString == null
+ ? null : new PathParser.PathData(fromString);
+ PathParser.PathData pathDataTo = toString == null
+ ? null : new PathParser.PathData(toString);
+
+ if (pathDataFrom != null) {
+ if (pathDataTo != null) {
+ anim.setObjectValues(pathDataFrom, pathDataTo);
+ if (!PathParser.canMorph(pathDataFrom, pathDataTo)) {
+ throw new InflateException(arrayAnimator.getPositionDescription()
+ + " Can't morph from " + fromString + " to " + toString);
+ }
+ } else {
+ anim.setObjectValues((Object)pathDataFrom);
+ }
+ evaluator = new PathDataEvaluator();
+ } else if (pathDataTo != null) {
+ anim.setObjectValues((Object)pathDataTo);
+ evaluator = new PathDataEvaluator();
+ }
+
+ if (DBG_ANIMATOR_INFLATER && evaluator != null) {
+ Log.v(TAG, "create a new PathDataEvaluator here");
+ }
+
+ return evaluator;
+ }
+
+ /**
+ * Setup ObjectAnimator's property or values from pathData.
+ *
+ * @param anim The target Animator which will be updated.
+ * @param arrayObjectAnimator TypedArray for the ObjectAnimator.
+ * @param getFloats True if the value type is float.
+ * @param pixelSize The relative pixel size, used to calculate the
+ * maximum error for path animations.
+ */
+ private static void setupObjectAnimator(ValueAnimator anim, TypedArray arrayObjectAnimator,
+ int valueType, float pixelSize) {
+ ObjectAnimator oa = (ObjectAnimator) anim;
+ String pathData = arrayObjectAnimator.getString(R.styleable.PropertyAnimator_pathData);
+
+ // Path can be involved in an ObjectAnimator in the following 3 ways:
+ // 1) Path morphing: the property to be animated is pathData, and valueFrom and valueTo
+ // are both of pathType. valueType = pathType needs to be explicitly defined.
+ // 2) A property in X or Y dimension can be animated along a path: the property needs to be
+ // defined in propertyXName or propertyYName attribute, the path will be defined in the
+ // pathData attribute. valueFrom and valueTo will not be necessary for this animation.
+ // 3) PathInterpolator can also define a path (in pathData) for its interpolation curve.
+ // Here we are dealing with case 2:
+ if (pathData != null) {
+ String propertyXName =
+ arrayObjectAnimator.getString(R.styleable.PropertyAnimator_propertyXName);
+ String propertyYName =
+ arrayObjectAnimator.getString(R.styleable.PropertyAnimator_propertyYName);
+
+ if (valueType == VALUE_TYPE_PATH || valueType == VALUE_TYPE_UNDEFINED) {
+ // When pathData is defined, we are in case #2 mentioned above. ValueType can only
+ // be float type, or int type. Otherwise we fallback to default type.
+ valueType = VALUE_TYPE_FLOAT;
+ }
+ if (propertyXName == null && propertyYName == null) {
+ throw new InflateException(arrayObjectAnimator.getPositionDescription()
+ + " propertyXName or propertyYName is needed for PathData");
+ } else {
+ Path path = PathParser.createPathFromPathData(pathData);
+ float error = 0.5f * pixelSize; // max half a pixel error
+ PathKeyframes keyframeSet = KeyframeSet.ofPath(path, error);
+ Keyframes xKeyframes;
+ Keyframes yKeyframes;
+ if (valueType == VALUE_TYPE_FLOAT) {
+ xKeyframes = keyframeSet.createXFloatKeyframes();
+ yKeyframes = keyframeSet.createYFloatKeyframes();
+ } else {
+ xKeyframes = keyframeSet.createXIntKeyframes();
+ yKeyframes = keyframeSet.createYIntKeyframes();
+ }
+ PropertyValuesHolder x = null;
+ PropertyValuesHolder y = null;
+ if (propertyXName != null) {
+ x = PropertyValuesHolder.ofKeyframes(propertyXName, xKeyframes);
+ }
+ if (propertyYName != null) {
+ y = PropertyValuesHolder.ofKeyframes(propertyYName, yKeyframes);
+ }
+ if (x == null) {
+ oa.setValues(y);
+ } else if (y == null) {
+ oa.setValues(x);
+ } else {
+ oa.setValues(x, y);
+ }
+ }
+ } else {
+ String propertyName =
+ arrayObjectAnimator.getString(R.styleable.PropertyAnimator_propertyName);
+ oa.setPropertyName(propertyName);
+ }
+ }
+
+ /**
+ * Setup ValueAnimator's values.
+ * This will handle all of the integer, float and color types.
+ *
+ * @param anim The target Animator which will be updated.
+ * @param arrayAnimator TypedArray for the ValueAnimator.
+ * @param getFloats True if the value type is float.
+ * @param hasFrom True if "valueFrom" exists.
+ * @param fromType The type of "valueFrom".
+ * @param hasTo True if "valueTo" exists.
+ * @param toType The type of "valueTo".
+ */
+ private static void setupValues(ValueAnimator anim, TypedArray arrayAnimator,
+ boolean getFloats, boolean hasFrom, int fromType, boolean hasTo, int toType) {
+ int valueFromIndex = R.styleable.Animator_valueFrom;
+ int valueToIndex = R.styleable.Animator_valueTo;
+ if (getFloats) {
+ float valueFrom;
+ float valueTo;
+ if (hasFrom) {
+ if (fromType == TypedValue.TYPE_DIMENSION) {
+ valueFrom = arrayAnimator.getDimension(valueFromIndex, 0f);
+ } else {
+ valueFrom = arrayAnimator.getFloat(valueFromIndex, 0f);
+ }
+ if (hasTo) {
+ if (toType == TypedValue.TYPE_DIMENSION) {
+ valueTo = arrayAnimator.getDimension(valueToIndex, 0f);
+ } else {
+ valueTo = arrayAnimator.getFloat(valueToIndex, 0f);
+ }
+ anim.setFloatValues(valueFrom, valueTo);
+ } else {
+ anim.setFloatValues(valueFrom);
+ }
+ } else {
+ if (toType == TypedValue.TYPE_DIMENSION) {
+ valueTo = arrayAnimator.getDimension(valueToIndex, 0f);
+ } else {
+ valueTo = arrayAnimator.getFloat(valueToIndex, 0f);
+ }
+ anim.setFloatValues(valueTo);
+ }
+ } else {
+ int valueFrom;
+ int valueTo;
+ if (hasFrom) {
+ if (fromType == TypedValue.TYPE_DIMENSION) {
+ valueFrom = (int) arrayAnimator.getDimension(valueFromIndex, 0f);
+ } else if (isColorType(fromType)) {
+ valueFrom = arrayAnimator.getColor(valueFromIndex, 0);
+ } else {
+ valueFrom = arrayAnimator.getInt(valueFromIndex, 0);
+ }
+ if (hasTo) {
+ if (toType == TypedValue.TYPE_DIMENSION) {
+ valueTo = (int) arrayAnimator.getDimension(valueToIndex, 0f);
+ } else if (isColorType(toType)) {
+ valueTo = arrayAnimator.getColor(valueToIndex, 0);
+ } else {
+ valueTo = arrayAnimator.getInt(valueToIndex, 0);
+ }
+ anim.setIntValues(valueFrom, valueTo);
+ } else {
+ anim.setIntValues(valueFrom);
+ }
+ } else {
+ if (hasTo) {
+ if (toType == TypedValue.TYPE_DIMENSION) {
+ valueTo = (int) arrayAnimator.getDimension(valueToIndex, 0f);
+ } else if (isColorType(toType)) {
+ valueTo = arrayAnimator.getColor(valueToIndex, 0);
+ } else {
+ valueTo = arrayAnimator.getInt(valueToIndex, 0);
+ }
+ anim.setIntValues(valueTo);
+ }
+ }
+ }
+ }
+
+ private static Animator createAnimatorFromXml(Resources res, Theme theme, XmlPullParser parser,
+ float pixelSize)
+ throws XmlPullParserException, IOException {
+ return createAnimatorFromXml(res, theme, parser, Xml.asAttributeSet(parser), null, 0,
+ pixelSize);
+ }
+
+ private static Animator createAnimatorFromXml(Resources res, Theme theme, XmlPullParser parser,
+ AttributeSet attrs, AnimatorSet parent, int sequenceOrdering, float pixelSize)
+ throws XmlPullParserException, IOException {
+ Animator anim = null;
+ ArrayList<Animator> childAnims = null;
+
+ // Make sure we are on a start tag.
+ int type;
+ int depth = parser.getDepth();
+
+ while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
+ && type != XmlPullParser.END_DOCUMENT) {
+
+ if (type != XmlPullParser.START_TAG) {
+ continue;
+ }
+
+ String name = parser.getName();
+ boolean gotValues = false;
+
+ if (name.equals("objectAnimator")) {
+ anim = loadObjectAnimator(res, theme, attrs, pixelSize);
+ } else if (name.equals("animator")) {
+ anim = loadAnimator(res, theme, attrs, null, pixelSize);
+ } else if (name.equals("set")) {
+ anim = new AnimatorSet();
+ TypedArray a;
+ if (theme != null) {
+ a = theme.obtainStyledAttributes(attrs, R.styleable.AnimatorSet, 0, 0);
+ } else {
+ a = res.obtainAttributes(attrs, R.styleable.AnimatorSet);
+ }
+ anim.appendChangingConfigurations(a.getChangingConfigurations());
+ int ordering = a.getInt(R.styleable.AnimatorSet_ordering, TOGETHER);
+ createAnimatorFromXml(res, theme, parser, attrs, (AnimatorSet) anim, ordering,
+ pixelSize);
+ a.recycle();
+ } else if (name.equals("propertyValuesHolder")) {
+ PropertyValuesHolder[] values = loadValues(res, theme, parser,
+ Xml.asAttributeSet(parser));
+ if (values != null && anim != null && (anim instanceof ValueAnimator)) {
+ ((ValueAnimator) anim).setValues(values);
+ }
+ gotValues = true;
+ } else {
+ throw new RuntimeException("Unknown animator name: " + parser.getName());
+ }
+
+ if (parent != null && !gotValues) {
+ if (childAnims == null) {
+ childAnims = new ArrayList<Animator>();
+ }
+ childAnims.add(anim);
+ }
+ }
+ if (parent != null && childAnims != null) {
+ Animator[] animsArray = new Animator[childAnims.size()];
+ int index = 0;
+ for (Animator a : childAnims) {
+ animsArray[index++] = a;
+ }
+ if (sequenceOrdering == TOGETHER) {
+ parent.playTogether(animsArray);
+ } else {
+ parent.playSequentially(animsArray);
+ }
+ }
+ return anim;
+ }
+
+ private static PropertyValuesHolder[] loadValues(Resources res, Theme theme,
+ XmlPullParser parser, AttributeSet attrs) throws XmlPullParserException, IOException {
+ ArrayList<PropertyValuesHolder> values = null;
+
+ int type;
+ while ((type = parser.getEventType()) != XmlPullParser.END_TAG &&
+ type != XmlPullParser.END_DOCUMENT) {
+
+ if (type != XmlPullParser.START_TAG) {
+ parser.next();
+ continue;
+ }
+
+ String name = parser.getName();
+
+ if (name.equals("propertyValuesHolder")) {
+ TypedArray a;
+ if (theme != null) {
+ a = theme.obtainStyledAttributes(attrs, R.styleable.PropertyValuesHolder, 0, 0);
+ } else {
+ a = res.obtainAttributes(attrs, R.styleable.PropertyValuesHolder);
+ }
+ String propertyName = a.getString(R.styleable.PropertyValuesHolder_propertyName);
+ int valueType = a.getInt(R.styleable.PropertyValuesHolder_valueType,
+ VALUE_TYPE_UNDEFINED);
+
+ PropertyValuesHolder pvh = loadPvh(res, theme, parser, propertyName, valueType);
+ if (pvh == null) {
+ pvh = getPVH(a, valueType,
+ R.styleable.PropertyValuesHolder_valueFrom,
+ R.styleable.PropertyValuesHolder_valueTo, propertyName);
+ }
+ if (pvh != null) {
+ if (values == null) {
+ values = new ArrayList<PropertyValuesHolder>();
+ }
+ values.add(pvh);
+ }
+ a.recycle();
+ }
+
+ parser.next();
+ }
+
+ PropertyValuesHolder[] valuesArray = null;
+ if (values != null) {
+ int count = values.size();
+ valuesArray = new PropertyValuesHolder[count];
+ for (int i = 0; i < count; ++i) {
+ valuesArray[i] = values.get(i);
+ }
+ }
+ return valuesArray;
+ }
+
+ // When no value type is provided in keyframe, we need to infer the type from the value. i.e.
+ // if value is defined in the style of a color value, then the color type is returned.
+ // Otherwise, default float type is returned.
+ private static int inferValueTypeOfKeyframe(Resources res, Theme theme, AttributeSet attrs) {
+ int valueType;
+ TypedArray a;
+ if (theme != null) {
+ a = theme.obtainStyledAttributes(attrs, R.styleable.Keyframe, 0, 0);
+ } else {
+ a = res.obtainAttributes(attrs, R.styleable.Keyframe);
+ }
+
+ TypedValue keyframeValue = a.peekValue(R.styleable.Keyframe_value);
+ boolean hasValue = (keyframeValue != null);
+ // When no value type is provided, check whether it's a color type first.
+ // If not, fall back to default value type (i.e. float type).
+ if (hasValue && isColorType(keyframeValue.type)) {
+ valueType = VALUE_TYPE_COLOR;
+ } else {
+ valueType = VALUE_TYPE_FLOAT;
+ }
+ a.recycle();
+ return valueType;
+ }
+
+ private static int inferValueTypeFromValues(TypedArray styledAttributes, int valueFromId,
+ int valueToId) {
+ TypedValue tvFrom = styledAttributes.peekValue(valueFromId);
+ boolean hasFrom = (tvFrom != null);
+ int fromType = hasFrom ? tvFrom.type : 0;
+ TypedValue tvTo = styledAttributes.peekValue(valueToId);
+ boolean hasTo = (tvTo != null);
+ int toType = hasTo ? tvTo.type : 0;
+
+ int valueType;
+ // Check whether it's color type. If not, fall back to default type (i.e. float type)
+ if ((hasFrom && isColorType(fromType)) || (hasTo && isColorType(toType))) {
+ valueType = VALUE_TYPE_COLOR;
+ } else {
+ valueType = VALUE_TYPE_FLOAT;
+ }
+ return valueType;
+ }
+
+ private static void dumpKeyframes(Object[] keyframes, String header) {
+ if (keyframes == null || keyframes.length == 0) {
+ return;
+ }
+ Log.d(TAG, header);
+ int count = keyframes.length;
+ for (int i = 0; i < count; ++i) {
+ Keyframe keyframe = (Keyframe) keyframes[i];
+ Log.d(TAG, "Keyframe " + i + ": fraction " +
+ (keyframe.getFraction() < 0 ? "null" : keyframe.getFraction()) + ", " +
+ ", value : " + ((keyframe.hasValue()) ? keyframe.getValue() : "null"));
+ }
+ }
+
+ // Load property values holder if there are keyframes defined in it. Otherwise return null.
+ private static PropertyValuesHolder loadPvh(Resources res, Theme theme, XmlPullParser parser,
+ String propertyName, int valueType)
+ throws XmlPullParserException, IOException {
+
+ PropertyValuesHolder value = null;
+ ArrayList<Keyframe> keyframes = null;
+
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_TAG &&
+ type != XmlPullParser.END_DOCUMENT) {
+ String name = parser.getName();
+ if (name.equals("keyframe")) {
+ if (valueType == VALUE_TYPE_UNDEFINED) {
+ valueType = inferValueTypeOfKeyframe(res, theme, Xml.asAttributeSet(parser));
+ }
+ Keyframe keyframe = loadKeyframe(res, theme, Xml.asAttributeSet(parser), valueType);
+ if (keyframe != null) {
+ if (keyframes == null) {
+ keyframes = new ArrayList<Keyframe>();
+ }
+ keyframes.add(keyframe);
+ }
+ parser.next();
+ }
+ }
+
+ int count;
+ if (keyframes != null && (count = keyframes.size()) > 0) {
+ // make sure we have keyframes at 0 and 1
+ // If we have keyframes with set fractions, add keyframes at start/end
+ // appropriately. If start/end have no set fractions:
+ // if there's only one keyframe, set its fraction to 1 and add one at 0
+ // if >1 keyframe, set the last fraction to 1, the first fraction to 0
+ Keyframe firstKeyframe = keyframes.get(0);
+ Keyframe lastKeyframe = keyframes.get(count - 1);
+ float endFraction = lastKeyframe.getFraction();
+ if (endFraction < 1) {
+ if (endFraction < 0) {
+ lastKeyframe.setFraction(1);
+ } else {
+ keyframes.add(keyframes.size(), createNewKeyframe(lastKeyframe, 1));
+ ++count;
+ }
+ }
+ float startFraction = firstKeyframe.getFraction();
+ if (startFraction != 0) {
+ if (startFraction < 0) {
+ firstKeyframe.setFraction(0);
+ } else {
+ keyframes.add(0, createNewKeyframe(firstKeyframe, 0));
+ ++count;
+ }
+ }
+ Keyframe[] keyframeArray = new Keyframe[count];
+ keyframes.toArray(keyframeArray);
+ for (int i = 0; i < count; ++i) {
+ Keyframe keyframe = keyframeArray[i];
+ if (keyframe.getFraction() < 0) {
+ if (i == 0) {
+ keyframe.setFraction(0);
+ } else if (i == count - 1) {
+ keyframe.setFraction(1);
+ } else {
+ // figure out the start/end parameters of the current gap
+ // in fractions and distribute the gap among those keyframes
+ int startIndex = i;
+ int endIndex = i;
+ for (int j = startIndex + 1; j < count - 1; ++j) {
+ if (keyframeArray[j].getFraction() >= 0) {
+ break;
+ }
+ endIndex = j;
+ }
+ float gap = keyframeArray[endIndex + 1].getFraction() -
+ keyframeArray[startIndex - 1].getFraction();
+ distributeKeyframes(keyframeArray, gap, startIndex, endIndex);
+ }
+ }
+ }
+ value = PropertyValuesHolder.ofKeyframe(propertyName, keyframeArray);
+ if (valueType == VALUE_TYPE_COLOR) {
+ value.setEvaluator(ArgbEvaluator.getInstance());
+ }
+ }
+
+ return value;
+ }
+
+ private static Keyframe createNewKeyframe(Keyframe sampleKeyframe, float fraction) {
+ return sampleKeyframe.getType() == float.class ?
+ Keyframe.ofFloat(fraction) :
+ (sampleKeyframe.getType() == int.class) ?
+ Keyframe.ofInt(fraction) :
+ Keyframe.ofObject(fraction);
+ }
+
+ /**
+ * Utility function to set fractions on keyframes to cover a gap in which the
+ * fractions are not currently set. Keyframe fractions will be distributed evenly
+ * in this gap. For example, a gap of 1 keyframe in the range 0-1 will be at .5, a gap
+ * of .6 spread between two keyframes will be at .2 and .4 beyond the fraction at the
+ * keyframe before startIndex.
+ * Assumptions:
+ * - First and last keyframe fractions (bounding this spread) are already set. So,
+ * for example, if no fractions are set, we will already set first and last keyframe
+ * fraction values to 0 and 1.
+ * - startIndex must be >0 (which follows from first assumption).
+ * - endIndex must be >= startIndex.
+ *
+ * @param keyframes the array of keyframes
+ * @param gap The total gap we need to distribute
+ * @param startIndex The index of the first keyframe whose fraction must be set
+ * @param endIndex The index of the last keyframe whose fraction must be set
+ */
+ private static void distributeKeyframes(Keyframe[] keyframes, float gap,
+ int startIndex, int endIndex) {
+ int count = endIndex - startIndex + 2;
+ float increment = gap / count;
+ for (int i = startIndex; i <= endIndex; ++i) {
+ keyframes[i].setFraction(keyframes[i-1].getFraction() + increment);
+ }
+ }
+
+ private static Keyframe loadKeyframe(Resources res, Theme theme, AttributeSet attrs,
+ int valueType)
+ throws XmlPullParserException, IOException {
+
+ TypedArray a;
+ if (theme != null) {
+ a = theme.obtainStyledAttributes(attrs, R.styleable.Keyframe, 0, 0);
+ } else {
+ a = res.obtainAttributes(attrs, R.styleable.Keyframe);
+ }
+
+ Keyframe keyframe = null;
+
+ float fraction = a.getFloat(R.styleable.Keyframe_fraction, -1);
+
+ TypedValue keyframeValue = a.peekValue(R.styleable.Keyframe_value);
+ boolean hasValue = (keyframeValue != null);
+ if (valueType == VALUE_TYPE_UNDEFINED) {
+ // When no value type is provided, check whether it's a color type first.
+ // If not, fall back to default value type (i.e. float type).
+ if (hasValue && isColorType(keyframeValue.type)) {
+ valueType = VALUE_TYPE_COLOR;
+ } else {
+ valueType = VALUE_TYPE_FLOAT;
+ }
+ }
+
+ if (hasValue) {
+ switch (valueType) {
+ case VALUE_TYPE_FLOAT:
+ float value = a.getFloat(R.styleable.Keyframe_value, 0);
+ keyframe = Keyframe.ofFloat(fraction, value);
+ break;
+ case VALUE_TYPE_COLOR:
+ case VALUE_TYPE_INT:
+ int intValue = a.getInt(R.styleable.Keyframe_value, 0);
+ keyframe = Keyframe.ofInt(fraction, intValue);
+ break;
+ }
+ } else {
+ keyframe = (valueType == VALUE_TYPE_FLOAT) ? Keyframe.ofFloat(fraction) :
+ Keyframe.ofInt(fraction);
+ }
+
+ final int resID = a.getResourceId(R.styleable.Keyframe_interpolator, 0);
+ if (resID > 0) {
+ final Interpolator interpolator = AnimationUtils.loadInterpolator(res, theme, resID);
+ keyframe.setInterpolator(interpolator);
+ }
+ a.recycle();
+
+ return keyframe;
+ }
+
+ private static ObjectAnimator loadObjectAnimator(Resources res, Theme theme, AttributeSet attrs,
+ float pathErrorScale) throws NotFoundException {
+ ObjectAnimator anim = new ObjectAnimator();
+
+ loadAnimator(res, theme, attrs, anim, pathErrorScale);
+
+ return anim;
+ }
+
+ /**
+ * Creates a new animation whose parameters come from the specified context
+ * and attributes set.
+ *
+ * @param res The resources
+ * @param attrs The set of attributes holding the animation parameters
+ * @param anim Null if this is a ValueAnimator, otherwise this is an
+ * ObjectAnimator
+ */
+ private static ValueAnimator loadAnimator(Resources res, Theme theme,
+ AttributeSet attrs, ValueAnimator anim, float pathErrorScale)
+ throws NotFoundException {
+ TypedArray arrayAnimator = null;
+ TypedArray arrayObjectAnimator = null;
+
+ if (theme != null) {
+ arrayAnimator = theme.obtainStyledAttributes(attrs, R.styleable.Animator, 0, 0);
+ } else {
+ arrayAnimator = res.obtainAttributes(attrs, R.styleable.Animator);
+ }
+
+ // If anim is not null, then it is an object animator.
+ if (anim != null) {
+ if (theme != null) {
+ arrayObjectAnimator = theme.obtainStyledAttributes(attrs,
+ R.styleable.PropertyAnimator, 0, 0);
+ } else {
+ arrayObjectAnimator = res.obtainAttributes(attrs, R.styleable.PropertyAnimator);
+ }
+ anim.appendChangingConfigurations(arrayObjectAnimator.getChangingConfigurations());
+ }
+
+ if (anim == null) {
+ anim = new ValueAnimator();
+ }
+ anim.appendChangingConfigurations(arrayAnimator.getChangingConfigurations());
+
+ parseAnimatorFromTypeArray(anim, arrayAnimator, arrayObjectAnimator, pathErrorScale);
+
+ final int resID = arrayAnimator.getResourceId(R.styleable.Animator_interpolator, 0);
+ if (resID > 0) {
+ final Interpolator interpolator = AnimationUtils.loadInterpolator(res, theme, resID);
+ if (interpolator instanceof BaseInterpolator) {
+ anim.appendChangingConfigurations(
+ ((BaseInterpolator) interpolator).getChangingConfiguration());
+ }
+ anim.setInterpolator(interpolator);
+ }
+
+ arrayAnimator.recycle();
+ if (arrayObjectAnimator != null) {
+ arrayObjectAnimator.recycle();
+ }
+ return anim;
+ }
+
+ private static @Config int getChangingConfigs(@NonNull Resources resources, @AnyRes int id) {
+ synchronized (sTmpTypedValue) {
+ resources.getValue(id, sTmpTypedValue, true);
+ return sTmpTypedValue.changingConfigurations;
+ }
+ }
+
+ private static boolean isColorType(int type) {
+ return (type >= TypedValue.TYPE_FIRST_COLOR_INT) && (type <= TypedValue.TYPE_LAST_COLOR_INT);
+ }
+}
diff --git a/android-35/android/animation/AnimatorListenerAdapter.java b/android-35/android/animation/AnimatorListenerAdapter.java
new file mode 100644
index 0000000..2ecb8c3
--- /dev/null
+++ b/android-35/android/animation/AnimatorListenerAdapter.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2010 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.animation;
+
+/**
+ * This adapter class provides empty implementations of the methods from {@link android.animation.Animator.AnimatorListener}.
+ * Any custom listener that cares only about a subset of the methods of this listener can
+ * simply subclass this adapter class instead of implementing the interface directly.
+ */
+public abstract class AnimatorListenerAdapter implements Animator.AnimatorListener,
+ Animator.AnimatorPauseListener {
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onAnimationRepeat(Animator animation) {
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onAnimationStart(Animator animation) {
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onAnimationPause(Animator animation) {
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onAnimationResume(Animator animation) {
+ }
+}
diff --git a/android-35/android/animation/AnimatorSet.java b/android-35/android/animation/AnimatorSet.java
new file mode 100644
index 0000000..845a346
--- /dev/null
+++ b/android-35/android/animation/AnimatorSet.java
@@ -0,0 +1,2241 @@
+/*
+ * Copyright (C) 2010 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.animation;
+
+import android.app.ActivityThread;
+import android.app.Application;
+import android.os.Build;
+import android.os.Looper;
+import android.util.AndroidRuntimeException;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.LongArray;
+import android.view.animation.Animation;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * This class plays a set of {@link Animator} objects in the specified order. Animations
+ * can be set up to play together, in sequence, or after a specified delay.
+ *
+ * <p>There are two different approaches to adding animations to a <code>AnimatorSet</code>:
+ * either the {@link AnimatorSet#playTogether(Animator[]) playTogether()} or
+ * {@link AnimatorSet#playSequentially(Animator[]) playSequentially()} methods can be called to add
+ * a set of animations all at once, or the {@link AnimatorSet#play(Animator)} can be
+ * used in conjunction with methods in the {@link AnimatorSet.Builder Builder}
+ * class to add animations
+ * one by one.</p>
+ *
+ * <p>It is possible to set up a <code>AnimatorSet</code> with circular dependencies between
+ * its animations. For example, an animation a1 could be set up to start before animation a2, a2
+ * before a3, and a3 before a1. The results of this configuration are undefined, but will typically
+ * result in none of the affected animations being played. Because of this (and because
+ * circular dependencies do not make logical sense anyway), circular dependencies
+ * should be avoided, and the dependency flow of animations should only be in one direction.
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For more information about animating with {@code AnimatorSet}, read the
+ * <a href="{@docRoot}guide/topics/graphics/prop-animation.html#choreography">Property
+ * Animation</a> developer guide.</p>
+ * </div>
+ */
+public final class AnimatorSet extends Animator implements AnimationHandler.AnimationFrameCallback {
+
+ private static final String TAG = "AnimatorSet";
+ /**
+ * Internal variables
+ * NOTE: This object implements the clone() method, making a deep copy of any referenced
+ * objects. As other non-trivial fields are added to this class, make sure to add logic
+ * to clone() to make deep copies of them.
+ */
+
+ /**
+ * Tracks animations currently being played, so that we know what to
+ * cancel or end when cancel() or end() is called on this AnimatorSet
+ */
+ private ArrayList<Node> mPlayingSet = new ArrayList<Node>();
+
+ /**
+ * Contains all nodes, mapped to their respective Animators. When new
+ * dependency information is added for an Animator, we want to add it
+ * to a single node representing that Animator, not create a new Node
+ * if one already exists.
+ */
+ private ArrayMap<Animator, Node> mNodeMap = new ArrayMap<Animator, Node>();
+
+ /**
+ * Contains the start and end events of all the nodes. All these events are sorted in this list.
+ */
+ private ArrayList<AnimationEvent> mEvents = new ArrayList<>();
+
+ /**
+ * Set of all nodes created for this AnimatorSet. This list is used upon
+ * starting the set, and the nodes are placed in sorted order into the
+ * sortedNodes collection.
+ */
+ private ArrayList<Node> mNodes = new ArrayList<Node>();
+
+ /**
+ * Tracks whether any change has been made to the AnimatorSet, which is then used to
+ * determine whether the dependency graph should be re-constructed.
+ */
+ private boolean mDependencyDirty = false;
+
+ /**
+ * Indicates whether an AnimatorSet has been start()'d, whether or
+ * not there is a nonzero startDelay.
+ */
+ private boolean mStarted = false;
+
+ // The amount of time in ms to delay starting the animation after start() is called
+ private long mStartDelay = 0;
+
+ // Animator used for a nonzero startDelay
+ private ValueAnimator mDelayAnim = ValueAnimator.ofFloat(0f, 1f).setDuration(0);
+
+ // Root of the dependency tree of all the animators in the set. In this tree, parent-child
+ // relationship captures the order of animation (i.e. parent and child will play sequentially),
+ // and sibling relationship indicates "with" relationship, as sibling animators start at the
+ // same time.
+ private Node mRootNode = new Node(mDelayAnim);
+
+ // How long the child animations should last in ms. The default value is negative, which
+ // simply means that there is no duration set on the AnimatorSet. When a real duration is
+ // set, it is passed along to the child animations.
+ private long mDuration = -1;
+
+ // Records the interpolator for the set. Null value indicates that no interpolator
+ // was set on this AnimatorSet, so it should not be passed down to the children.
+ private TimeInterpolator mInterpolator = null;
+
+ // The total duration of finishing all the Animators in the set.
+ private long mTotalDuration = 0;
+
+ // In pre-N releases, calling end() before start() on an animator set is no-op. But that is not
+ // consistent with the behavior for other animator types. In order to keep the behavior
+ // consistent within Animation framework, when end() is called without start(), we will start
+ // the animator set and immediately end it for N and forward.
+ private final boolean mShouldIgnoreEndWithoutStart;
+
+ // In pre-O releases, calling start() doesn't reset all the animators values to start values.
+ // As a result, the start of the animation is inconsistent with what setCurrentPlayTime(0) would
+ // look like on O. Also it is inconsistent with what reverse() does on O, as reverse would
+ // advance all the animations to the right beginning values for before starting to reverse.
+ // From O and forward, we will add an additional step of resetting the animation values (unless
+ // the animation was previously seeked and therefore doesn't start from the beginning).
+ private final boolean mShouldResetValuesAtStart;
+
+ // In pre-O releases, end() may never explicitly called on a child animator. As a result, end()
+ // may not even be properly implemented in a lot of cases. After a few apps crashing on this,
+ // it became necessary to use an sdk target guard for calling end().
+ private final boolean mEndCanBeCalled;
+
+ // The time, in milliseconds, when last frame of the animation came in. -1 when the animation is
+ // not running.
+ private long mLastFrameTime = -1;
+
+ // The time, in milliseconds, when the first frame of the animation came in. This is the
+ // frame before we start counting down the start delay, if any.
+ // -1 when the animation is not running.
+ private long mFirstFrame = -1;
+
+ // The time, in milliseconds, when the first frame of the animation came in.
+ // -1 when the animation is not running.
+ private int mLastEventId = -1;
+
+ // Indicates whether the animation is reversing.
+ private boolean mReversing = false;
+
+ // Indicates whether the animation should register frame callbacks. If false, the animation will
+ // passively wait for an AnimatorSet to pulse it.
+ private boolean mSelfPulse = true;
+
+ // SeekState stores the last seeked play time as well as seek direction.
+ private SeekState mSeekState = new SeekState();
+
+ // Indicates where children animators are all initialized with their start values captured.
+ private boolean mChildrenInitialized = false;
+
+ /**
+ * Set on the next frame after pause() is called, used to calculate a new startTime
+ * or delayStartTime which allows the animator set to continue from the point at which
+ * it was paused. If negative, has not yet been set.
+ */
+ private long mPauseTime = -1;
+
+ /**
+ * The start and stop times of all descendant animators.
+ */
+ private long[] mChildStartAndStopTimes;
+
+ // This is to work around a bug in b/34736819. This needs to be removed once app team
+ // fixes their side.
+ private AnimatorListenerAdapter mAnimationEndListener = new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (mNodeMap.get(animation) == null) {
+ throw new AndroidRuntimeException("Error: animation ended is not in the node map");
+ }
+ mNodeMap.get(animation).mEnded = true;
+
+ }
+ };
+
+ public AnimatorSet() {
+ super();
+ mNodeMap.put(mDelayAnim, mRootNode);
+ mNodes.add(mRootNode);
+ boolean isPreO;
+ // Set the flag to ignore calling end() without start() for pre-N releases
+ Application app = ActivityThread.currentApplication();
+ if (app == null || app.getApplicationInfo() == null) {
+ mShouldIgnoreEndWithoutStart = true;
+ isPreO = true;
+ } else {
+ if (app.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) {
+ mShouldIgnoreEndWithoutStart = true;
+ } else {
+ mShouldIgnoreEndWithoutStart = false;
+ }
+
+ isPreO = app.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.O;
+ }
+ mShouldResetValuesAtStart = !isPreO;
+ mEndCanBeCalled = !isPreO;
+ }
+
+ /**
+ * Sets up this AnimatorSet to play all of the supplied animations at the same time.
+ * This is equivalent to calling {@link #play(Animator)} with the first animator in the
+ * set and then {@link Builder#with(Animator)} with each of the other animators. Note that
+ * an Animator with a {@link Animator#setStartDelay(long) startDelay} will not actually
+ * start until that delay elapses, which means that if the first animator in the list
+ * supplied to this constructor has a startDelay, none of the other animators will start
+ * until that first animator's startDelay has elapsed.
+ *
+ * @param items The animations that will be started simultaneously.
+ */
+ public void playTogether(Animator... items) {
+ if (items != null) {
+ Builder builder = play(items[0]);
+ for (int i = 1; i < items.length; ++i) {
+ builder.with(items[i]);
+ }
+ }
+ }
+
+ /**
+ * Sets up this AnimatorSet to play all of the supplied animations at the same time.
+ *
+ * @param items The animations that will be started simultaneously.
+ */
+ public void playTogether(Collection<Animator> items) {
+ if (items != null && items.size() > 0) {
+ Builder builder = null;
+ for (Animator anim : items) {
+ if (builder == null) {
+ builder = play(anim);
+ } else {
+ builder.with(anim);
+ }
+ }
+ }
+ }
+
+ /**
+ * Sets up this AnimatorSet to play each of the supplied animations when the
+ * previous animation ends.
+ *
+ * @param items The animations that will be started one after another.
+ */
+ public void playSequentially(Animator... items) {
+ if (items != null) {
+ if (items.length == 1) {
+ play(items[0]);
+ } else {
+ for (int i = 0; i < items.length - 1; ++i) {
+ play(items[i]).before(items[i + 1]);
+ }
+ }
+ }
+ }
+
+ /**
+ * Sets up this AnimatorSet to play each of the supplied animations when the
+ * previous animation ends.
+ *
+ * @param items The animations that will be started one after another.
+ */
+ public void playSequentially(List<Animator> items) {
+ if (items != null && items.size() > 0) {
+ if (items.size() == 1) {
+ play(items.get(0));
+ } else {
+ for (int i = 0; i < items.size() - 1; ++i) {
+ play(items.get(i)).before(items.get(i + 1));
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns the current list of child Animator objects controlled by this
+ * AnimatorSet. This is a copy of the internal list; modifications to the returned list
+ * will not affect the AnimatorSet, although changes to the underlying Animator objects
+ * will affect those objects being managed by the AnimatorSet.
+ *
+ * @return ArrayList<Animator> The list of child animations of this AnimatorSet.
+ */
+ public ArrayList<Animator> getChildAnimations() {
+ ArrayList<Animator> childList = new ArrayList<Animator>();
+ int size = mNodes.size();
+ for (int i = 0; i < size; i++) {
+ Node node = mNodes.get(i);
+ if (node != mRootNode) {
+ childList.add(node.mAnimation);
+ }
+ }
+ return childList;
+ }
+
+ /**
+ * Sets the target object for all current {@link #getChildAnimations() child animations}
+ * of this AnimatorSet that take targets ({@link ObjectAnimator} and
+ * AnimatorSet).
+ *
+ * @param target The object being animated
+ */
+ @Override
+ public void setTarget(Object target) {
+ int size = mNodes.size();
+ for (int i = 0; i < size; i++) {
+ Node node = mNodes.get(i);
+ Animator animation = node.mAnimation;
+ if (animation instanceof AnimatorSet) {
+ ((AnimatorSet)animation).setTarget(target);
+ } else if (animation instanceof ObjectAnimator) {
+ ((ObjectAnimator)animation).setTarget(target);
+ }
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public int getChangingConfigurations() {
+ int conf = super.getChangingConfigurations();
+ final int nodeCount = mNodes.size();
+ for (int i = 0; i < nodeCount; i ++) {
+ conf |= mNodes.get(i).mAnimation.getChangingConfigurations();
+ }
+ return conf;
+ }
+
+ /**
+ * Sets the TimeInterpolator for all current {@link #getChildAnimations() child animations}
+ * of this AnimatorSet. The default value is null, which means that no interpolator
+ * is set on this AnimatorSet. Setting the interpolator to any non-null value
+ * will cause that interpolator to be set on the child animations
+ * when the set is started.
+ *
+ * @param interpolator the interpolator to be used by each child animation of this AnimatorSet
+ */
+ @Override
+ public void setInterpolator(TimeInterpolator interpolator) {
+ mInterpolator = interpolator;
+ }
+
+ @Override
+ public TimeInterpolator getInterpolator() {
+ return mInterpolator;
+ }
+
+ /**
+ * This method creates a <code>Builder</code> object, which is used to
+ * set up playing constraints. This initial <code>play()</code> method
+ * tells the <code>Builder</code> the animation that is the dependency for
+ * the succeeding commands to the <code>Builder</code>. For example,
+ * calling <code>play(a1).with(a2)</code> sets up the AnimatorSet to play
+ * <code>a1</code> and <code>a2</code> at the same time,
+ * <code>play(a1).before(a2)</code> sets up the AnimatorSet to play
+ * <code>a1</code> first, followed by <code>a2</code>, and
+ * <code>play(a1).after(a2)</code> sets up the AnimatorSet to play
+ * <code>a2</code> first, followed by <code>a1</code>.
+ *
+ * <p>Note that <code>play()</code> is the only way to tell the
+ * <code>Builder</code> the animation upon which the dependency is created,
+ * so successive calls to the various functions in <code>Builder</code>
+ * will all refer to the initial parameter supplied in <code>play()</code>
+ * as the dependency of the other animations. For example, calling
+ * <code>play(a1).before(a2).before(a3)</code> will play both <code>a2</code>
+ * and <code>a3</code> when a1 ends; it does not set up a dependency between
+ * <code>a2</code> and <code>a3</code>.</p>
+ *
+ * @param anim The animation that is the dependency used in later calls to the
+ * methods in the returned <code>Builder</code> object. A null parameter will result
+ * in a null <code>Builder</code> return value.
+ * @return Builder The object that constructs the AnimatorSet based on the dependencies
+ * outlined in the calls to <code>play</code> and the other methods in the
+ * <code>Builder</code object.
+ */
+ public Builder play(Animator anim) {
+ if (anim != null) {
+ return new Builder(anim);
+ }
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>Note that canceling a <code>AnimatorSet</code> also cancels all of the animations that it
+ * is responsible for.</p>
+ */
+ @SuppressWarnings("unchecked")
+ @Override
+ public void cancel() {
+ if (Looper.myLooper() == null) {
+ throw new AndroidRuntimeException("Animators may only be run on Looper threads");
+ }
+ if (isStarted() || mStartListenersCalled) {
+ notifyListeners(AnimatorCaller.ON_CANCEL, false);
+ callOnPlayingSet(Animator::cancel);
+ mPlayingSet.clear();
+ endAnimation();
+ }
+ }
+
+ /**
+ * Calls consumer on every Animator of mPlayingSet.
+ *
+ * @param consumer The method to call on every Animator of mPlayingSet.
+ */
+ private void callOnPlayingSet(Consumer<Animator> consumer) {
+ final ArrayList<Node> list = mPlayingSet;
+ final int size = list.size();
+ //noinspection ForLoopReplaceableByForEach
+ for (int i = 0; i < size; i++) {
+ final Animator animator = list.get(i).mAnimation;
+ consumer.accept(animator);
+ }
+ }
+
+ // Force all the animations to end when the duration scale is 0.
+ private void forceToEnd() {
+ if (mEndCanBeCalled) {
+ end();
+ return;
+ }
+
+ // Note: we don't want to combine this case with the end() method below because in
+ // the case of developer calling end(), we still need to make sure end() is explicitly
+ // called on the child animators to maintain the old behavior.
+ if (mReversing) {
+ handleAnimationEvents(mLastEventId, 0, getTotalDuration());
+ } else {
+ long zeroScalePlayTime = getTotalDuration();
+ if (zeroScalePlayTime == DURATION_INFINITE) {
+ // Use a large number for the play time.
+ zeroScalePlayTime = Integer.MAX_VALUE;
+ }
+ handleAnimationEvents(mLastEventId, mEvents.size() - 1, zeroScalePlayTime);
+ }
+ mPlayingSet.clear();
+ endAnimation();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>Note that ending a <code>AnimatorSet</code> also ends all of the animations that it is
+ * responsible for.</p>
+ */
+ @Override
+ public void end() {
+ if (Looper.myLooper() == null) {
+ throw new AndroidRuntimeException("Animators may only be run on Looper threads");
+ }
+ if (mShouldIgnoreEndWithoutStart && !isStarted()) {
+ return;
+ }
+ if (isStarted()) {
+ mStarted = false; // don't allow reentrancy
+ // Iterate the animations that haven't finished or haven't started, and end them.
+ if (mReversing) {
+ // Between start() and first frame, mLastEventId would be unset (i.e. -1)
+ mLastEventId = mLastEventId == -1 ? mEvents.size() : mLastEventId;
+ for (int eventId = mLastEventId - 1; eventId >= 0; eventId--) {
+ AnimationEvent event = mEvents.get(eventId);
+ Animator anim = event.mNode.mAnimation;
+ if (mNodeMap.get(anim).mEnded) {
+ continue;
+ }
+ if (event.mEvent == AnimationEvent.ANIMATION_END) {
+ anim.reverse();
+ } else if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED
+ && anim.isStarted()) {
+ // Make sure anim hasn't finished before calling end() so that we don't end
+ // already ended animations, which will cause start and end callbacks to be
+ // triggered again.
+ anim.end();
+ }
+ }
+ } else {
+ for (int eventId = mLastEventId + 1; eventId < mEvents.size(); eventId++) {
+ // Avoid potential reentrant loop caused by child animators manipulating
+ // AnimatorSet's lifecycle (i.e. not a recommended approach).
+ AnimationEvent event = mEvents.get(eventId);
+ Animator anim = event.mNode.mAnimation;
+ if (mNodeMap.get(anim).mEnded) {
+ continue;
+ }
+ if (event.mEvent == AnimationEvent.ANIMATION_START) {
+ anim.start();
+ } else if (event.mEvent == AnimationEvent.ANIMATION_END && anim.isStarted()) {
+ // Make sure anim hasn't finished before calling end() so that we don't end
+ // already ended animations, which will cause start and end callbacks to be
+ // triggered again.
+ anim.end();
+ }
+ }
+ }
+ }
+ endAnimation();
+ }
+
+ /**
+ * Returns true if any of the child animations of this AnimatorSet have been started and have
+ * not yet ended. Child animations will not be started until the AnimatorSet has gone past
+ * its initial delay set through {@link #setStartDelay(long)}.
+ *
+ * @return Whether this AnimatorSet has gone past the initial delay, and at least one child
+ * animation has been started and not yet ended.
+ */
+ @Override
+ public boolean isRunning() {
+ if (mStartDelay == 0) {
+ return mStarted;
+ }
+ return mLastFrameTime > 0;
+ }
+
+ @Override
+ public boolean isStarted() {
+ return mStarted;
+ }
+
+ /**
+ * The amount of time, in milliseconds, to delay starting the animation after
+ * {@link #start()} is called.
+ *
+ * @return the number of milliseconds to delay running the animation
+ */
+ @Override
+ public long getStartDelay() {
+ return mStartDelay;
+ }
+
+ /**
+ * The amount of time, in milliseconds, to delay starting the animation after
+ * {@link #start()} is called. Note that the start delay should always be non-negative. Any
+ * negative start delay will be clamped to 0 on N and above.
+ *
+ * @param startDelay The amount of the delay, in milliseconds
+ */
+ @Override
+ public void setStartDelay(long startDelay) {
+ // Clamp start delay to non-negative range.
+ if (startDelay < 0) {
+ Log.w(TAG, "Start delay should always be non-negative");
+ startDelay = 0;
+ }
+ long delta = startDelay - mStartDelay;
+ if (delta == 0) {
+ return;
+ }
+ mStartDelay = startDelay;
+ if (!mDependencyDirty) {
+ // Dependency graph already constructed, update all the nodes' start/end time
+ int size = mNodes.size();
+ for (int i = 0; i < size; i++) {
+ Node node = mNodes.get(i);
+ if (node == mRootNode) {
+ node.mEndTime = mStartDelay;
+ } else {
+ node.mStartTime = node.mStartTime == DURATION_INFINITE ?
+ DURATION_INFINITE : node.mStartTime + delta;
+ node.mEndTime = node.mEndTime == DURATION_INFINITE ?
+ DURATION_INFINITE : node.mEndTime + delta;
+ }
+ }
+ // Update total duration, if necessary.
+ if (mTotalDuration != DURATION_INFINITE) {
+ mTotalDuration += delta;
+ }
+ }
+ }
+
+ /**
+ * Gets the length of each of the child animations of this AnimatorSet. This value may
+ * be less than 0, which indicates that no duration has been set on this AnimatorSet
+ * and each of the child animations will use their own duration.
+ *
+ * @return The length of the animation, in milliseconds, of each of the child
+ * animations of this AnimatorSet.
+ */
+ @Override
+ public long getDuration() {
+ return mDuration;
+ }
+
+ /**
+ * Sets the length of each of the current child animations of this AnimatorSet. By default,
+ * each child animation will use its own duration. If the duration is set on the AnimatorSet,
+ * then each child animation inherits this duration.
+ *
+ * @param duration The length of the animation, in milliseconds, of each of the child
+ * animations of this AnimatorSet.
+ */
+ @Override
+ public AnimatorSet setDuration(long duration) {
+ if (duration < 0) {
+ throw new IllegalArgumentException("duration must be a value of zero or greater");
+ }
+ mDependencyDirty = true;
+ // Just record the value for now - it will be used later when the AnimatorSet starts
+ mDuration = duration;
+ return this;
+ }
+
+ @Override
+ public void setupStartValues() {
+ int size = mNodes.size();
+ for (int i = 0; i < size; i++) {
+ Node node = mNodes.get(i);
+ if (node != mRootNode) {
+ node.mAnimation.setupStartValues();
+ }
+ }
+ }
+
+ @Override
+ public void setupEndValues() {
+ int size = mNodes.size();
+ for (int i = 0; i < size; i++) {
+ Node node = mNodes.get(i);
+ if (node != mRootNode) {
+ node.mAnimation.setupEndValues();
+ }
+ }
+ }
+
+ @Override
+ public void pause() {
+ if (Looper.myLooper() == null) {
+ throw new AndroidRuntimeException("Animators may only be run on Looper threads");
+ }
+ boolean previouslyPaused = mPaused;
+ super.pause();
+ if (!previouslyPaused && mPaused) {
+ mPauseTime = -1;
+ callOnPlayingSet(Animator::pause);
+ }
+ }
+
+ @Override
+ public void resume() {
+ if (Looper.myLooper() == null) {
+ throw new AndroidRuntimeException("Animators may only be run on Looper threads");
+ }
+ boolean previouslyPaused = mPaused;
+ super.resume();
+ if (previouslyPaused && !mPaused) {
+ if (mPauseTime >= 0) {
+ addAnimationCallback(0);
+ }
+ callOnPlayingSet(Animator::resume);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>Starting this <code>AnimatorSet</code> will, in turn, start the animations for which
+ * it is responsible. The details of when exactly those animations are started depends on
+ * the dependency relationships that have been set up between the animations.
+ *
+ * <b>Note:</b> Manipulating AnimatorSet's lifecycle in the child animators' listener callbacks
+ * will lead to undefined behaviors. Also, AnimatorSet will ignore any seeking in the child
+ * animators once {@link #start()} is called.
+ */
+ @SuppressWarnings("unchecked")
+ @Override
+ public void start() {
+ start(false, true);
+ }
+
+ @Override
+ void startWithoutPulsing(boolean inReverse) {
+ start(inReverse, false);
+ }
+
+ private void initAnimation() {
+ if (mInterpolator != null) {
+ for (int i = 0; i < mNodes.size(); i++) {
+ Node node = mNodes.get(i);
+ node.mAnimation.setInterpolator(mInterpolator);
+ }
+ }
+ updateAnimatorsDuration();
+ createDependencyGraph();
+ }
+
+ private void start(boolean inReverse, boolean selfPulse) {
+ if (Looper.myLooper() == null) {
+ throw new AndroidRuntimeException("Animators may only be run on Looper threads");
+ }
+ if (inReverse == mReversing && selfPulse == mSelfPulse && mStarted) {
+ // It is already started
+ return;
+ }
+ mStarted = true;
+ mSelfPulse = selfPulse;
+ mPaused = false;
+ mPauseTime = -1;
+
+ int size = mNodes.size();
+ for (int i = 0; i < size; i++) {
+ Node node = mNodes.get(i);
+ node.mEnded = false;
+ node.mAnimation.setAllowRunningAsynchronously(false);
+ }
+
+ initAnimation();
+ if (inReverse && !canReverse()) {
+ throw new UnsupportedOperationException("Cannot reverse infinite AnimatorSet");
+ }
+
+ mReversing = inReverse;
+
+ // Now that all dependencies are set up, start the animations that should be started.
+ boolean isEmptySet = isEmptySet(this);
+ if (!isEmptySet) {
+ startAnimation();
+ }
+
+ notifyStartListeners(inReverse);
+ if (isEmptySet) {
+ // In the case of empty AnimatorSet, or 0 duration scale, we will trigger the
+ // onAnimationEnd() right away.
+ end();
+ }
+ }
+
+ // Returns true if set is empty or contains nothing but animator sets with no start delay.
+ private static boolean isEmptySet(AnimatorSet set) {
+ if (set.getStartDelay() > 0) {
+ return false;
+ }
+ for (int i = 0; i < set.getChildAnimations().size(); i++) {
+ Animator anim = set.getChildAnimations().get(i);
+ if (!(anim instanceof AnimatorSet)) {
+ // Contains non-AnimatorSet, not empty.
+ return false;
+ } else {
+ if (!isEmptySet((AnimatorSet) anim)) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ private void updateAnimatorsDuration() {
+ if (mDuration >= 0) {
+ // If the duration was set on this AnimatorSet, pass it along to all child animations
+ int size = mNodes.size();
+ for (int i = 0; i < size; i++) {
+ Node node = mNodes.get(i);
+ // TODO: don't set the duration of the timing-only nodes created by AnimatorSet to
+ // insert "play-after" delays
+ node.mAnimation.setDuration(mDuration);
+ }
+ }
+ mDelayAnim.setDuration(mStartDelay);
+ }
+
+ @Override
+ void skipToEndValue(boolean inReverse) {
+ // This makes sure the animation events are sorted an up to date.
+ initAnimation();
+ initChildren();
+
+ // Calling skip to the end in the sequence that they would be called in a forward/reverse
+ // run, such that the sequential animations modifying the same property would have
+ // the right value in the end.
+ if (inReverse) {
+ for (int i = mEvents.size() - 1; i >= 0; i--) {
+ AnimationEvent event = mEvents.get(i);
+ if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) {
+ event.mNode.mAnimation.skipToEndValue(true);
+ }
+ }
+ } else {
+ for (int i = 0; i < mEvents.size(); i++) {
+ AnimationEvent event = mEvents.get(i);
+ if (event.mEvent == AnimationEvent.ANIMATION_END) {
+ event.mNode.mAnimation.skipToEndValue(false);
+ }
+ }
+ }
+ }
+
+ /**
+ * Internal only.
+ *
+ * This method sets the animation values based on the play time. It also fast forward or
+ * backward all the child animations progress accordingly.
+ *
+ * This method is also responsible for calling
+ * {@link android.view.animation.Animation.AnimationListener#onAnimationRepeat(Animation)},
+ * as needed, based on the last play time and current play time.
+ */
+ private void animateBasedOnPlayTime(
+ long currentPlayTime,
+ long lastPlayTime,
+ boolean inReverse
+ ) {
+ if (currentPlayTime < 0 || lastPlayTime < -1) {
+ throw new UnsupportedOperationException("Error: Play time should never be negative.");
+ }
+ // TODO: take into account repeat counts and repeat callback when repeat is implemented.
+
+ if (inReverse) {
+ long duration = getTotalDuration();
+ if (duration == DURATION_INFINITE) {
+ throw new UnsupportedOperationException(
+ "Cannot reverse AnimatorSet with infinite duration"
+ );
+ }
+ // Convert the play times to the forward direction.
+ currentPlayTime = Math.min(currentPlayTime, duration);
+ currentPlayTime = duration - currentPlayTime;
+ lastPlayTime = duration - lastPlayTime;
+ }
+
+ long[] startEndTimes = ensureChildStartAndEndTimes();
+ int index = findNextIndex(lastPlayTime, startEndTimes);
+ int endIndex = findNextIndex(currentPlayTime, startEndTimes);
+
+ // Change values at the start/end times so that values are set in the right order.
+ // We don't want an animator that would finish before another to override the value
+ // set by another animator that finishes earlier.
+ if (currentPlayTime >= lastPlayTime) {
+ while (index < endIndex) {
+ long playTime = startEndTimes[index];
+ if (lastPlayTime != playTime) {
+ animateSkipToEnds(playTime, lastPlayTime);
+ animateValuesInRange(playTime, lastPlayTime);
+ lastPlayTime = playTime;
+ }
+ index++;
+ }
+ } else {
+ while (index > endIndex) {
+ index--;
+ long playTime = startEndTimes[index];
+ if (lastPlayTime != playTime) {
+ animateSkipToEnds(playTime, lastPlayTime);
+ animateValuesInRange(playTime, lastPlayTime);
+ lastPlayTime = playTime;
+ }
+ }
+ }
+ if (currentPlayTime != lastPlayTime) {
+ animateSkipToEnds(currentPlayTime, lastPlayTime);
+ animateValuesInRange(currentPlayTime, lastPlayTime);
+ }
+ }
+
+ /**
+ * Looks through startEndTimes for playTime. If it is in startEndTimes, the index after
+ * is returned. Otherwise, it returns the index at which it would be placed if it were
+ * to be inserted.
+ */
+ private int findNextIndex(long playTime, long[] startEndTimes) {
+ int index = Arrays.binarySearch(startEndTimes, playTime);
+ if (index < 0) {
+ index = -index - 1;
+ } else {
+ index++;
+ }
+ return index;
+ }
+
+ @Override
+ void animateSkipToEnds(long currentPlayTime, long lastPlayTime) {
+ initAnimation();
+
+ if (lastPlayTime > currentPlayTime) {
+ notifyStartListeners(true);
+ for (int i = mEvents.size() - 1; i >= 0; i--) {
+ AnimationEvent event = mEvents.get(i);
+ Node node = event.mNode;
+ if (event.mEvent == AnimationEvent.ANIMATION_END
+ && node.mStartTime != DURATION_INFINITE
+ ) {
+ Animator animator = node.mAnimation;
+ long start = node.mStartTime;
+ long end = node.mTotalDuration == DURATION_INFINITE
+ ? Long.MAX_VALUE : node.mEndTime;
+ if (currentPlayTime <= start && start < lastPlayTime) {
+ animator.animateSkipToEnds(
+ 0,
+ lastPlayTime - node.mStartTime
+ );
+ mPlayingSet.remove(node);
+ } else if (start <= currentPlayTime && currentPlayTime <= end) {
+ animator.animateSkipToEnds(
+ currentPlayTime - node.mStartTime,
+ lastPlayTime - node.mStartTime
+ );
+ if (!mPlayingSet.contains(node)) {
+ mPlayingSet.add(node);
+ }
+ }
+ }
+ }
+ if (currentPlayTime <= 0) {
+ notifyEndListeners(true);
+ }
+ } else {
+ notifyStartListeners(false);
+ int eventsSize = mEvents.size();
+ for (int i = 0; i < eventsSize; i++) {
+ AnimationEvent event = mEvents.get(i);
+ Node node = event.mNode;
+ if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED
+ && node.mStartTime != DURATION_INFINITE
+ ) {
+ Animator animator = node.mAnimation;
+ long start = node.mStartTime;
+ long end = node.mTotalDuration == DURATION_INFINITE
+ ? Long.MAX_VALUE : node.mEndTime;
+ if (lastPlayTime < end && end <= currentPlayTime) {
+ animator.animateSkipToEnds(
+ end - node.mStartTime,
+ lastPlayTime - node.mStartTime
+ );
+ mPlayingSet.remove(node);
+ } else if (start <= currentPlayTime && currentPlayTime <= end) {
+ animator.animateSkipToEnds(
+ currentPlayTime - node.mStartTime,
+ lastPlayTime - node.mStartTime
+ );
+ if (!mPlayingSet.contains(node)) {
+ mPlayingSet.add(node);
+ }
+ }
+ }
+ }
+ if (currentPlayTime >= getTotalDuration()) {
+ notifyEndListeners(false);
+ }
+ }
+ }
+
+ @Override
+ void animateValuesInRange(long currentPlayTime, long lastPlayTime) {
+ initAnimation();
+
+ if (lastPlayTime < 0 || (lastPlayTime == 0 && currentPlayTime > 0)) {
+ notifyStartListeners(false);
+ } else {
+ long duration = getTotalDuration();
+ if (duration >= 0
+ && (lastPlayTime > duration || (lastPlayTime == duration
+ && currentPlayTime < duration))
+ ) {
+ notifyStartListeners(true);
+ }
+ }
+
+ int eventsSize = mEvents.size();
+ for (int i = 0; i < eventsSize; i++) {
+ AnimationEvent event = mEvents.get(i);
+ Node node = event.mNode;
+ if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED
+ && node.mStartTime != DURATION_INFINITE
+ ) {
+ Animator animator = node.mAnimation;
+ long start = node.mStartTime;
+ long end = node.mTotalDuration == DURATION_INFINITE
+ ? Long.MAX_VALUE : node.mEndTime;
+ if ((start < currentPlayTime && currentPlayTime < end)
+ || (start == currentPlayTime && lastPlayTime < start)
+ || (end == currentPlayTime && lastPlayTime > end)
+ ) {
+ animator.animateValuesInRange(
+ currentPlayTime - node.mStartTime,
+ Math.max(-1, lastPlayTime - node.mStartTime)
+ );
+ }
+ }
+ }
+ }
+
+ private long[] ensureChildStartAndEndTimes() {
+ if (mChildStartAndStopTimes == null) {
+ LongArray startAndEndTimes = new LongArray();
+ getStartAndEndTimes(startAndEndTimes, 0);
+ long[] times = startAndEndTimes.toArray();
+ Arrays.sort(times);
+ mChildStartAndStopTimes = times;
+ }
+ return mChildStartAndStopTimes;
+ }
+
+ @Override
+ void getStartAndEndTimes(LongArray times, long offset) {
+ int eventsSize = mEvents.size();
+ for (int i = 0; i < eventsSize; i++) {
+ AnimationEvent event = mEvents.get(i);
+ if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED
+ && event.mNode.mStartTime != DURATION_INFINITE
+ ) {
+ event.mNode.mAnimation.getStartAndEndTimes(times, offset + event.mNode.mStartTime);
+ }
+ }
+ }
+
+ @Override
+ boolean isInitialized() {
+ if (mChildrenInitialized) {
+ return true;
+ }
+
+ boolean allInitialized = true;
+ for (int i = 0; i < mNodes.size(); i++) {
+ if (!mNodes.get(i).mAnimation.isInitialized()) {
+ allInitialized = false;
+ break;
+ }
+ }
+ mChildrenInitialized = allInitialized;
+ return mChildrenInitialized;
+ }
+
+ /**
+ * Sets the position of the animation to the specified point in time. This time should
+ * be between 0 and the total duration of the animation, including any repetition. If
+ * the animation has not yet been started, then it will not advance forward after it is
+ * set to this time; it will simply set the time to this value and perform any appropriate
+ * actions based on that time. If the animation is already running, then setCurrentPlayTime()
+ * will set the current playing time to this value and continue playing from that point.
+ * On {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and above, an AnimatorSet
+ * that hasn't been {@link #start()}ed, will issue
+ * {@link android.animation.Animator.AnimatorListener#onAnimationStart(Animator, boolean)}
+ * and {@link android.animation.Animator.AnimatorListener#onAnimationEnd(Animator, boolean)}
+ * events.
+ *
+ * @param playTime The time, in milliseconds, to which the animation is advanced or rewound.
+ * Unless the animation is reversing, the playtime is considered the time since
+ * the end of the start delay of the AnimatorSet in a forward playing direction.
+ *
+ */
+ public void setCurrentPlayTime(long playTime) {
+ if (mReversing && getTotalDuration() == DURATION_INFINITE) {
+ // Should never get here
+ throw new UnsupportedOperationException("Error: Cannot seek in reverse in an infinite"
+ + " AnimatorSet");
+ }
+
+ if ((getTotalDuration() != DURATION_INFINITE && playTime > getTotalDuration() - mStartDelay)
+ || playTime < 0) {
+ throw new UnsupportedOperationException("Error: Play time should always be in between"
+ + " 0 and duration.");
+ }
+
+ initAnimation();
+
+ long lastPlayTime = mSeekState.getPlayTime();
+ if (!isStarted() || isPaused()) {
+ if (mReversing && !isStarted()) {
+ throw new UnsupportedOperationException("Error: Something went wrong. mReversing"
+ + " should not be set when AnimatorSet is not started.");
+ }
+ if (!mSeekState.isActive()) {
+ findLatestEventIdForTime(0);
+ initChildren();
+ // Set all the values to start values.
+ skipToEndValue(!mReversing);
+ mSeekState.setPlayTime(0, mReversing);
+ }
+ }
+ mSeekState.setPlayTime(playTime, mReversing);
+ animateBasedOnPlayTime(playTime, lastPlayTime, mReversing);
+ }
+
+ /**
+ * Returns the milliseconds elapsed since the start of the animation.
+ *
+ * <p>For ongoing animations, this method returns the current progress of the animation in
+ * terms of play time. For an animation that has not yet been started: if the animation has been
+ * seeked to a certain time via {@link #setCurrentPlayTime(long)}, the seeked play time will
+ * be returned; otherwise, this method will return 0.
+ *
+ * @return the current position in time of the animation in milliseconds
+ */
+ public long getCurrentPlayTime() {
+ if (mSeekState.isActive()) {
+ return mSeekState.getPlayTime();
+ }
+ if (mLastFrameTime == -1) {
+ // Not yet started or during start delay
+ return 0;
+ }
+ float durationScale = ValueAnimator.getDurationScale();
+ durationScale = durationScale == 0 ? 1 : durationScale;
+ if (mReversing) {
+ return (long) ((mLastFrameTime - mFirstFrame) / durationScale);
+ } else {
+ return (long) ((mLastFrameTime - mFirstFrame - mStartDelay) / durationScale);
+ }
+ }
+
+ private void initChildren() {
+ if (!isInitialized()) {
+ mChildrenInitialized = true;
+ skipToEndValue(false);
+ }
+ }
+
+ /**
+ * @param frameTime The frame start time, in the {@link SystemClock#uptimeMillis()} time
+ * base.
+ * @return
+ * @hide
+ */
+ @Override
+ public boolean doAnimationFrame(long frameTime) {
+ float durationScale = ValueAnimator.getDurationScale();
+ if (durationScale == 0f) {
+ // Duration scale is 0, end the animation right away.
+ forceToEnd();
+ return true;
+ }
+
+ // After the first frame comes in, we need to wait for start delay to pass before updating
+ // any animation values.
+ if (mFirstFrame < 0) {
+ mFirstFrame = frameTime;
+ }
+
+ // Handle pause/resume
+ if (mPaused) {
+ // Note: Child animations don't receive pause events. Since it's never a contract that
+ // the child animators will be paused when set is paused, this is unlikely to be an
+ // issue.
+ mPauseTime = frameTime;
+ removeAnimationCallback();
+ return false;
+ } else if (mPauseTime > 0) {
+ // Offset by the duration that the animation was paused
+ mFirstFrame += (frameTime - mPauseTime);
+ mPauseTime = -1;
+ }
+
+ // Continue at seeked position
+ if (mSeekState.isActive()) {
+ mSeekState.updateSeekDirection(mReversing);
+ if (mReversing) {
+ mFirstFrame = (long) (frameTime - mSeekState.getPlayTime() * durationScale);
+ } else {
+ mFirstFrame = (long) (frameTime - (mSeekState.getPlayTime() + mStartDelay)
+ * durationScale);
+ }
+ mSeekState.reset();
+ }
+
+ if (!mReversing && frameTime < mFirstFrame + mStartDelay * durationScale) {
+ // Still during start delay in a forward playing case.
+ return false;
+ }
+
+ // From here on, we always use unscaled play time. Note this unscaled playtime includes
+ // the start delay.
+ long unscaledPlayTime = (long) ((frameTime - mFirstFrame) / durationScale);
+ mLastFrameTime = frameTime;
+
+ // 1. Pulse the animators that will start or end in this frame
+ // 2. Pulse the animators that will finish in a later frame
+ int latestId = findLatestEventIdForTime(unscaledPlayTime);
+ int startId = mLastEventId;
+
+ handleAnimationEvents(startId, latestId, unscaledPlayTime);
+
+ mLastEventId = latestId;
+
+ // Pump a frame to the on-going animators
+ for (int i = 0; i < mPlayingSet.size(); i++) {
+ Node node = mPlayingSet.get(i);
+ if (!node.mEnded) {
+ pulseFrame(node, getPlayTimeForNodeIncludingDelay(unscaledPlayTime, node));
+ }
+ }
+
+ // Remove all the finished anims
+ for (int i = mPlayingSet.size() - 1; i >= 0; i--) {
+ if (mPlayingSet.get(i).mEnded) {
+ mPlayingSet.remove(i);
+ }
+ }
+
+ boolean finished = false;
+ if (mReversing) {
+ if (mPlayingSet.size() == 1 && mPlayingSet.get(0) == mRootNode) {
+ // The only animation that is running is the delay animation.
+ finished = true;
+ } else if (mPlayingSet.isEmpty() && mLastEventId < 3) {
+ // The only remaining animation is the delay animation
+ finished = true;
+ }
+ } else {
+ finished = mPlayingSet.isEmpty() && mLastEventId == mEvents.size() - 1;
+ }
+
+ if (finished) {
+ endAnimation();
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public void commitAnimationFrame(long frameTime) {
+ // No op.
+ }
+
+ @Override
+ boolean pulseAnimationFrame(long frameTime) {
+ return doAnimationFrame(frameTime);
+ }
+
+ /**
+ * When playing forward, we call start() at the animation's scheduled start time, and make sure
+ * to pump a frame at the animation's scheduled end time.
+ *
+ * When playing in reverse, we should reverse the animation when we hit animation's end event,
+ * and expect the animation to end at the its delay ended event, rather than start event.
+ */
+ private void handleAnimationEvents(int startId, int latestId, long playTime) {
+ if (mReversing) {
+ startId = startId == -1 ? mEvents.size() : startId;
+ for (int i = startId - 1; i >= latestId; i--) {
+ AnimationEvent event = mEvents.get(i);
+ Node node = event.mNode;
+ if (event.mEvent == AnimationEvent.ANIMATION_END) {
+ if (node.mAnimation.isStarted()) {
+ // If the animation has already been started before its due time (i.e.
+ // the child animator is being manipulated outside of the AnimatorSet), we
+ // need to cancel the animation to reset the internal state (e.g. frame
+ // time tracking) and remove the self pulsing callbacks
+ node.mAnimation.cancel();
+ }
+ node.mEnded = false;
+ mPlayingSet.add(event.mNode);
+ node.mAnimation.startWithoutPulsing(true);
+ pulseFrame(node, 0);
+ } else if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED && !node.mEnded) {
+ // end event:
+ pulseFrame(node, getPlayTimeForNodeIncludingDelay(playTime, node));
+ }
+ }
+ } else {
+ for (int i = startId + 1; i <= latestId; i++) {
+ AnimationEvent event = mEvents.get(i);
+ Node node = event.mNode;
+ if (event.mEvent == AnimationEvent.ANIMATION_START) {
+ mPlayingSet.add(event.mNode);
+ if (node.mAnimation.isStarted()) {
+ // If the animation has already been started before its due time (i.e.
+ // the child animator is being manipulated outside of the AnimatorSet), we
+ // need to cancel the animation to reset the internal state (e.g. frame
+ // time tracking) and remove the self pulsing callbacks
+ node.mAnimation.cancel();
+ }
+ node.mEnded = false;
+ node.mAnimation.startWithoutPulsing(false);
+ pulseFrame(node, 0);
+ } else if (event.mEvent == AnimationEvent.ANIMATION_END && !node.mEnded) {
+ // start event:
+ pulseFrame(node, getPlayTimeForNodeIncludingDelay(playTime, node));
+ }
+ }
+ }
+ }
+
+ /**
+ * This method pulses frames into child animations. It scales the input animation play time
+ * with the duration scale and pass that to the child animation via pulseAnimationFrame(long).
+ *
+ * @param node child animator node
+ * @param animPlayTime unscaled play time (including start delay) for the child animator
+ */
+ private void pulseFrame(Node node, long animPlayTime) {
+ if (!node.mEnded) {
+ float durationScale = ValueAnimator.getDurationScale();
+ durationScale = durationScale == 0 ? 1 : durationScale;
+ if (node.mAnimation.pulseAnimationFrame((long) (animPlayTime * durationScale))) {
+ node.mEnded = true;
+ }
+ }
+ }
+
+ private long getPlayTimeForNodeIncludingDelay(long overallPlayTime, Node node) {
+ return getPlayTimeForNodeIncludingDelay(overallPlayTime, node, mReversing);
+ }
+
+ private long getPlayTimeForNodeIncludingDelay(
+ long overallPlayTime,
+ Node node,
+ boolean inReverse
+ ) {
+ if (inReverse) {
+ overallPlayTime = getTotalDuration() - overallPlayTime;
+ return node.mEndTime - overallPlayTime;
+ } else {
+ return overallPlayTime - node.mStartTime;
+ }
+ }
+
+ private void startAnimation() {
+ addAnimationEndListener();
+
+ // Register animation callback
+ addAnimationCallback(0);
+
+ if (mSeekState.getPlayTimeNormalized() == 0 && mReversing) {
+ // Maintain old behavior, if seeked to 0 then call reverse, we'll treat the case
+ // the same as no seeking at all.
+ mSeekState.reset();
+ }
+ // Set the child animators to the right end:
+ if (mShouldResetValuesAtStart) {
+ if (isInitialized()) {
+ skipToEndValue(!mReversing);
+ } else if (mReversing) {
+ // Reversing but haven't initialized all the children yet.
+ initChildren();
+ skipToEndValue(!mReversing);
+ } else {
+ // If not all children are initialized and play direction is forward
+ for (int i = mEvents.size() - 1; i >= 0; i--) {
+ if (mEvents.get(i).mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) {
+ Animator anim = mEvents.get(i).mNode.mAnimation;
+ // Only reset the animations that have been initialized to start value,
+ // so that if they are defined without a start value, they will get the
+ // values set at the right time (i.e. the next animation run)
+ if (anim.isInitialized()) {
+ anim.skipToEndValue(true);
+ }
+ }
+ }
+ }
+ }
+
+ if (mReversing || mStartDelay == 0 || mSeekState.isActive()) {
+ long playTime;
+ // If no delay, we need to call start on the first animations to be consistent with old
+ // behavior.
+ if (mSeekState.isActive()) {
+ mSeekState.updateSeekDirection(mReversing);
+ playTime = mSeekState.getPlayTime();
+ } else {
+ playTime = 0;
+ }
+ int toId = findLatestEventIdForTime(playTime);
+ handleAnimationEvents(-1, toId, playTime);
+ for (int i = mPlayingSet.size() - 1; i >= 0; i--) {
+ if (mPlayingSet.get(i).mEnded) {
+ mPlayingSet.remove(i);
+ }
+ }
+ mLastEventId = toId;
+ }
+ }
+
+ // This is to work around the issue in b/34736819, as the old behavior in AnimatorSet had
+ // masked a real bug in play movies. TODO: remove this and below once the root cause is fixed.
+ private void addAnimationEndListener() {
+ for (int i = 1; i < mNodes.size(); i++) {
+ mNodes.get(i).mAnimation.addListener(mAnimationEndListener);
+ }
+ }
+
+ private void removeAnimationEndListener() {
+ for (int i = 1; i < mNodes.size(); i++) {
+ mNodes.get(i).mAnimation.removeListener(mAnimationEndListener);
+ }
+ }
+
+ private int findLatestEventIdForTime(long currentPlayTime) {
+ int size = mEvents.size();
+ int latestId = mLastEventId;
+ // Call start on the first animations now to be consistent with the old behavior
+ if (mReversing) {
+ currentPlayTime = getTotalDuration() - currentPlayTime;
+ mLastEventId = mLastEventId == -1 ? size : mLastEventId;
+ for (int j = mLastEventId - 1; j >= 0; j--) {
+ AnimationEvent event = mEvents.get(j);
+ if (event.getTime() >= currentPlayTime) {
+ latestId = j;
+ }
+ }
+ } else {
+ for (int i = mLastEventId + 1; i < size; i++) {
+ AnimationEvent event = mEvents.get(i);
+ // TODO: need a function that accounts for infinite duration to compare time
+ if (event.getTime() != DURATION_INFINITE && event.getTime() <= currentPlayTime) {
+ latestId = i;
+ }
+ }
+ }
+ return latestId;
+ }
+
+ private void endAnimation() {
+ mStarted = false;
+ mLastFrameTime = -1;
+ mFirstFrame = -1;
+ mLastEventId = -1;
+ mPaused = false;
+ mPauseTime = -1;
+ mSeekState.reset();
+ mPlayingSet.clear();
+
+ // No longer receive callbacks
+ removeAnimationCallback();
+ notifyEndListeners(mReversing);
+ removeAnimationEndListener();
+ mSelfPulse = true;
+ mReversing = false;
+ }
+
+ private void removeAnimationCallback() {
+ if (!mSelfPulse) {
+ return;
+ }
+ AnimationHandler handler = AnimationHandler.getInstance();
+ handler.removeCallback(this);
+ }
+
+ private void addAnimationCallback(long delay) {
+ if (!mSelfPulse) {
+ return;
+ }
+ AnimationHandler handler = AnimationHandler.getInstance();
+ handler.addAnimationFrameCallback(this, delay);
+ }
+
+ @Override
+ public AnimatorSet clone() {
+ final AnimatorSet anim = (AnimatorSet) super.clone();
+ /*
+ * The basic clone() operation copies all items. This doesn't work very well for
+ * AnimatorSet, because it will copy references that need to be recreated and state
+ * that may not apply. What we need to do now is put the clone in an uninitialized
+ * state, with fresh, empty data structures. Then we will build up the nodes list
+ * manually, as we clone each Node (and its animation). The clone will then be sorted,
+ * and will populate any appropriate lists, when it is started.
+ */
+ final int nodeCount = mNodes.size();
+ anim.mStarted = false;
+ anim.mLastFrameTime = -1;
+ anim.mFirstFrame = -1;
+ anim.mLastEventId = -1;
+ anim.mPaused = false;
+ anim.mPauseTime = -1;
+ anim.mSeekState = new SeekState();
+ anim.mSelfPulse = true;
+ anim.mStartListenersCalled = false;
+ anim.mPlayingSet = new ArrayList<Node>();
+ anim.mNodeMap = new ArrayMap<Animator, Node>();
+ anim.mNodes = new ArrayList<Node>(nodeCount);
+ anim.mEvents = new ArrayList<AnimationEvent>();
+ anim.mAnimationEndListener = new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (anim.mNodeMap.get(animation) == null) {
+ throw new AndroidRuntimeException("Error: animation ended is not in the node"
+ + " map");
+ }
+ anim.mNodeMap.get(animation).mEnded = true;
+
+ }
+ };
+ anim.mReversing = false;
+ anim.mDependencyDirty = true;
+
+ // Walk through the old nodes list, cloning each node and adding it to the new nodemap.
+ // One problem is that the old node dependencies point to nodes in the old AnimatorSet.
+ // We need to track the old/new nodes in order to reconstruct the dependencies in the clone.
+
+ HashMap<Node, Node> clonesMap = new HashMap<>(nodeCount);
+ for (int n = 0; n < nodeCount; n++) {
+ final Node node = mNodes.get(n);
+ Node nodeClone = node.clone();
+ // Remove the old internal listener from the cloned child
+ nodeClone.mAnimation.removeListener(mAnimationEndListener);
+ clonesMap.put(node, nodeClone);
+ anim.mNodes.add(nodeClone);
+ anim.mNodeMap.put(nodeClone.mAnimation, nodeClone);
+ }
+
+ anim.mRootNode = clonesMap.get(mRootNode);
+ anim.mDelayAnim = (ValueAnimator) anim.mRootNode.mAnimation;
+
+ // Now that we've cloned all of the nodes, we're ready to walk through their
+ // dependencies, mapping the old dependencies to the new nodes
+ for (int i = 0; i < nodeCount; i++) {
+ Node node = mNodes.get(i);
+ // Update dependencies for node's clone
+ Node nodeClone = clonesMap.get(node);
+ nodeClone.mLatestParent = node.mLatestParent == null
+ ? null : clonesMap.get(node.mLatestParent);
+ int size = node.mChildNodes == null ? 0 : node.mChildNodes.size();
+ for (int j = 0; j < size; j++) {
+ nodeClone.mChildNodes.set(j, clonesMap.get(node.mChildNodes.get(j)));
+ }
+ size = node.mSiblings == null ? 0 : node.mSiblings.size();
+ for (int j = 0; j < size; j++) {
+ nodeClone.mSiblings.set(j, clonesMap.get(node.mSiblings.get(j)));
+ }
+ size = node.mParents == null ? 0 : node.mParents.size();
+ for (int j = 0; j < size; j++) {
+ nodeClone.mParents.set(j, clonesMap.get(node.mParents.get(j)));
+ }
+ }
+ return anim;
+ }
+
+
+ /**
+ * AnimatorSet is only reversible when the set contains no sequential animation, and no child
+ * animators have a start delay.
+ * @hide
+ */
+ @Override
+ public boolean canReverse() {
+ return getTotalDuration() != DURATION_INFINITE;
+ }
+
+ /**
+ * Plays the AnimatorSet in reverse. If the animation has been seeked to a specific play time
+ * using {@link #setCurrentPlayTime(long)}, it will play backwards from the point seeked when
+ * reverse was called. Otherwise, then it will start from the end and play backwards. This
+ * behavior is only set for the current animation; future playing of the animation will use the
+ * default behavior of playing forward.
+ * <p>
+ * Note: reverse is not supported for infinite AnimatorSet.
+ */
+ @Override
+ public void reverse() {
+ start(true, true);
+ }
+
+ @Override
+ public String toString() {
+ String returnVal = "AnimatorSet@" + Integer.toHexString(hashCode()) + "{";
+ int size = mNodes.size();
+ for (int i = 0; i < size; i++) {
+ Node node = mNodes.get(i);
+ returnVal += "\n " + node.mAnimation.toString();
+ }
+ return returnVal + "\n}";
+ }
+
+ private void printChildCount() {
+ // Print out the child count through a level traverse.
+ ArrayList<Node> list = new ArrayList<>(mNodes.size());
+ list.add(mRootNode);
+ Log.d(TAG, "Current tree: ");
+ int index = 0;
+ while (index < list.size()) {
+ int listSize = list.size();
+ StringBuilder builder = new StringBuilder();
+ for (; index < listSize; index++) {
+ Node node = list.get(index);
+ int num = 0;
+ if (node.mChildNodes != null) {
+ for (int i = 0; i < node.mChildNodes.size(); i++) {
+ Node child = node.mChildNodes.get(i);
+ if (child.mLatestParent == node) {
+ num++;
+ list.add(child);
+ }
+ }
+ }
+ builder.append(" ");
+ builder.append(num);
+ }
+ Log.d(TAG, builder.toString());
+ }
+ }
+
+ private void createDependencyGraph() {
+ if (!mDependencyDirty) {
+ // Check whether any duration of the child animations has changed
+ boolean durationChanged = false;
+ for (int i = 0; i < mNodes.size(); i++) {
+ Animator anim = mNodes.get(i).mAnimation;
+ if (mNodes.get(i).mTotalDuration != anim.getTotalDuration()) {
+ durationChanged = true;
+ break;
+ }
+ }
+ if (!durationChanged) {
+ return;
+ }
+ }
+
+ mDependencyDirty = false;
+ // Traverse all the siblings and make sure they have all the parents
+ int size = mNodes.size();
+ for (int i = 0; i < size; i++) {
+ mNodes.get(i).mParentsAdded = false;
+ }
+ for (int i = 0; i < size; i++) {
+ Node node = mNodes.get(i);
+ if (node.mParentsAdded) {
+ continue;
+ }
+
+ node.mParentsAdded = true;
+ if (node.mSiblings == null) {
+ continue;
+ }
+
+ // Find all the siblings
+ findSiblings(node, node.mSiblings);
+ node.mSiblings.remove(node);
+
+ // Get parents from all siblings
+ int siblingSize = node.mSiblings.size();
+ for (int j = 0; j < siblingSize; j++) {
+ node.addParents(node.mSiblings.get(j).mParents);
+ }
+
+ // Now make sure all siblings share the same set of parents
+ for (int j = 0; j < siblingSize; j++) {
+ Node sibling = node.mSiblings.get(j);
+ sibling.addParents(node.mParents);
+ sibling.mParentsAdded = true;
+ }
+ }
+
+ for (int i = 0; i < size; i++) {
+ Node node = mNodes.get(i);
+ if (node != mRootNode && node.mParents == null) {
+ node.addParent(mRootNode);
+ }
+ }
+
+ // Do a DFS on the tree
+ ArrayList<Node> visited = new ArrayList<Node>(mNodes.size());
+ // Assign start/end time
+ mRootNode.mStartTime = 0;
+ mRootNode.mEndTime = mDelayAnim.getDuration();
+ updatePlayTime(mRootNode, visited);
+
+ sortAnimationEvents();
+ mTotalDuration = mEvents.get(mEvents.size() - 1).getTime();
+ }
+
+ private void sortAnimationEvents() {
+ // Sort the list of events in ascending order of their time
+ // Create the list including the delay animation.
+ mEvents.clear();
+ for (int i = 1; i < mNodes.size(); i++) {
+ Node node = mNodes.get(i);
+ mEvents.add(new AnimationEvent(node, AnimationEvent.ANIMATION_START));
+ mEvents.add(new AnimationEvent(node, AnimationEvent.ANIMATION_DELAY_ENDED));
+ mEvents.add(new AnimationEvent(node, AnimationEvent.ANIMATION_END));
+ }
+ mEvents.sort(new Comparator<AnimationEvent>() {
+ @Override
+ public int compare(AnimationEvent e1, AnimationEvent e2) {
+ long t1 = e1.getTime();
+ long t2 = e2.getTime();
+ if (t1 == t2) {
+ // For events that happen at the same time, we need them to be in the sequence
+ // (end, start, start delay ended)
+ if (e2.mEvent + e1.mEvent == AnimationEvent.ANIMATION_START
+ + AnimationEvent.ANIMATION_DELAY_ENDED) {
+ // Ensure start delay happens after start
+ return e1.mEvent - e2.mEvent;
+ } else {
+ return e2.mEvent - e1.mEvent;
+ }
+ }
+ if (t2 == DURATION_INFINITE) {
+ return -1;
+ }
+ if (t1 == DURATION_INFINITE) {
+ return 1;
+ }
+ // When neither event happens at INFINITE time:
+ return t1 - t2 > 0 ? 1 : -1;
+ }
+ });
+
+ int eventSize = mEvents.size();
+ // For the same animation, start event has to happen before end.
+ for (int i = 0; i < eventSize;) {
+ AnimationEvent event = mEvents.get(i);
+ if (event.mEvent == AnimationEvent.ANIMATION_END) {
+ boolean needToSwapStart;
+ if (event.mNode.mStartTime == event.mNode.mEndTime) {
+ needToSwapStart = true;
+ } else if (event.mNode.mEndTime == event.mNode.mStartTime
+ + event.mNode.mAnimation.getStartDelay()) {
+ // Swapping start delay
+ needToSwapStart = false;
+ } else {
+ i++;
+ continue;
+ }
+
+ int startEventId = eventSize;
+ int startDelayEndId = eventSize;
+ for (int j = i + 1; j < eventSize; j++) {
+ if (startEventId < eventSize && startDelayEndId < eventSize) {
+ break;
+ }
+ if (mEvents.get(j).mNode == event.mNode) {
+ if (mEvents.get(j).mEvent == AnimationEvent.ANIMATION_START) {
+ // Found start event
+ startEventId = j;
+ } else if (mEvents.get(j).mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) {
+ startDelayEndId = j;
+ }
+ }
+
+ }
+ if (needToSwapStart && startEventId == mEvents.size()) {
+ throw new UnsupportedOperationException("Something went wrong, no start is"
+ + "found after stop for an animation that has the same start and end"
+ + "time.");
+
+ }
+ if (startDelayEndId == mEvents.size()) {
+ throw new UnsupportedOperationException("Something went wrong, no start"
+ + "delay end is found after stop for an animation");
+
+ }
+
+ // We need to make sure start is inserted before start delay ended event,
+ // because otherwise inserting start delay ended events first would change
+ // the start event index.
+ if (needToSwapStart) {
+ AnimationEvent startEvent = mEvents.remove(startEventId);
+ mEvents.add(i, startEvent);
+ i++;
+ }
+
+ AnimationEvent startDelayEndEvent = mEvents.remove(startDelayEndId);
+ mEvents.add(i, startDelayEndEvent);
+ i += 2;
+ } else {
+ i++;
+ }
+ }
+
+ if (!mEvents.isEmpty() && mEvents.get(0).mEvent != AnimationEvent.ANIMATION_START) {
+ throw new UnsupportedOperationException(
+ "Sorting went bad, the start event should always be at index 0");
+ }
+
+ // Add AnimatorSet's start delay node to the beginning
+ mEvents.add(0, new AnimationEvent(mRootNode, AnimationEvent.ANIMATION_START));
+ mEvents.add(1, new AnimationEvent(mRootNode, AnimationEvent.ANIMATION_DELAY_ENDED));
+ mEvents.add(2, new AnimationEvent(mRootNode, AnimationEvent.ANIMATION_END));
+
+ if (mEvents.get(mEvents.size() - 1).mEvent == AnimationEvent.ANIMATION_START
+ || mEvents.get(mEvents.size() - 1).mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) {
+ throw new UnsupportedOperationException(
+ "Something went wrong, the last event is not an end event");
+ }
+ }
+
+ /**
+ * Based on parent's start/end time, calculate children's start/end time. If cycle exists in
+ * the graph, all the nodes on the cycle will be marked to start at {@link #DURATION_INFINITE},
+ * meaning they will ever play.
+ */
+ private void updatePlayTime(Node parent, ArrayList<Node> visited) {
+ if (parent.mChildNodes == null) {
+ if (parent == mRootNode) {
+ // All the animators are in a cycle
+ for (int i = 0; i < mNodes.size(); i++) {
+ Node node = mNodes.get(i);
+ if (node != mRootNode) {
+ node.mStartTime = DURATION_INFINITE;
+ node.mEndTime = DURATION_INFINITE;
+ }
+ }
+ }
+ return;
+ }
+
+ visited.add(parent);
+ int childrenSize = parent.mChildNodes.size();
+ for (int i = 0; i < childrenSize; i++) {
+ Node child = parent.mChildNodes.get(i);
+ child.mTotalDuration = child.mAnimation.getTotalDuration(); // Update cached duration.
+
+ int index = visited.indexOf(child);
+ if (index >= 0) {
+ // Child has been visited, cycle found. Mark all the nodes in the cycle.
+ for (int j = index; j < visited.size(); j++) {
+ visited.get(j).mLatestParent = null;
+ visited.get(j).mStartTime = DURATION_INFINITE;
+ visited.get(j).mEndTime = DURATION_INFINITE;
+ }
+ child.mStartTime = DURATION_INFINITE;
+ child.mEndTime = DURATION_INFINITE;
+ child.mLatestParent = null;
+ Log.w(TAG, "Cycle found in AnimatorSet: " + this);
+ continue;
+ }
+
+ if (child.mStartTime != DURATION_INFINITE) {
+ if (parent.mEndTime == DURATION_INFINITE) {
+ child.mLatestParent = parent;
+ child.mStartTime = DURATION_INFINITE;
+ child.mEndTime = DURATION_INFINITE;
+ } else {
+ if (parent.mEndTime >= child.mStartTime) {
+ child.mLatestParent = parent;
+ child.mStartTime = parent.mEndTime;
+ }
+
+ child.mEndTime = child.mTotalDuration == DURATION_INFINITE
+ ? DURATION_INFINITE : child.mStartTime + child.mTotalDuration;
+ }
+ }
+ updatePlayTime(child, visited);
+ }
+ visited.remove(parent);
+ }
+
+ // Recursively find all the siblings
+ private void findSiblings(Node node, ArrayList<Node> siblings) {
+ if (!siblings.contains(node)) {
+ siblings.add(node);
+ if (node.mSiblings == null) {
+ return;
+ }
+ for (int i = 0; i < node.mSiblings.size(); i++) {
+ findSiblings(node.mSiblings.get(i), siblings);
+ }
+ }
+ }
+
+ /**
+ * @hide
+ * TODO: For animatorSet defined in XML, we can use a flag to indicate what the play order
+ * if defined (i.e. sequential or together), then we can use the flag instead of calculating
+ * dynamically. Note that when AnimatorSet is empty this method returns true.
+ * @return whether all the animators in the set are supposed to play together
+ */
+ public boolean shouldPlayTogether() {
+ updateAnimatorsDuration();
+ createDependencyGraph();
+ // All the child nodes are set out to play right after the delay animation
+ return mRootNode.mChildNodes == null || mRootNode.mChildNodes.size() == mNodes.size() - 1;
+ }
+
+ @Override
+ public long getTotalDuration() {
+ updateAnimatorsDuration();
+ createDependencyGraph();
+ return mTotalDuration;
+ }
+
+ private Node getNodeForAnimation(Animator anim) {
+ Node node = mNodeMap.get(anim);
+ if (node == null) {
+ node = new Node(anim);
+ mNodeMap.put(anim, node);
+ mNodes.add(node);
+ }
+ return node;
+ }
+
+ /**
+ * A Node is an embodiment of both the Animator that it wraps as well as
+ * any dependencies that are associated with that Animation. This includes
+ * both dependencies upon other nodes (in the dependencies list) as
+ * well as dependencies of other nodes upon this (in the nodeDependents list).
+ */
+ private static class Node implements Cloneable {
+ Animator mAnimation;
+
+ /**
+ * Child nodes are the nodes associated with animations that will be played immediately
+ * after current node.
+ */
+ ArrayList<Node> mChildNodes = null;
+
+ /**
+ * Flag indicating whether the animation in this node is finished. This flag
+ * is used by AnimatorSet to check, as each animation ends, whether all child animations
+ * are mEnded and it's time to send out an end event for the entire AnimatorSet.
+ */
+ boolean mEnded = false;
+
+ /**
+ * Nodes with animations that are defined to play simultaneously with the animation
+ * associated with this current node.
+ */
+ ArrayList<Node> mSiblings;
+
+ /**
+ * Parent nodes are the nodes with animations preceding current node's animation. Parent
+ * nodes here are derived from user defined animation sequence.
+ */
+ ArrayList<Node> mParents;
+
+ /**
+ * Latest parent is the parent node associated with a animation that finishes after all
+ * the other parents' animations.
+ */
+ Node mLatestParent = null;
+
+ boolean mParentsAdded = false;
+ long mStartTime = 0;
+ long mEndTime = 0;
+ long mTotalDuration = 0;
+
+ /**
+ * Constructs the Node with the animation that it encapsulates. A Node has no
+ * dependencies by default; dependencies are added via the addDependency()
+ * method.
+ *
+ * @param animation The animation that the Node encapsulates.
+ */
+ public Node(Animator animation) {
+ this.mAnimation = animation;
+ }
+
+ @Override
+ public Node clone() {
+ try {
+ Node node = (Node) super.clone();
+ node.mAnimation = mAnimation.clone();
+ if (mChildNodes != null) {
+ node.mChildNodes = new ArrayList<>(mChildNodes);
+ }
+ if (mSiblings != null) {
+ node.mSiblings = new ArrayList<>(mSiblings);
+ }
+ if (mParents != null) {
+ node.mParents = new ArrayList<>(mParents);
+ }
+ node.mEnded = false;
+ return node;
+ } catch (CloneNotSupportedException e) {
+ throw new AssertionError();
+ }
+ }
+
+ void addChild(Node node) {
+ if (mChildNodes == null) {
+ mChildNodes = new ArrayList<>();
+ }
+ if (!mChildNodes.contains(node)) {
+ mChildNodes.add(node);
+ node.addParent(this);
+ }
+ }
+
+ public void addSibling(Node node) {
+ if (mSiblings == null) {
+ mSiblings = new ArrayList<Node>();
+ }
+ if (!mSiblings.contains(node)) {
+ mSiblings.add(node);
+ node.addSibling(this);
+ }
+ }
+
+ public void addParent(Node node) {
+ if (mParents == null) {
+ mParents = new ArrayList<Node>();
+ }
+ if (!mParents.contains(node)) {
+ mParents.add(node);
+ node.addChild(this);
+ }
+ }
+
+ public void addParents(ArrayList<Node> parents) {
+ if (parents == null) {
+ return;
+ }
+ int size = parents.size();
+ for (int i = 0; i < size; i++) {
+ addParent(parents.get(i));
+ }
+ }
+ }
+
+ /**
+ * This class is a wrapper around a node and an event for the animation corresponding to the
+ * node. The 3 types of events represent the start of an animation, the end of a start delay of
+ * an animation, and the end of an animation. When playing forward (i.e. in the non-reverse
+ * direction), start event marks when start() should be called, and end event corresponds to
+ * when the animation should finish. When playing in reverse, start delay will not be a part
+ * of the animation. Therefore, reverse() is called at the end event, and animation should end
+ * at the delay ended event.
+ */
+ private static class AnimationEvent {
+ static final int ANIMATION_START = 0;
+ static final int ANIMATION_DELAY_ENDED = 1;
+ static final int ANIMATION_END = 2;
+ final Node mNode;
+ final int mEvent;
+
+ AnimationEvent(Node node, int event) {
+ mNode = node;
+ mEvent = event;
+ }
+
+ long getTime() {
+ if (mEvent == ANIMATION_START) {
+ return mNode.mStartTime;
+ } else if (mEvent == ANIMATION_DELAY_ENDED) {
+ return mNode.mStartTime == DURATION_INFINITE
+ ? DURATION_INFINITE : mNode.mStartTime + mNode.mAnimation.getStartDelay();
+ } else {
+ return mNode.mEndTime;
+ }
+ }
+
+ public String toString() {
+ String eventStr = mEvent == ANIMATION_START ? "start" : (
+ mEvent == ANIMATION_DELAY_ENDED ? "delay ended" : "end");
+ return eventStr + " " + mNode.mAnimation.toString();
+ }
+ }
+
+ private class SeekState {
+ private long mPlayTime = -1;
+ private boolean mSeekingInReverse = false;
+ void reset() {
+ mPlayTime = -1;
+ mSeekingInReverse = false;
+ }
+
+ void setPlayTime(long playTime, boolean inReverse) {
+ // Clamp the play time
+ if (getTotalDuration() != DURATION_INFINITE) {
+ mPlayTime = Math.min(playTime, getTotalDuration() - mStartDelay);
+ } else {
+ mPlayTime = playTime;
+ }
+ mPlayTime = Math.max(0, mPlayTime);
+ mSeekingInReverse = inReverse;
+ }
+
+ void updateSeekDirection(boolean inReverse) {
+ // Change seek direction without changing the overall fraction
+ if (inReverse && getTotalDuration() == DURATION_INFINITE) {
+ throw new UnsupportedOperationException("Error: Cannot reverse infinite animator"
+ + " set");
+ }
+ if (mPlayTime >= 0) {
+ if (inReverse != mSeekingInReverse) {
+ mPlayTime = getTotalDuration() - mStartDelay - mPlayTime;
+ mSeekingInReverse = inReverse;
+ }
+ }
+ }
+
+ long getPlayTime() {
+ return mPlayTime;
+ }
+
+ /**
+ * Returns the playtime assuming the animation is forward playing
+ */
+ long getPlayTimeNormalized() {
+ if (mReversing) {
+ return getTotalDuration() - mStartDelay - mPlayTime;
+ }
+ return mPlayTime;
+ }
+
+ boolean isActive() {
+ return mPlayTime != -1;
+ }
+ }
+
+ /**
+ * The <code>Builder</code> object is a utility class to facilitate adding animations to a
+ * <code>AnimatorSet</code> along with the relationships between the various animations. The
+ * intention of the <code>Builder</code> methods, along with the {@link
+ * AnimatorSet#play(Animator) play()} method of <code>AnimatorSet</code> is to make it possible
+ * to express the dependency relationships of animations in a natural way. Developers can also
+ * use the {@link AnimatorSet#playTogether(Animator[]) playTogether()} and {@link
+ * AnimatorSet#playSequentially(Animator[]) playSequentially()} methods if these suit the need,
+ * but it might be easier in some situations to express the AnimatorSet of animations in pairs.
+ * <p/>
+ * <p>The <code>Builder</code> object cannot be constructed directly, but is rather constructed
+ * internally via a call to {@link AnimatorSet#play(Animator)}.</p>
+ * <p/>
+ * <p>For example, this sets up a AnimatorSet to play anim1 and anim2 at the same time, anim3 to
+ * play when anim2 finishes, and anim4 to play when anim3 finishes:</p>
+ * <pre>
+ * AnimatorSet s = new AnimatorSet();
+ * s.play(anim1).with(anim2);
+ * s.play(anim2).before(anim3);
+ * s.play(anim4).after(anim3);
+ * </pre>
+ * <p/>
+ * <p>Note in the example that both {@link Builder#before(Animator)} and {@link
+ * Builder#after(Animator)} are used. These are just different ways of expressing the same
+ * relationship and are provided to make it easier to say things in a way that is more natural,
+ * depending on the situation.</p>
+ * <p/>
+ * <p>It is possible to make several calls into the same <code>Builder</code> object to express
+ * multiple relationships. However, note that it is only the animation passed into the initial
+ * {@link AnimatorSet#play(Animator)} method that is the dependency in any of the successive
+ * calls to the <code>Builder</code> object. For example, the following code starts both anim2
+ * and anim3 when anim1 ends; there is no direct dependency relationship between anim2 and
+ * anim3:
+ * <pre>
+ * AnimatorSet s = new AnimatorSet();
+ * s.play(anim1).before(anim2).before(anim3);
+ * </pre>
+ * If the desired result is to play anim1 then anim2 then anim3, this code expresses the
+ * relationship correctly:</p>
+ * <pre>
+ * AnimatorSet s = new AnimatorSet();
+ * s.play(anim1).before(anim2);
+ * s.play(anim2).before(anim3);
+ * </pre>
+ * <p/>
+ * <p>Note that it is possible to express relationships that cannot be resolved and will not
+ * result in sensible results. For example, <code>play(anim1).after(anim1)</code> makes no
+ * sense. In general, circular dependencies like this one (or more indirect ones where a depends
+ * on b, which depends on c, which depends on a) should be avoided. Only create AnimatorSets
+ * that can boil down to a simple, one-way relationship of animations starting with, before, and
+ * after other, different, animations.</p>
+ */
+ public class Builder {
+
+ /**
+ * This tracks the current node being processed. It is supplied to the play() method
+ * of AnimatorSet and passed into the constructor of Builder.
+ */
+ private Node mCurrentNode;
+
+ /**
+ * package-private constructor. Builders are only constructed by AnimatorSet, when the
+ * play() method is called.
+ *
+ * @param anim The animation that is the dependency for the other animations passed into
+ * the other methods of this Builder object.
+ */
+ Builder(Animator anim) {
+ mDependencyDirty = true;
+ mCurrentNode = getNodeForAnimation(anim);
+ }
+
+ /**
+ * Sets up the given animation to play at the same time as the animation supplied in the
+ * {@link AnimatorSet#play(Animator)} call that created this <code>Builder</code> object.
+ *
+ * @param anim The animation that will play when the animation supplied to the
+ * {@link AnimatorSet#play(Animator)} method starts.
+ */
+ public Builder with(Animator anim) {
+ Node node = getNodeForAnimation(anim);
+ mCurrentNode.addSibling(node);
+ return this;
+ }
+
+ /**
+ * Sets up the given animation to play when the animation supplied in the
+ * {@link AnimatorSet#play(Animator)} call that created this <code>Builder</code> object
+ * ends.
+ *
+ * @param anim The animation that will play when the animation supplied to the
+ * {@link AnimatorSet#play(Animator)} method ends.
+ */
+ public Builder before(Animator anim) {
+ Node node = getNodeForAnimation(anim);
+ mCurrentNode.addChild(node);
+ return this;
+ }
+
+ /**
+ * Sets up the given animation to play when the animation supplied in the
+ * {@link AnimatorSet#play(Animator)} call that created this <code>Builder</code> object
+ * to start when the animation supplied in this method call ends.
+ *
+ * @param anim The animation whose end will cause the animation supplied to the
+ * {@link AnimatorSet#play(Animator)} method to play.
+ */
+ public Builder after(Animator anim) {
+ Node node = getNodeForAnimation(anim);
+ mCurrentNode.addParent(node);
+ return this;
+ }
+
+ /**
+ * Sets up the animation supplied in the
+ * {@link AnimatorSet#play(Animator)} call that created this <code>Builder</code> object
+ * to play when the given amount of time elapses.
+ *
+ * @param delay The number of milliseconds that should elapse before the
+ * animation starts.
+ */
+ public Builder after(long delay) {
+ // setup a ValueAnimator just to run the clock
+ ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
+ anim.setDuration(delay);
+ after(anim);
+ return this;
+ }
+
+ }
+
+}
diff --git a/android-35/android/animation/ArgbEvaluator.java b/android-35/android/animation/ArgbEvaluator.java
new file mode 100644
index 0000000..9519ddd
--- /dev/null
+++ b/android-35/android/animation/ArgbEvaluator.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2010 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.animation;
+
+import android.compat.annotation.UnsupportedAppUsage;
+
+/**
+ * This evaluator can be used to perform type interpolation between integer
+ * values that represent ARGB colors.
+ */
+public class ArgbEvaluator implements TypeEvaluator {
+ private static final ArgbEvaluator sInstance = new ArgbEvaluator();
+
+ /**
+ * Returns an instance of <code>ArgbEvaluator</code> that may be used in
+ * {@link ValueAnimator#setEvaluator(TypeEvaluator)}. The same instance may
+ * be used in multiple <code>Animator</code>s because it holds no state.
+ * @return An instance of <code>ArgbEvalutor</code>.
+ *
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public static ArgbEvaluator getInstance() {
+ return sInstance;
+ }
+
+ /**
+ * This function returns the calculated in-between value for a color
+ * given integers that represent the start and end values in the four
+ * bytes of the 32-bit int. Each channel is separately linearly interpolated
+ * and the resulting calculated values are recombined into the return value.
+ *
+ * @param fraction The fraction from the starting to the ending values
+ * @param startValue A 32-bit int value representing colors in the
+ * separate bytes of the parameter
+ * @param endValue A 32-bit int value representing colors in the
+ * separate bytes of the parameter
+ * @return A value that is calculated to be the linearly interpolated
+ * result, derived by separating the start and end values into separate
+ * color channels and interpolating each one separately, recombining the
+ * resulting values in the same way.
+ */
+ public Object evaluate(float fraction, Object startValue, Object endValue) {
+ int startInt = (Integer) startValue;
+ float startA = ((startInt >> 24) & 0xff) / 255.0f;
+ float startR = ((startInt >> 16) & 0xff) / 255.0f;
+ float startG = ((startInt >> 8) & 0xff) / 255.0f;
+ float startB = ( startInt & 0xff) / 255.0f;
+
+ int endInt = (Integer) endValue;
+ float endA = ((endInt >> 24) & 0xff) / 255.0f;
+ float endR = ((endInt >> 16) & 0xff) / 255.0f;
+ float endG = ((endInt >> 8) & 0xff) / 255.0f;
+ float endB = ( endInt & 0xff) / 255.0f;
+
+ // convert from sRGB to linear
+ startR = (float) Math.pow(startR, 2.2);
+ startG = (float) Math.pow(startG, 2.2);
+ startB = (float) Math.pow(startB, 2.2);
+
+ endR = (float) Math.pow(endR, 2.2);
+ endG = (float) Math.pow(endG, 2.2);
+ endB = (float) Math.pow(endB, 2.2);
+
+ // compute the interpolated color in linear space
+ float a = startA + fraction * (endA - startA);
+ float r = startR + fraction * (endR - startR);
+ float g = startG + fraction * (endG - startG);
+ float b = startB + fraction * (endB - startB);
+
+ // convert back to sRGB in the [0..255] range
+ a = a * 255.0f;
+ r = (float) Math.pow(r, 1.0 / 2.2) * 255.0f;
+ g = (float) Math.pow(g, 1.0 / 2.2) * 255.0f;
+ b = (float) Math.pow(b, 1.0 / 2.2) * 255.0f;
+
+ return Math.round(a) << 24 | Math.round(r) << 16 | Math.round(g) << 8 | Math.round(b);
+ }
+}
\ No newline at end of file
diff --git a/android-35/android/animation/BidirectionalTypeConverter.java b/android-35/android/animation/BidirectionalTypeConverter.java
new file mode 100644
index 0000000..960650e
--- /dev/null
+++ b/android-35/android/animation/BidirectionalTypeConverter.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2014 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.animation;
+
+/**
+ * Abstract base class used convert type T to another type V and back again. This
+ * is necessary when the value types of in animation are different from the property
+ * type. BidirectionalTypeConverter is needed when only the final value for the
+ * animation is supplied to animators.
+ * @see PropertyValuesHolder#setConverter(TypeConverter)
+ */
+public abstract class BidirectionalTypeConverter<T, V> extends TypeConverter<T, V> {
+ private BidirectionalTypeConverter mInvertedConverter;
+
+ public BidirectionalTypeConverter(Class<T> fromClass, Class<V> toClass) {
+ super(fromClass, toClass);
+ }
+
+ /**
+ * Does a conversion from the target type back to the source type. The subclass
+ * must implement this when a TypeConverter is used in animations and current
+ * values will need to be read for an animation.
+ * @param value The Object to convert.
+ * @return A value of type T, converted from <code>value</code>.
+ */
+ public abstract T convertBack(V value);
+
+ /**
+ * Returns the inverse of this converter, where the from and to classes are reversed.
+ * The inverted converter uses this convert to call {@link #convertBack(Object)} for
+ * {@link #convert(Object)} calls and {@link #convert(Object)} for
+ * {@link #convertBack(Object)} calls.
+ * @return The inverse of this converter, where the from and to classes are reversed.
+ */
+ public BidirectionalTypeConverter<V, T> invert() {
+ if (mInvertedConverter == null) {
+ mInvertedConverter = new InvertedConverter(this);
+ }
+ return mInvertedConverter;
+ }
+
+ private static class InvertedConverter<From, To> extends BidirectionalTypeConverter<From, To> {
+ private BidirectionalTypeConverter<To, From> mConverter;
+
+ public InvertedConverter(BidirectionalTypeConverter<To, From> converter) {
+ super(converter.getTargetType(), converter.getSourceType());
+ mConverter = converter;
+ }
+
+ @Override
+ public From convertBack(To value) {
+ return mConverter.convert(value);
+ }
+
+ @Override
+ public To convert(From value) {
+ return mConverter.convertBack(value);
+ }
+ }
+}
diff --git a/android-35/android/animation/FloatArrayEvaluator.java b/android-35/android/animation/FloatArrayEvaluator.java
new file mode 100644
index 0000000..9ae1197
--- /dev/null
+++ b/android-35/android/animation/FloatArrayEvaluator.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2013 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.animation;
+
+/**
+ * This evaluator can be used to perform type interpolation between <code>float[]</code> values.
+ * Each index into the array is treated as a separate value to interpolate. For example,
+ * evaluating <code>{100, 200}</code> and <code>{300, 400}</code> will interpolate the value at
+ * the first index between 100 and 300 and the value at the second index value between 200 and 400.
+ */
+public class FloatArrayEvaluator implements TypeEvaluator<float[]> {
+
+ private float[] mArray;
+
+ /**
+ * Create a FloatArrayEvaluator that does not reuse the animated value. Care must be taken
+ * when using this option because on every evaluation a new <code>float[]</code> will be
+ * allocated.
+ *
+ * @see #FloatArrayEvaluator(float[])
+ */
+ public FloatArrayEvaluator() {
+ }
+
+ /**
+ * Create a FloatArrayEvaluator that reuses <code>reuseArray</code> for every evaluate() call.
+ * Caution must be taken to ensure that the value returned from
+ * {@link android.animation.ValueAnimator#getAnimatedValue()} is not cached, modified, or
+ * used across threads. The value will be modified on each <code>evaluate()</code> call.
+ *
+ * @param reuseArray The array to modify and return from <code>evaluate</code>.
+ */
+ public FloatArrayEvaluator(float[] reuseArray) {
+ mArray = reuseArray;
+ }
+
+ /**
+ * Interpolates the value at each index by the fraction. If
+ * {@link #FloatArrayEvaluator(float[])} was used to construct this object,
+ * <code>reuseArray</code> will be returned, otherwise a new <code>float[]</code>
+ * will be returned.
+ *
+ * @param fraction The fraction from the starting to the ending values
+ * @param startValue The start value.
+ * @param endValue The end value.
+ * @return A <code>float[]</code> where each element is an interpolation between
+ * the same index in startValue and endValue.
+ */
+ @Override
+ public float[] evaluate(float fraction, float[] startValue, float[] endValue) {
+ float[] array = mArray;
+ if (array == null) {
+ array = new float[startValue.length];
+ }
+
+ for (int i = 0; i < array.length; i++) {
+ float start = startValue[i];
+ float end = endValue[i];
+ array[i] = start + (fraction * (end - start));
+ }
+ return array;
+ }
+}
diff --git a/android-35/android/animation/FloatEvaluator.java b/android-35/android/animation/FloatEvaluator.java
new file mode 100644
index 0000000..ae90e37
--- /dev/null
+++ b/android-35/android/animation/FloatEvaluator.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2010 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.animation;
+
+/**
+ * This evaluator can be used to perform type interpolation between <code>float</code> values.
+ */
+public class FloatEvaluator implements TypeEvaluator<Number> {
+
+ /**
+ * This function returns the result of linearly interpolating the start and end values, with
+ * <code>fraction</code> representing the proportion between the start and end values. The
+ * calculation is a simple parametric calculation: <code>result = x0 + t * (x1 - x0)</code>,
+ * where <code>x0</code> is <code>startValue</code>, <code>x1</code> is <code>endValue</code>,
+ * and <code>t</code> is <code>fraction</code>.
+ *
+ * @param fraction The fraction from the starting to the ending values
+ * @param startValue The start value; should be of type <code>float</code> or
+ * <code>Float</code>
+ * @param endValue The end value; should be of type <code>float</code> or <code>Float</code>
+ * @return A linear interpolation between the start and end values, given the
+ * <code>fraction</code> parameter.
+ */
+ public Float evaluate(float fraction, Number startValue, Number endValue) {
+ float startFloat = startValue.floatValue();
+ return startFloat + fraction * (endValue.floatValue() - startFloat);
+ }
+}
\ No newline at end of file
diff --git a/android-35/android/animation/FloatKeyframeSet.java b/android-35/android/animation/FloatKeyframeSet.java
new file mode 100644
index 0000000..11837b5
--- /dev/null
+++ b/android-35/android/animation/FloatKeyframeSet.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2010 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.animation;
+
+import android.animation.Keyframe.FloatKeyframe;
+
+import java.util.List;
+
+/**
+ * This class holds a collection of FloatKeyframe objects and is called by ValueAnimator to calculate
+ * values between those keyframes for a given animation. The class internal to the animation
+ * package because it is an implementation detail of how Keyframes are stored and used.
+ *
+ * <p>This type-specific subclass of KeyframeSet, along with the other type-specific subclass for
+ * int, exists to speed up the getValue() method when there is no custom
+ * TypeEvaluator set for the animation, so that values can be calculated without autoboxing to the
+ * Object equivalents of these primitive types.</p>
+ */
+class FloatKeyframeSet extends KeyframeSet implements Keyframes.FloatKeyframes {
+ public FloatKeyframeSet(FloatKeyframe... keyframes) {
+ super(keyframes);
+ }
+
+ @Override
+ public Object getValue(float fraction) {
+ return getFloatValue(fraction);
+ }
+
+ @Override
+ public FloatKeyframeSet clone() {
+ final List<Keyframe> keyframes = mKeyframes;
+ final int numKeyframes = mKeyframes.size();
+ FloatKeyframe[] newKeyframes = new FloatKeyframe[numKeyframes];
+ for (int i = 0; i < numKeyframes; ++i) {
+ newKeyframes[i] = (FloatKeyframe) keyframes.get(i).clone();
+ }
+ FloatKeyframeSet newSet = new FloatKeyframeSet(newKeyframes);
+ return newSet;
+ }
+
+ @Override
+ public float getFloatValue(float fraction) {
+ if (fraction <= 0f) {
+ final FloatKeyframe prevKeyframe = (FloatKeyframe) mKeyframes.get(0);
+ final FloatKeyframe nextKeyframe = (FloatKeyframe) mKeyframes.get(1);
+ float prevValue = prevKeyframe.getFloatValue();
+ float nextValue = nextKeyframe.getFloatValue();
+ float prevFraction = prevKeyframe.getFraction();
+ float nextFraction = nextKeyframe.getFraction();
+ final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
+ if (interpolator != null) {
+ fraction = interpolator.getInterpolation(fraction);
+ }
+ float intervalFraction = (fraction - prevFraction) / (nextFraction - prevFraction);
+ return mEvaluator == null ?
+ prevValue + intervalFraction * (nextValue - prevValue) :
+ ((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).
+ floatValue();
+ } else if (fraction >= 1f) {
+ final FloatKeyframe prevKeyframe = (FloatKeyframe) mKeyframes.get(mNumKeyframes - 2);
+ final FloatKeyframe nextKeyframe = (FloatKeyframe) mKeyframes.get(mNumKeyframes - 1);
+ float prevValue = prevKeyframe.getFloatValue();
+ float nextValue = nextKeyframe.getFloatValue();
+ float prevFraction = prevKeyframe.getFraction();
+ float nextFraction = nextKeyframe.getFraction();
+ final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
+ if (interpolator != null) {
+ fraction = interpolator.getInterpolation(fraction);
+ }
+ float intervalFraction = (fraction - prevFraction) / (nextFraction - prevFraction);
+ return mEvaluator == null ?
+ prevValue + intervalFraction * (nextValue - prevValue) :
+ ((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).
+ floatValue();
+ }
+ FloatKeyframe prevKeyframe = (FloatKeyframe) mKeyframes.get(0);
+ for (int i = 1; i < mNumKeyframes; ++i) {
+ FloatKeyframe nextKeyframe = (FloatKeyframe) mKeyframes.get(i);
+ if (fraction < nextKeyframe.getFraction()) {
+ final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
+ float intervalFraction = (fraction - prevKeyframe.getFraction()) /
+ (nextKeyframe.getFraction() - prevKeyframe.getFraction());
+ float prevValue = prevKeyframe.getFloatValue();
+ float nextValue = nextKeyframe.getFloatValue();
+ // Apply interpolator on the proportional duration.
+ if (interpolator != null) {
+ intervalFraction = interpolator.getInterpolation(intervalFraction);
+ }
+ return mEvaluator == null ?
+ prevValue + intervalFraction * (nextValue - prevValue) :
+ ((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).
+ floatValue();
+ }
+ prevKeyframe = nextKeyframe;
+ }
+ // shouldn't get here
+ return ((Number)mKeyframes.get(mNumKeyframes - 1).getValue()).floatValue();
+ }
+
+ @Override
+ public Class getType() {
+ return Float.class;
+ }
+}
+
diff --git a/android-35/android/animation/IntArrayEvaluator.java b/android-35/android/animation/IntArrayEvaluator.java
new file mode 100644
index 0000000..d7f10f3
--- /dev/null
+++ b/android-35/android/animation/IntArrayEvaluator.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2013 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.animation;
+
+/**
+ * This evaluator can be used to perform type interpolation between <code>int[]</code> values.
+ * Each index into the array is treated as a separate value to interpolate. For example,
+ * evaluating <code>{100, 200}</code> and <code>{300, 400}</code> will interpolate the value at
+ * the first index between 100 and 300 and the value at the second index value between 200 and 400.
+ */
+public class IntArrayEvaluator implements TypeEvaluator<int[]> {
+
+ private int[] mArray;
+
+ /**
+ * Create an IntArrayEvaluator that does not reuse the animated value. Care must be taken
+ * when using this option because on every evaluation a new <code>int[]</code> will be
+ * allocated.
+ *
+ * @see #IntArrayEvaluator(int[])
+ */
+ public IntArrayEvaluator() {
+ }
+
+ /**
+ * Create an IntArrayEvaluator that reuses <code>reuseArray</code> for every evaluate() call.
+ * Caution must be taken to ensure that the value returned from
+ * {@link android.animation.ValueAnimator#getAnimatedValue()} is not cached, modified, or
+ * used across threads. The value will be modified on each <code>evaluate()</code> call.
+ *
+ * @param reuseArray The array to modify and return from <code>evaluate</code>.
+ */
+ public IntArrayEvaluator(int[] reuseArray) {
+ mArray = reuseArray;
+ }
+
+ /**
+ * Interpolates the value at each index by the fraction. If {@link #IntArrayEvaluator(int[])}
+ * was used to construct this object, <code>reuseArray</code> will be returned, otherwise
+ * a new <code>int[]</code> will be returned.
+ *
+ * @param fraction The fraction from the starting to the ending values
+ * @param startValue The start value.
+ * @param endValue The end value.
+ * @return An <code>int[]</code> where each element is an interpolation between
+ * the same index in startValue and endValue.
+ */
+ @Override
+ public int[] evaluate(float fraction, int[] startValue, int[] endValue) {
+ int[] array = mArray;
+ if (array == null) {
+ array = new int[startValue.length];
+ }
+ for (int i = 0; i < array.length; i++) {
+ int start = startValue[i];
+ int end = endValue[i];
+ array[i] = (int) (start + (fraction * (end - start)));
+ }
+ return array;
+ }
+}
diff --git a/android-35/android/animation/IntEvaluator.java b/android-35/android/animation/IntEvaluator.java
new file mode 100644
index 0000000..1de2ae7
--- /dev/null
+++ b/android-35/android/animation/IntEvaluator.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2010 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.animation;
+
+/**
+ * This evaluator can be used to perform type interpolation between <code>int</code> values.
+ */
+public class IntEvaluator implements TypeEvaluator<Integer> {
+
+ /**
+ * This function returns the result of linearly interpolating the start and end values, with
+ * <code>fraction</code> representing the proportion between the start and end values. The
+ * calculation is a simple parametric calculation: <code>result = x0 + t * (x1 - x0)</code>,
+ * where <code>x0</code> is <code>startValue</code>, <code>x1</code> is <code>endValue</code>,
+ * and <code>t</code> is <code>fraction</code>.
+ *
+ * @param fraction The fraction from the starting to the ending values
+ * @param startValue The start value; should be of type <code>int</code> or
+ * <code>Integer</code>
+ * @param endValue The end value; should be of type <code>int</code> or <code>Integer</code>
+ * @return A linear interpolation between the start and end values, given the
+ * <code>fraction</code> parameter.
+ */
+ public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
+ int startInt = startValue;
+ return (int)(startInt + fraction * (endValue - startInt));
+ }
+}
diff --git a/android-35/android/animation/IntKeyframeSet.java b/android-35/android/animation/IntKeyframeSet.java
new file mode 100644
index 0000000..f1e146e
--- /dev/null
+++ b/android-35/android/animation/IntKeyframeSet.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2010 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.animation;
+
+import android.animation.Keyframe.IntKeyframe;
+
+import java.util.List;
+
+/**
+ * This class holds a collection of IntKeyframe objects and is called by ValueAnimator to calculate
+ * values between those keyframes for a given animation. The class internal to the animation
+ * package because it is an implementation detail of how Keyframes are stored and used.
+ *
+ * <p>This type-specific subclass of KeyframeSet, along with the other type-specific subclass for
+ * float, exists to speed up the getValue() method when there is no custom
+ * TypeEvaluator set for the animation, so that values can be calculated without autoboxing to the
+ * Object equivalents of these primitive types.</p>
+ */
+class IntKeyframeSet extends KeyframeSet implements Keyframes.IntKeyframes {
+ public IntKeyframeSet(IntKeyframe... keyframes) {
+ super(keyframes);
+ }
+
+ @Override
+ public Object getValue(float fraction) {
+ return getIntValue(fraction);
+ }
+
+ @Override
+ public IntKeyframeSet clone() {
+ List<Keyframe> keyframes = mKeyframes;
+ int numKeyframes = mKeyframes.size();
+ IntKeyframe[] newKeyframes = new IntKeyframe[numKeyframes];
+ for (int i = 0; i < numKeyframes; ++i) {
+ newKeyframes[i] = (IntKeyframe) keyframes.get(i).clone();
+ }
+ IntKeyframeSet newSet = new IntKeyframeSet(newKeyframes);
+ return newSet;
+ }
+
+ @Override
+ public int getIntValue(float fraction) {
+ if (fraction <= 0f) {
+ final IntKeyframe prevKeyframe = (IntKeyframe) mKeyframes.get(0);
+ final IntKeyframe nextKeyframe = (IntKeyframe) mKeyframes.get(1);
+ int prevValue = prevKeyframe.getIntValue();
+ int nextValue = nextKeyframe.getIntValue();
+ float prevFraction = prevKeyframe.getFraction();
+ float nextFraction = nextKeyframe.getFraction();
+ final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
+ if (interpolator != null) {
+ fraction = interpolator.getInterpolation(fraction);
+ }
+ float intervalFraction = (fraction - prevFraction) / (nextFraction - prevFraction);
+ return mEvaluator == null ?
+ prevValue + (int)(intervalFraction * (nextValue - prevValue)) :
+ ((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).
+ intValue();
+ } else if (fraction >= 1f) {
+ final IntKeyframe prevKeyframe = (IntKeyframe) mKeyframes.get(mNumKeyframes - 2);
+ final IntKeyframe nextKeyframe = (IntKeyframe) mKeyframes.get(mNumKeyframes - 1);
+ int prevValue = prevKeyframe.getIntValue();
+ int nextValue = nextKeyframe.getIntValue();
+ float prevFraction = prevKeyframe.getFraction();
+ float nextFraction = nextKeyframe.getFraction();
+ final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
+ if (interpolator != null) {
+ fraction = interpolator.getInterpolation(fraction);
+ }
+ float intervalFraction = (fraction - prevFraction) / (nextFraction - prevFraction);
+ return mEvaluator == null ?
+ prevValue + (int)(intervalFraction * (nextValue - prevValue)) :
+ ((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).intValue();
+ }
+ IntKeyframe prevKeyframe = (IntKeyframe) mKeyframes.get(0);
+ for (int i = 1; i < mNumKeyframes; ++i) {
+ IntKeyframe nextKeyframe = (IntKeyframe) mKeyframes.get(i);
+ if (fraction < nextKeyframe.getFraction()) {
+ final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
+ float intervalFraction = (fraction - prevKeyframe.getFraction()) /
+ (nextKeyframe.getFraction() - prevKeyframe.getFraction());
+ int prevValue = prevKeyframe.getIntValue();
+ int nextValue = nextKeyframe.getIntValue();
+ // Apply interpolator on the proportional duration.
+ if (interpolator != null) {
+ intervalFraction = interpolator.getInterpolation(intervalFraction);
+ }
+ return mEvaluator == null ?
+ prevValue + (int)(intervalFraction * (nextValue - prevValue)) :
+ ((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).
+ intValue();
+ }
+ prevKeyframe = nextKeyframe;
+ }
+ // shouldn't get here
+ return ((Number)mKeyframes.get(mNumKeyframes - 1).getValue()).intValue();
+ }
+
+ @Override
+ public Class getType() {
+ return Integer.class;
+ }
+}
+
diff --git a/android-35/android/animation/Keyframe.java b/android-35/android/animation/Keyframe.java
new file mode 100644
index 0000000..bcb94d1
--- /dev/null
+++ b/android-35/android/animation/Keyframe.java
@@ -0,0 +1,388 @@
+/*
+ * Copyright (C) 2010 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.animation;
+
+/**
+ * This class holds a time/value pair for an animation. The Keyframe class is used
+ * by {@link ValueAnimator} to define the values that the animation target will have over the course
+ * of the animation. As the time proceeds from one keyframe to the other, the value of the
+ * target object will animate between the value at the previous keyframe and the value at the
+ * next keyframe. Each keyframe also holds an optional {@link TimeInterpolator}
+ * object, which defines the time interpolation over the intervalue preceding the keyframe.
+ *
+ * <p>The Keyframe class itself is abstract. The type-specific factory methods will return
+ * a subclass of Keyframe specific to the type of value being stored. This is done to improve
+ * performance when dealing with the most common cases (e.g., <code>float</code> and
+ * <code>int</code> values). Other types will fall into a more general Keyframe class that
+ * treats its values as Objects. Unless your animation requires dealing with a custom type
+ * or a data structure that needs to be animated directly (and evaluated using an implementation
+ * of {@link TypeEvaluator}), you should stick to using float and int as animations using those
+ * types have lower runtime overhead than other types.</p>
+ */
+public abstract class Keyframe implements Cloneable {
+ /**
+ * Flag to indicate whether this keyframe has a valid value. This flag is used when an
+ * animation first starts, to populate placeholder keyframes with real values derived
+ * from the target object.
+ */
+ boolean mHasValue;
+
+ /**
+ * Flag to indicate whether the value in the keyframe was read from the target object or not.
+ * If so, its value will be recalculated if target changes.
+ */
+ boolean mValueWasSetOnStart;
+
+
+ /**
+ * The time at which mValue will hold true.
+ */
+ float mFraction;
+
+ /**
+ * The type of the value in this Keyframe. This type is determined at construction time,
+ * based on the type of the <code>value</code> object passed into the constructor.
+ */
+ Class mValueType;
+
+ /**
+ * The optional time interpolator for the interval preceding this keyframe. A null interpolator
+ * (the default) results in linear interpolation over the interval.
+ */
+ private TimeInterpolator mInterpolator = null;
+
+
+
+ /**
+ * Constructs a Keyframe object with the given time and value. The time defines the
+ * time, as a proportion of an overall animation's duration, at which the value will hold true
+ * for the animation. The value for the animation between keyframes will be calculated as
+ * an interpolation between the values at those keyframes.
+ *
+ * @param fraction The time, expressed as a value between 0 and 1, representing the fraction
+ * of time elapsed of the overall animation duration.
+ * @param value The value that the object will animate to as the animation time approaches
+ * the time in this keyframe, and the value animated from as the time passes the time in
+ * this keyframe.
+ */
+ public static Keyframe ofInt(float fraction, int value) {
+ return new IntKeyframe(fraction, value);
+ }
+
+ /**
+ * Constructs a Keyframe object with the given time. The value at this time will be derived
+ * from the target object when the animation first starts (note that this implies that keyframes
+ * with no initial value must be used as part of an {@link ObjectAnimator}).
+ * The time defines the
+ * time, as a proportion of an overall animation's duration, at which the value will hold true
+ * for the animation. The value for the animation between keyframes will be calculated as
+ * an interpolation between the values at those keyframes.
+ *
+ * @param fraction The time, expressed as a value between 0 and 1, representing the fraction
+ * of time elapsed of the overall animation duration.
+ */
+ public static Keyframe ofInt(float fraction) {
+ return new IntKeyframe(fraction);
+ }
+
+ /**
+ * Constructs a Keyframe object with the given time and value. The time defines the
+ * time, as a proportion of an overall animation's duration, at which the value will hold true
+ * for the animation. The value for the animation between keyframes will be calculated as
+ * an interpolation between the values at those keyframes.
+ *
+ * @param fraction The time, expressed as a value between 0 and 1, representing the fraction
+ * of time elapsed of the overall animation duration.
+ * @param value The value that the object will animate to as the animation time approaches
+ * the time in this keyframe, and the value animated from as the time passes the time in
+ * this keyframe.
+ */
+ public static Keyframe ofFloat(float fraction, float value) {
+ return new FloatKeyframe(fraction, value);
+ }
+
+ /**
+ * Constructs a Keyframe object with the given time. The value at this time will be derived
+ * from the target object when the animation first starts (note that this implies that keyframes
+ * with no initial value must be used as part of an {@link ObjectAnimator}).
+ * The time defines the
+ * time, as a proportion of an overall animation's duration, at which the value will hold true
+ * for the animation. The value for the animation between keyframes will be calculated as
+ * an interpolation between the values at those keyframes.
+ *
+ * @param fraction The time, expressed as a value between 0 and 1, representing the fraction
+ * of time elapsed of the overall animation duration.
+ */
+ public static Keyframe ofFloat(float fraction) {
+ return new FloatKeyframe(fraction);
+ }
+
+ /**
+ * Constructs a Keyframe object with the given time and value. The time defines the
+ * time, as a proportion of an overall animation's duration, at which the value will hold true
+ * for the animation. The value for the animation between keyframes will be calculated as
+ * an interpolation between the values at those keyframes.
+ *
+ * @param fraction The time, expressed as a value between 0 and 1, representing the fraction
+ * of time elapsed of the overall animation duration.
+ * @param value The value that the object will animate to as the animation time approaches
+ * the time in this keyframe, and the value animated from as the time passes the time in
+ * this keyframe.
+ */
+ public static Keyframe ofObject(float fraction, Object value) {
+ return new ObjectKeyframe(fraction, value);
+ }
+
+ /**
+ * Constructs a Keyframe object with the given time. The value at this time will be derived
+ * from the target object when the animation first starts (note that this implies that keyframes
+ * with no initial value must be used as part of an {@link ObjectAnimator}).
+ * The time defines the
+ * time, as a proportion of an overall animation's duration, at which the value will hold true
+ * for the animation. The value for the animation between keyframes will be calculated as
+ * an interpolation between the values at those keyframes.
+ *
+ * @param fraction The time, expressed as a value between 0 and 1, representing the fraction
+ * of time elapsed of the overall animation duration.
+ */
+ public static Keyframe ofObject(float fraction) {
+ return new ObjectKeyframe(fraction, null);
+ }
+
+ /**
+ * Indicates whether this keyframe has a valid value. This method is called internally when
+ * an {@link ObjectAnimator} first starts; keyframes without values are assigned values at
+ * that time by deriving the value for the property from the target object.
+ *
+ * @return boolean Whether this object has a value assigned.
+ */
+ public boolean hasValue() {
+ return mHasValue;
+ }
+
+ /**
+ * If the Keyframe's value was acquired from the target object, this flag should be set so that,
+ * if target changes, value will be reset.
+ *
+ * @return boolean Whether this Keyframe's value was retieved from the target object or not.
+ */
+ boolean valueWasSetOnStart() {
+ return mValueWasSetOnStart;
+ }
+
+ void setValueWasSetOnStart(boolean valueWasSetOnStart) {
+ mValueWasSetOnStart = valueWasSetOnStart;
+ }
+
+ /**
+ * Gets the value for this Keyframe.
+ *
+ * @return The value for this Keyframe.
+ */
+ public abstract Object getValue();
+
+ /**
+ * Sets the value for this Keyframe.
+ *
+ * @param value value for this Keyframe.
+ */
+ public abstract void setValue(Object value);
+
+ /**
+ * Gets the time for this keyframe, as a fraction of the overall animation duration.
+ *
+ * @return The time associated with this keyframe, as a fraction of the overall animation
+ * duration. This should be a value between 0 and 1.
+ */
+ public float getFraction() {
+ return mFraction;
+ }
+
+ /**
+ * Sets the time for this keyframe, as a fraction of the overall animation duration.
+ *
+ * @param fraction time associated with this keyframe, as a fraction of the overall animation
+ * duration. This should be a value between 0 and 1.
+ */
+ public void setFraction(float fraction) {
+ mFraction = fraction;
+ }
+
+ /**
+ * Gets the optional interpolator for this Keyframe. A value of <code>null</code> indicates
+ * that there is no interpolation, which is the same as linear interpolation.
+ *
+ * @return The optional interpolator for this Keyframe.
+ */
+ public TimeInterpolator getInterpolator() {
+ return mInterpolator;
+ }
+
+ /**
+ * Sets the optional interpolator for this Keyframe. A value of <code>null</code> indicates
+ * that there is no interpolation, which is the same as linear interpolation.
+ *
+ * @return The optional interpolator for this Keyframe.
+ */
+ public void setInterpolator(TimeInterpolator interpolator) {
+ mInterpolator = interpolator;
+ }
+
+ /**
+ * Gets the type of keyframe. This information is used by ValueAnimator to determine the type of
+ * {@link TypeEvaluator} to use when calculating values between keyframes. The type is based
+ * on the type of Keyframe created.
+ *
+ * @return The type of the value stored in the Keyframe.
+ */
+ public Class getType() {
+ return mValueType;
+ }
+
+ @Override
+ public abstract Keyframe clone();
+
+ /**
+ * This internal subclass is used for all types which are not int or float.
+ */
+ static class ObjectKeyframe extends Keyframe {
+
+ /**
+ * The value of the animation at the time mFraction.
+ */
+ Object mValue;
+
+ ObjectKeyframe(float fraction, Object value) {
+ mFraction = fraction;
+ mValue = value;
+ mHasValue = (value != null);
+ mValueType = mHasValue ? value.getClass() : Object.class;
+ }
+
+ public Object getValue() {
+ return mValue;
+ }
+
+ public void setValue(Object value) {
+ mValue = value;
+ mHasValue = (value != null);
+ }
+
+ @Override
+ public ObjectKeyframe clone() {
+ ObjectKeyframe kfClone = new ObjectKeyframe(getFraction(), hasValue() ? mValue : null);
+ kfClone.mValueWasSetOnStart = mValueWasSetOnStart;
+ kfClone.setInterpolator(getInterpolator());
+ return kfClone;
+ }
+ }
+
+ /**
+ * Internal subclass used when the keyframe value is of type int.
+ */
+ static class IntKeyframe extends Keyframe {
+
+ /**
+ * The value of the animation at the time mFraction.
+ */
+ int mValue;
+
+ IntKeyframe(float fraction, int value) {
+ mFraction = fraction;
+ mValue = value;
+ mValueType = int.class;
+ mHasValue = true;
+ }
+
+ IntKeyframe(float fraction) {
+ mFraction = fraction;
+ mValueType = int.class;
+ }
+
+ public int getIntValue() {
+ return mValue;
+ }
+
+ public Object getValue() {
+ return mValue;
+ }
+
+ public void setValue(Object value) {
+ if (value != null && value.getClass() == Integer.class) {
+ mValue = ((Integer)value).intValue();
+ mHasValue = true;
+ }
+ }
+
+ @Override
+ public IntKeyframe clone() {
+ IntKeyframe kfClone = mHasValue ?
+ new IntKeyframe(getFraction(), mValue) :
+ new IntKeyframe(getFraction());
+ kfClone.setInterpolator(getInterpolator());
+ kfClone.mValueWasSetOnStart = mValueWasSetOnStart;
+ return kfClone;
+ }
+ }
+
+ /**
+ * Internal subclass used when the keyframe value is of type float.
+ */
+ static class FloatKeyframe extends Keyframe {
+ /**
+ * The value of the animation at the time mFraction.
+ */
+ float mValue;
+
+ FloatKeyframe(float fraction, float value) {
+ mFraction = fraction;
+ mValue = value;
+ mValueType = float.class;
+ mHasValue = true;
+ }
+
+ FloatKeyframe(float fraction) {
+ mFraction = fraction;
+ mValueType = float.class;
+ }
+
+ public float getFloatValue() {
+ return mValue;
+ }
+
+ public Object getValue() {
+ return mValue;
+ }
+
+ public void setValue(Object value) {
+ if (value != null && value.getClass() == Float.class) {
+ mValue = ((Float)value).floatValue();
+ mHasValue = true;
+ }
+ }
+
+ @Override
+ public FloatKeyframe clone() {
+ FloatKeyframe kfClone = mHasValue ?
+ new FloatKeyframe(getFraction(), mValue) :
+ new FloatKeyframe(getFraction());
+ kfClone.setInterpolator(getInterpolator());
+ kfClone.mValueWasSetOnStart = mValueWasSetOnStart;
+ return kfClone;
+ }
+ }
+}
diff --git a/android-35/android/animation/KeyframeSet.java b/android-35/android/animation/KeyframeSet.java
new file mode 100644
index 0000000..116d063
--- /dev/null
+++ b/android-35/android/animation/KeyframeSet.java
@@ -0,0 +1,257 @@
+/*
+ * Copyright (C) 2010 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.animation;
+
+import android.animation.Keyframe.FloatKeyframe;
+import android.animation.Keyframe.IntKeyframe;
+import android.animation.Keyframe.ObjectKeyframe;
+import android.graphics.Path;
+import android.util.Log;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * This class holds a collection of Keyframe objects and is called by ValueAnimator to calculate
+ * values between those keyframes for a given animation. The class internal to the animation
+ * package because it is an implementation detail of how Keyframes are stored and used.
+ * @hide
+ */
+public class KeyframeSet implements Keyframes {
+
+ int mNumKeyframes;
+
+ Keyframe mFirstKeyframe;
+ Keyframe mLastKeyframe;
+ TimeInterpolator mInterpolator; // only used in the 2-keyframe case
+ List<Keyframe> mKeyframes; // only used when there are not 2 keyframes
+ TypeEvaluator mEvaluator;
+
+
+ public KeyframeSet(Keyframe... keyframes) {
+ mNumKeyframes = keyframes.length;
+ // immutable list
+ mKeyframes = Arrays.asList(keyframes);
+ mFirstKeyframe = keyframes[0];
+ mLastKeyframe = keyframes[mNumKeyframes - 1];
+ mInterpolator = mLastKeyframe.getInterpolator();
+ }
+
+ public List<Keyframe> getKeyframes() {
+ return mKeyframes;
+ }
+
+ public static KeyframeSet ofInt(int... values) {
+ int numKeyframes = values.length;
+ IntKeyframe keyframes[] = new IntKeyframe[Math.max(numKeyframes,2)];
+ if (numKeyframes == 1) {
+ keyframes[0] = (IntKeyframe) Keyframe.ofInt(0f);
+ keyframes[1] = (IntKeyframe) Keyframe.ofInt(1f, values[0]);
+ } else {
+ keyframes[0] = (IntKeyframe) Keyframe.ofInt(0f, values[0]);
+ for (int i = 1; i < numKeyframes; ++i) {
+ keyframes[i] =
+ (IntKeyframe) Keyframe.ofInt((float) i / (numKeyframes - 1), values[i]);
+ }
+ }
+ return new IntKeyframeSet(keyframes);
+ }
+
+ public static KeyframeSet ofFloat(float... values) {
+ boolean badValue = false;
+ int numKeyframes = values.length;
+ FloatKeyframe keyframes[] = new FloatKeyframe[Math.max(numKeyframes,2)];
+ if (numKeyframes == 1) {
+ keyframes[0] = (FloatKeyframe) Keyframe.ofFloat(0f);
+ keyframes[1] = (FloatKeyframe) Keyframe.ofFloat(1f, values[0]);
+ if (Float.isNaN(values[0])) {
+ badValue = true;
+ }
+ } else {
+ keyframes[0] = (FloatKeyframe) Keyframe.ofFloat(0f, values[0]);
+ for (int i = 1; i < numKeyframes; ++i) {
+ keyframes[i] =
+ (FloatKeyframe) Keyframe.ofFloat((float) i / (numKeyframes - 1), values[i]);
+ if (Float.isNaN(values[i])) {
+ badValue = true;
+ }
+ }
+ }
+ if (badValue) {
+ Log.w("Animator", "Bad value (NaN) in float animator");
+ }
+ return new FloatKeyframeSet(keyframes);
+ }
+
+ public static KeyframeSet ofKeyframe(Keyframe... keyframes) {
+ // if all keyframes of same primitive type, create the appropriate KeyframeSet
+ int numKeyframes = keyframes.length;
+ boolean hasFloat = false;
+ boolean hasInt = false;
+ boolean hasOther = false;
+ for (int i = 0; i < numKeyframes; ++i) {
+ if (keyframes[i] instanceof FloatKeyframe) {
+ hasFloat = true;
+ } else if (keyframes[i] instanceof IntKeyframe) {
+ hasInt = true;
+ } else {
+ hasOther = true;
+ }
+ }
+ if (hasFloat && !hasInt && !hasOther) {
+ FloatKeyframe floatKeyframes[] = new FloatKeyframe[numKeyframes];
+ for (int i = 0; i < numKeyframes; ++i) {
+ floatKeyframes[i] = (FloatKeyframe) keyframes[i];
+ }
+ return new FloatKeyframeSet(floatKeyframes);
+ } else if (hasInt && !hasFloat && !hasOther) {
+ IntKeyframe intKeyframes[] = new IntKeyframe[numKeyframes];
+ for (int i = 0; i < numKeyframes; ++i) {
+ intKeyframes[i] = (IntKeyframe) keyframes[i];
+ }
+ return new IntKeyframeSet(intKeyframes);
+ } else {
+ return new KeyframeSet(keyframes);
+ }
+ }
+
+ public static KeyframeSet ofObject(Object... values) {
+ int numKeyframes = values.length;
+ ObjectKeyframe keyframes[] = new ObjectKeyframe[Math.max(numKeyframes,2)];
+ if (numKeyframes == 1) {
+ keyframes[0] = (ObjectKeyframe) Keyframe.ofObject(0f);
+ keyframes[1] = (ObjectKeyframe) Keyframe.ofObject(1f, values[0]);
+ } else {
+ keyframes[0] = (ObjectKeyframe) Keyframe.ofObject(0f, values[0]);
+ for (int i = 1; i < numKeyframes; ++i) {
+ keyframes[i] = (ObjectKeyframe) Keyframe.ofObject((float) i / (numKeyframes - 1), values[i]);
+ }
+ }
+ return new KeyframeSet(keyframes);
+ }
+
+ public static PathKeyframes ofPath(Path path) {
+ return new PathKeyframes(path);
+ }
+
+ public static PathKeyframes ofPath(Path path, float error) {
+ return new PathKeyframes(path, error);
+ }
+
+ /**
+ * Sets the TypeEvaluator to be used when calculating animated values. This object
+ * is required only for KeyframeSets that are not either IntKeyframeSet or FloatKeyframeSet,
+ * both of which assume their own evaluator to speed up calculations with those primitive
+ * types.
+ *
+ * @param evaluator The TypeEvaluator to be used to calculate animated values.
+ */
+ public void setEvaluator(TypeEvaluator evaluator) {
+ mEvaluator = evaluator;
+ }
+
+ @Override
+ public Class getType() {
+ return mFirstKeyframe.getType();
+ }
+
+ @Override
+ public KeyframeSet clone() {
+ List<Keyframe> keyframes = mKeyframes;
+ int numKeyframes = mKeyframes.size();
+ final Keyframe[] newKeyframes = new Keyframe[numKeyframes];
+ for (int i = 0; i < numKeyframes; ++i) {
+ newKeyframes[i] = keyframes.get(i).clone();
+ }
+ KeyframeSet newSet = new KeyframeSet(newKeyframes);
+ return newSet;
+ }
+
+ /**
+ * Gets the animated value, given the elapsed fraction of the animation (interpolated by the
+ * animation's interpolator) and the evaluator used to calculate in-between values. This
+ * function maps the input fraction to the appropriate keyframe interval and a fraction
+ * between them and returns the interpolated value. Note that the input fraction may fall
+ * outside the [0-1] bounds, if the animation's interpolator made that happen (e.g., a
+ * spring interpolation that might send the fraction past 1.0). We handle this situation by
+ * just using the two keyframes at the appropriate end when the value is outside those bounds.
+ *
+ * @param fraction The elapsed fraction of the animation
+ * @return The animated value.
+ */
+ public Object getValue(float fraction) {
+ // Special-case optimization for the common case of only two keyframes
+ if (mNumKeyframes == 2) {
+ if (mInterpolator != null) {
+ fraction = mInterpolator.getInterpolation(fraction);
+ }
+ return mEvaluator.evaluate(fraction, mFirstKeyframe.getValue(),
+ mLastKeyframe.getValue());
+ }
+ if (fraction <= 0f) {
+ final Keyframe nextKeyframe = mKeyframes.get(1);
+ final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
+ if (interpolator != null) {
+ fraction = interpolator.getInterpolation(fraction);
+ }
+ final float prevFraction = mFirstKeyframe.getFraction();
+ float intervalFraction = (fraction - prevFraction) /
+ (nextKeyframe.getFraction() - prevFraction);
+ return mEvaluator.evaluate(intervalFraction, mFirstKeyframe.getValue(),
+ nextKeyframe.getValue());
+ } else if (fraction >= 1f) {
+ final Keyframe prevKeyframe = mKeyframes.get(mNumKeyframes - 2);
+ final TimeInterpolator interpolator = mLastKeyframe.getInterpolator();
+ if (interpolator != null) {
+ fraction = interpolator.getInterpolation(fraction);
+ }
+ final float prevFraction = prevKeyframe.getFraction();
+ float intervalFraction = (fraction - prevFraction) /
+ (mLastKeyframe.getFraction() - prevFraction);
+ return mEvaluator.evaluate(intervalFraction, prevKeyframe.getValue(),
+ mLastKeyframe.getValue());
+ }
+ Keyframe prevKeyframe = mFirstKeyframe;
+ for (int i = 1; i < mNumKeyframes; ++i) {
+ Keyframe nextKeyframe = mKeyframes.get(i);
+ if (fraction < nextKeyframe.getFraction()) {
+ final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
+ final float prevFraction = prevKeyframe.getFraction();
+ float intervalFraction = (fraction - prevFraction) /
+ (nextKeyframe.getFraction() - prevFraction);
+ // Apply interpolator on the proportional duration.
+ if (interpolator != null) {
+ intervalFraction = interpolator.getInterpolation(intervalFraction);
+ }
+ return mEvaluator.evaluate(intervalFraction, prevKeyframe.getValue(),
+ nextKeyframe.getValue());
+ }
+ prevKeyframe = nextKeyframe;
+ }
+ // shouldn't reach here
+ return mLastKeyframe.getValue();
+ }
+
+ @Override
+ public String toString() {
+ String returnVal = " ";
+ for (int i = 0; i < mNumKeyframes; ++i) {
+ returnVal += mKeyframes.get(i).getValue() + " ";
+ }
+ return returnVal;
+ }
+}
diff --git a/android-35/android/animation/Keyframes.java b/android-35/android/animation/Keyframes.java
new file mode 100644
index 0000000..6666294
--- /dev/null
+++ b/android-35/android/animation/Keyframes.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2014 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.animation;
+
+import java.util.List;
+
+/**
+ * This interface abstracts a collection of Keyframe objects and is called by
+ * ValueAnimator to calculate values between those keyframes for a given animation.
+ * @hide
+ */
+public interface Keyframes extends Cloneable {
+
+ /**
+ * Sets the TypeEvaluator to be used when calculating animated values. This object
+ * is required only for Keyframes that are not either IntKeyframes or FloatKeyframes,
+ * both of which assume their own evaluator to speed up calculations with those primitive
+ * types.
+ *
+ * @param evaluator The TypeEvaluator to be used to calculate animated values.
+ */
+ void setEvaluator(TypeEvaluator evaluator);
+
+ /**
+ * @return The value type contained by the contained Keyframes.
+ */
+ Class getType();
+
+ /**
+ * Gets the animated value, given the elapsed fraction of the animation (interpolated by the
+ * animation's interpolator) and the evaluator used to calculate in-between values. This
+ * function maps the input fraction to the appropriate keyframe interval and a fraction
+ * between them and returns the interpolated value. Note that the input fraction may fall
+ * outside the [0-1] bounds, if the animation's interpolator made that happen (e.g., a
+ * spring interpolation that might send the fraction past 1.0). We handle this situation by
+ * just using the two keyframes at the appropriate end when the value is outside those bounds.
+ *
+ * @param fraction The elapsed fraction of the animation
+ * @return The animated value.
+ */
+ Object getValue(float fraction);
+
+ /**
+ * @return A list of all Keyframes contained by this. This may return null if this is
+ * not made up of Keyframes.
+ */
+ List<Keyframe> getKeyframes();
+
+ Keyframes clone();
+
+ /**
+ * A specialization of Keyframes that has integer primitive value calculation.
+ */
+ public interface IntKeyframes extends Keyframes {
+
+ /**
+ * Works like {@link #getValue(float)}, but returning a primitive.
+ * @param fraction The elapsed fraction of the animation
+ * @return The animated value.
+ */
+ int getIntValue(float fraction);
+ }
+
+ /**
+ * A specialization of Keyframes that has float primitive value calculation.
+ */
+ public interface FloatKeyframes extends Keyframes {
+
+ /**
+ * Works like {@link #getValue(float)}, but returning a primitive.
+ * @param fraction The elapsed fraction of the animation
+ * @return The animated value.
+ */
+ float getFloatValue(float fraction);
+ }
+}
diff --git a/android-35/android/animation/LayoutTransition.java b/android-35/android/animation/LayoutTransition.java
new file mode 100644
index 0000000..21f0b6b
--- /dev/null
+++ b/android-35/android/animation/LayoutTransition.java
@@ -0,0 +1,1545 @@
+/*
+ * Copyright (C) 2010 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.animation;
+
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Build;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.view.ViewTreeObserver;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This class enables automatic animations on layout changes in ViewGroup objects. To enable
+ * transitions for a layout container, create a LayoutTransition object and set it on any
+ * ViewGroup by calling {@link ViewGroup#setLayoutTransition(LayoutTransition)}. This will cause
+ * default animations to run whenever items are added to or removed from that container. To specify
+ * custom animations, use the {@link LayoutTransition#setAnimator(int, Animator)
+ * setAnimator()} method.
+ *
+ * <p>One of the core concepts of these transition animations is that there are two types of
+ * changes that cause the transition and four different animations that run because of
+ * those changes. The changes that trigger the transition are items being added to a container
+ * (referred to as an "appearing" transition) or removed from a container (also known as
+ * "disappearing"). Setting the visibility of views (between GONE and VISIBLE) will trigger
+ * the same add/remove logic. The animations that run due to those events are one that animates
+ * items being added, one that animates items being removed, and two that animate the other
+ * items in the container that change due to the add/remove occurrence. Users of
+ * the transition may want different animations for the changing items depending on whether
+ * they are changing due to an appearing or disappearing event, so there is one animation for
+ * each of these variations of the changing event. Most of the API of this class is concerned
+ * with setting up the basic properties of the animations used in these four situations,
+ * or with setting up custom animations for any or all of the four.</p>
+ *
+ * <p>By default, the DISAPPEARING animation begins immediately, as does the CHANGE_APPEARING
+ * animation. The other animations begin after a delay that is set to the default duration
+ * of the animations. This behavior facilitates a sequence of animations in transitions as
+ * follows: when an item is being added to a layout, the other children of that container will
+ * move first (thus creating space for the new item), then the appearing animation will run to
+ * animate the item being added. Conversely, when an item is removed from a container, the
+ * animation to remove it will run first, then the animations of the other children in the
+ * layout will run (closing the gap created in the layout when the item was removed). If this
+ * default choreography behavior is not desired, the {@link #setDuration(int, long)} and
+ * {@link #setStartDelay(int, long)} of any or all of the animations can be changed as
+ * appropriate. Keep in mind, however, that if you start an APPEARING animation before a
+ * DISAPPEARING animation is completed, the DISAPPEARING animation stops, and any effects from
+ * the DISAPPEARING animation are reverted. If you instead start a DISAPPEARING animation
+ * before an APPEARING animation is completed, a similar set of effects occurs for the
+ * APPEARING animation.</p>
+ *
+ * <p>The animations specified for the transition, both the defaults and any custom animations
+ * set on the transition object, are templates only. That is, these animations exist to hold the
+ * basic animation properties, such as the duration, start delay, and properties being animated.
+ * But the actual target object, as well as the start and end values for those properties, are
+ * set automatically in the process of setting up the transition each time it runs. Each of the
+ * animations is cloned from the original copy and the clone is then populated with the dynamic
+ * values of the target being animated (such as one of the items in a layout container that is
+ * moving as a result of the layout event) as well as the values that are changing (such as the
+ * position and size of that object). The actual values that are pushed to each animation
+ * depends on what properties are specified for the animation. For example, the default
+ * CHANGE_APPEARING animation animates the <code>left</code>, <code>top</code>, <code>right</code>,
+ * <code>bottom</code>, <code>scrollX</code>, and <code>scrollY</code> properties.
+ * Values for these properties are updated with the pre- and post-layout
+ * values when the transition begins. Custom animations will be similarly populated with
+ * the target and values being animated, assuming they use ObjectAnimator objects with
+ * property names that are known on the target object.</p>
+ *
+ * <p>This class, and the associated XML flag for containers, animateLayoutChanges="true",
+ * provides a simple utility meant for automating changes in straightforward situations.
+ * Using LayoutTransition at multiple levels of a nested view hierarchy may not work due to the
+ * interrelationship of the various levels of layout. Also, a container that is being scrolled
+ * at the same time as items are being added or removed is probably not a good candidate for
+ * this utility, because the before/after locations calculated by LayoutTransition
+ * may not match the actual locations when the animations finish due to the container
+ * being scrolled as the animations are running. You can work around that
+ * particular issue by disabling the 'changing' animations by setting the CHANGE_APPEARING
+ * and CHANGE_DISAPPEARING animations to null, and setting the startDelay of the
+ * other animations appropriately.</p>
+ */
+public class LayoutTransition {
+
+ /**
+ * A flag indicating the animation that runs on those items that are changing
+ * due to a new item appearing in the container.
+ */
+ public static final int CHANGE_APPEARING = 0;
+
+ /**
+ * A flag indicating the animation that runs on those items that are changing
+ * due to an item disappearing from the container.
+ */
+ public static final int CHANGE_DISAPPEARING = 1;
+
+ /**
+ * A flag indicating the animation that runs on those items that are appearing
+ * in the container.
+ */
+ public static final int APPEARING = 2;
+
+ /**
+ * A flag indicating the animation that runs on those items that are disappearing
+ * from the container.
+ */
+ public static final int DISAPPEARING = 3;
+
+ /**
+ * A flag indicating the animation that runs on those items that are changing
+ * due to a layout change not caused by items being added to or removed
+ * from the container. This transition type is not enabled by default; it can be
+ * enabled via {@link #enableTransitionType(int)}.
+ */
+ public static final int CHANGING = 4;
+
+ /**
+ * Private bit fields used to set the collection of enabled transition types for
+ * mTransitionTypes.
+ */
+ private static final int FLAG_APPEARING = 0x01;
+ private static final int FLAG_DISAPPEARING = 0x02;
+ private static final int FLAG_CHANGE_APPEARING = 0x04;
+ private static final int FLAG_CHANGE_DISAPPEARING = 0x08;
+ private static final int FLAG_CHANGING = 0x10;
+
+ /**
+ * These variables hold the animations that are currently used to run the transition effects.
+ * These animations are set to defaults, but can be changed to custom animations by
+ * calls to setAnimator().
+ */
+ private Animator mDisappearingAnim = null;
+ private Animator mAppearingAnim = null;
+ private Animator mChangingAppearingAnim = null;
+ private Animator mChangingDisappearingAnim = null;
+ private Animator mChangingAnim = null;
+
+ /**
+ * These are the default animations, defined in the constructor, that will be used
+ * unless the user specifies custom animations.
+ */
+ private static ObjectAnimator defaultChange;
+ private static ObjectAnimator defaultChangeIn;
+ private static ObjectAnimator defaultChangeOut;
+ private static ObjectAnimator defaultFadeIn;
+ private static ObjectAnimator defaultFadeOut;
+
+ /**
+ * The default duration used by all animations.
+ */
+ private static long DEFAULT_DURATION = 300;
+
+ /**
+ * The durations of the different animations
+ */
+ private long mChangingAppearingDuration = DEFAULT_DURATION;
+ private long mChangingDisappearingDuration = DEFAULT_DURATION;
+ private long mChangingDuration = DEFAULT_DURATION;
+ private long mAppearingDuration = DEFAULT_DURATION;
+ private long mDisappearingDuration = DEFAULT_DURATION;
+
+ /**
+ * The start delays of the different animations. Note that the default behavior of
+ * the appearing item is the default duration, since it should wait for the items to move
+ * before fading it. Same for the changing animation when disappearing; it waits for the item
+ * to fade out before moving the other items.
+ */
+ private long mAppearingDelay = DEFAULT_DURATION;
+ private long mDisappearingDelay = 0;
+ private long mChangingAppearingDelay = 0;
+ private long mChangingDisappearingDelay = DEFAULT_DURATION;
+ private long mChangingDelay = 0;
+
+ /**
+ * The inter-animation delays used on the changing animations
+ */
+ private long mChangingAppearingStagger = 0;
+ private long mChangingDisappearingStagger = 0;
+ private long mChangingStagger = 0;
+
+ /**
+ * Static interpolators - these are stateless and can be shared across the instances
+ */
+ private static TimeInterpolator ACCEL_DECEL_INTERPOLATOR =
+ new AccelerateDecelerateInterpolator();
+ private static TimeInterpolator DECEL_INTERPOLATOR = new DecelerateInterpolator();
+ private static TimeInterpolator sAppearingInterpolator = ACCEL_DECEL_INTERPOLATOR;
+ private static TimeInterpolator sDisappearingInterpolator = ACCEL_DECEL_INTERPOLATOR;
+ private static TimeInterpolator sChangingAppearingInterpolator = DECEL_INTERPOLATOR;
+ private static TimeInterpolator sChangingDisappearingInterpolator = DECEL_INTERPOLATOR;
+ private static TimeInterpolator sChangingInterpolator = DECEL_INTERPOLATOR;
+
+ /**
+ * The default interpolators used for the animations
+ */
+ private TimeInterpolator mAppearingInterpolator = sAppearingInterpolator;
+ private TimeInterpolator mDisappearingInterpolator = sDisappearingInterpolator;
+ private TimeInterpolator mChangingAppearingInterpolator = sChangingAppearingInterpolator;
+ private TimeInterpolator mChangingDisappearingInterpolator = sChangingDisappearingInterpolator;
+ private TimeInterpolator mChangingInterpolator = sChangingInterpolator;
+
+ /**
+ * These hashmaps are used to store the animations that are currently running as part of
+ * the transition. The reason for this is that a further layout event should cause
+ * existing animations to stop where they are prior to starting new animations. So
+ * we cache all of the current animations in this map for possible cancellation on
+ * another layout event. LinkedHashMaps are used to preserve the order in which animations
+ * are inserted, so that we process events (such as setting up start values) in the same order.
+ */
+ private final HashMap<View, Animator> pendingAnimations =
+ new HashMap<View, Animator>();
+ private final LinkedHashMap<View, Animator> currentChangingAnimations =
+ new LinkedHashMap<View, Animator>();
+ private final LinkedHashMap<View, Animator> currentAppearingAnimations =
+ new LinkedHashMap<View, Animator>();
+ private final LinkedHashMap<View, Animator> currentDisappearingAnimations =
+ new LinkedHashMap<View, Animator>();
+
+ /**
+ * This hashmap is used to track the listeners that have been added to the children of
+ * a container. When a layout change occurs, an animation is created for each View, so that
+ * the pre-layout values can be cached in that animation. Then a listener is added to the
+ * view to see whether the layout changes the bounds of that view. If so, the animation
+ * is set with the final values and then run. If not, the animation is not started. When
+ * the process of setting up and running all appropriate animations is done, we need to
+ * remove these listeners and clear out the map.
+ */
+ private final HashMap<View, View.OnLayoutChangeListener> layoutChangeListenerMap =
+ new HashMap<View, View.OnLayoutChangeListener>();
+
+ /**
+ * Used to track the current delay being assigned to successive animations as they are
+ * started. This value is incremented for each new animation, then zeroed before the next
+ * transition begins.
+ */
+ private long staggerDelay;
+
+ /**
+ * These are the types of transition animations that the LayoutTransition is reacting
+ * to. By default, appearing/disappearing and the change animations related to them are
+ * enabled (not CHANGING).
+ */
+ private int mTransitionTypes = FLAG_CHANGE_APPEARING | FLAG_CHANGE_DISAPPEARING |
+ FLAG_APPEARING | FLAG_DISAPPEARING;
+ /**
+ * The set of listeners that should be notified when APPEARING/DISAPPEARING transitions
+ * start and end.
+ */
+ private ArrayList<TransitionListener> mListeners;
+
+ /**
+ * Controls whether changing animations automatically animate the parent hierarchy as well.
+ * This behavior prevents artifacts when wrap_content layouts snap to the end state as the
+ * transition begins, causing visual glitches and clipping.
+ * Default value is true.
+ */
+ private boolean mAnimateParentHierarchy = true;
+
+
+ /**
+ * Constructs a LayoutTransition object. By default, the object will listen to layout
+ * events on any ViewGroup that it is set on and will run default animations for each
+ * type of layout event.
+ */
+ public LayoutTransition() {
+ if (defaultChangeIn == null) {
+ // "left" is just a placeholder; we'll put real properties/values in when needed
+ PropertyValuesHolder pvhLeft = PropertyValuesHolder.ofInt("left", 0, 1);
+ PropertyValuesHolder pvhTop = PropertyValuesHolder.ofInt("top", 0, 1);
+ PropertyValuesHolder pvhRight = PropertyValuesHolder.ofInt("right", 0, 1);
+ PropertyValuesHolder pvhBottom = PropertyValuesHolder.ofInt("bottom", 0, 1);
+ PropertyValuesHolder pvhScrollX = PropertyValuesHolder.ofInt("scrollX", 0, 1);
+ PropertyValuesHolder pvhScrollY = PropertyValuesHolder.ofInt("scrollY", 0, 1);
+ defaultChangeIn = ObjectAnimator.ofPropertyValuesHolder((Object)null,
+ pvhLeft, pvhTop, pvhRight, pvhBottom, pvhScrollX, pvhScrollY);
+ defaultChangeIn.setDuration(DEFAULT_DURATION);
+ defaultChangeIn.setStartDelay(mChangingAppearingDelay);
+ defaultChangeIn.setInterpolator(mChangingAppearingInterpolator);
+ defaultChangeOut = defaultChangeIn.clone();
+ defaultChangeOut.setStartDelay(mChangingDisappearingDelay);
+ defaultChangeOut.setInterpolator(mChangingDisappearingInterpolator);
+ defaultChange = defaultChangeIn.clone();
+ defaultChange.setStartDelay(mChangingDelay);
+ defaultChange.setInterpolator(mChangingInterpolator);
+
+ defaultFadeIn = ObjectAnimator.ofFloat(null, "alpha", 0f, 1f);
+ defaultFadeIn.setDuration(DEFAULT_DURATION);
+ defaultFadeIn.setStartDelay(mAppearingDelay);
+ defaultFadeIn.setInterpolator(mAppearingInterpolator);
+ defaultFadeOut = ObjectAnimator.ofFloat(null, "alpha", 1f, 0f);
+ defaultFadeOut.setDuration(DEFAULT_DURATION);
+ defaultFadeOut.setStartDelay(mDisappearingDelay);
+ defaultFadeOut.setInterpolator(mDisappearingInterpolator);
+ }
+ mChangingAppearingAnim = defaultChangeIn;
+ mChangingDisappearingAnim = defaultChangeOut;
+ mChangingAnim = defaultChange;
+ mAppearingAnim = defaultFadeIn;
+ mDisappearingAnim = defaultFadeOut;
+ }
+
+ /**
+ * Sets the duration to be used by all animations of this transition object. If you want to
+ * set the duration of just one of the animations in particular, use the
+ * {@link #setDuration(int, long)} method.
+ *
+ * @param duration The length of time, in milliseconds, that the transition animations
+ * should last.
+ */
+ public void setDuration(long duration) {
+ mChangingAppearingDuration = duration;
+ mChangingDisappearingDuration = duration;
+ mChangingDuration = duration;
+ mAppearingDuration = duration;
+ mDisappearingDuration = duration;
+ }
+
+ /**
+ * Enables the specified transitionType for this LayoutTransition object.
+ * By default, a LayoutTransition listens for changes in children being
+ * added/remove/hidden/shown in the container, and runs the animations associated with
+ * those events. That is, all transition types besides {@link #CHANGING} are enabled by default.
+ * You can also enable {@link #CHANGING} animations by calling this method with the
+ * {@link #CHANGING} transitionType.
+ *
+ * @param transitionType One of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
+ * {@link #CHANGING}, {@link #APPEARING}, or {@link #DISAPPEARING}.
+ */
+ public void enableTransitionType(int transitionType) {
+ switch (transitionType) {
+ case APPEARING:
+ mTransitionTypes |= FLAG_APPEARING;
+ break;
+ case DISAPPEARING:
+ mTransitionTypes |= FLAG_DISAPPEARING;
+ break;
+ case CHANGE_APPEARING:
+ mTransitionTypes |= FLAG_CHANGE_APPEARING;
+ break;
+ case CHANGE_DISAPPEARING:
+ mTransitionTypes |= FLAG_CHANGE_DISAPPEARING;
+ break;
+ case CHANGING:
+ mTransitionTypes |= FLAG_CHANGING;
+ break;
+ }
+ }
+
+ /**
+ * Disables the specified transitionType for this LayoutTransition object.
+ * By default, all transition types except {@link #CHANGING} are enabled.
+ *
+ * @param transitionType One of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
+ * {@link #CHANGING}, {@link #APPEARING}, or {@link #DISAPPEARING}.
+ */
+ public void disableTransitionType(int transitionType) {
+ switch (transitionType) {
+ case APPEARING:
+ mTransitionTypes &= ~FLAG_APPEARING;
+ break;
+ case DISAPPEARING:
+ mTransitionTypes &= ~FLAG_DISAPPEARING;
+ break;
+ case CHANGE_APPEARING:
+ mTransitionTypes &= ~FLAG_CHANGE_APPEARING;
+ break;
+ case CHANGE_DISAPPEARING:
+ mTransitionTypes &= ~FLAG_CHANGE_DISAPPEARING;
+ break;
+ case CHANGING:
+ mTransitionTypes &= ~FLAG_CHANGING;
+ break;
+ }
+ }
+
+ /**
+ * Returns whether the specified transitionType is enabled for this LayoutTransition object.
+ * By default, all transition types except {@link #CHANGING} are enabled.
+ *
+ * @param transitionType One of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
+ * {@link #CHANGING}, {@link #APPEARING}, or {@link #DISAPPEARING}.
+ * @return true if the specified transitionType is currently enabled, false otherwise.
+ */
+ public boolean isTransitionTypeEnabled(int transitionType) {
+ switch (transitionType) {
+ case APPEARING:
+ return (mTransitionTypes & FLAG_APPEARING) == FLAG_APPEARING;
+ case DISAPPEARING:
+ return (mTransitionTypes & FLAG_DISAPPEARING) == FLAG_DISAPPEARING;
+ case CHANGE_APPEARING:
+ return (mTransitionTypes & FLAG_CHANGE_APPEARING) == FLAG_CHANGE_APPEARING;
+ case CHANGE_DISAPPEARING:
+ return (mTransitionTypes & FLAG_CHANGE_DISAPPEARING) == FLAG_CHANGE_DISAPPEARING;
+ case CHANGING:
+ return (mTransitionTypes & FLAG_CHANGING) == FLAG_CHANGING;
+ }
+ return false;
+ }
+
+ /**
+ * Sets the start delay on one of the animation objects used by this transition. The
+ * <code>transitionType</code> parameter determines the animation whose start delay
+ * is being set.
+ *
+ * @param transitionType One of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
+ * {@link #CHANGING}, {@link #APPEARING}, or {@link #DISAPPEARING}, which determines
+ * the animation whose start delay is being set.
+ * @param delay The length of time, in milliseconds, to delay before starting the animation.
+ * @see Animator#setStartDelay(long)
+ */
+ public void setStartDelay(int transitionType, long delay) {
+ switch (transitionType) {
+ case CHANGE_APPEARING:
+ mChangingAppearingDelay = delay;
+ break;
+ case CHANGE_DISAPPEARING:
+ mChangingDisappearingDelay = delay;
+ break;
+ case CHANGING:
+ mChangingDelay = delay;
+ break;
+ case APPEARING:
+ mAppearingDelay = delay;
+ break;
+ case DISAPPEARING:
+ mDisappearingDelay = delay;
+ break;
+ }
+ }
+
+ /**
+ * Gets the start delay on one of the animation objects used by this transition. The
+ * <code>transitionType</code> parameter determines the animation whose start delay
+ * is returned.
+ *
+ * @param transitionType One of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
+ * {@link #CHANGING}, {@link #APPEARING}, or {@link #DISAPPEARING}, which determines
+ * the animation whose start delay is returned.
+ * @return long The start delay of the specified animation.
+ * @see Animator#getStartDelay()
+ */
+ public long getStartDelay(int transitionType) {
+ switch (transitionType) {
+ case CHANGE_APPEARING:
+ return mChangingAppearingDelay;
+ case CHANGE_DISAPPEARING:
+ return mChangingDisappearingDelay;
+ case CHANGING:
+ return mChangingDelay;
+ case APPEARING:
+ return mAppearingDelay;
+ case DISAPPEARING:
+ return mDisappearingDelay;
+ }
+ // shouldn't reach here
+ return 0;
+ }
+
+ /**
+ * Sets the duration on one of the animation objects used by this transition. The
+ * <code>transitionType</code> parameter determines the animation whose duration
+ * is being set.
+ *
+ * @param transitionType One of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
+ * {@link #CHANGING}, {@link #APPEARING}, or {@link #DISAPPEARING}, which determines
+ * the animation whose duration is being set.
+ * @param duration The length of time, in milliseconds, that the specified animation should run.
+ * @see Animator#setDuration(long)
+ */
+ public void setDuration(int transitionType, long duration) {
+ switch (transitionType) {
+ case CHANGE_APPEARING:
+ mChangingAppearingDuration = duration;
+ break;
+ case CHANGE_DISAPPEARING:
+ mChangingDisappearingDuration = duration;
+ break;
+ case CHANGING:
+ mChangingDuration = duration;
+ break;
+ case APPEARING:
+ mAppearingDuration = duration;
+ break;
+ case DISAPPEARING:
+ mDisappearingDuration = duration;
+ break;
+ }
+ }
+
+ /**
+ * Gets the duration on one of the animation objects used by this transition. The
+ * <code>transitionType</code> parameter determines the animation whose duration
+ * is returned.
+ *
+ * @param transitionType One of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
+ * {@link #CHANGING}, {@link #APPEARING}, or {@link #DISAPPEARING}, which determines
+ * the animation whose duration is returned.
+ * @return long The duration of the specified animation.
+ * @see Animator#getDuration()
+ */
+ public long getDuration(int transitionType) {
+ switch (transitionType) {
+ case CHANGE_APPEARING:
+ return mChangingAppearingDuration;
+ case CHANGE_DISAPPEARING:
+ return mChangingDisappearingDuration;
+ case CHANGING:
+ return mChangingDuration;
+ case APPEARING:
+ return mAppearingDuration;
+ case DISAPPEARING:
+ return mDisappearingDuration;
+ }
+ // shouldn't reach here
+ return 0;
+ }
+
+ /**
+ * Sets the length of time to delay between starting each animation during one of the
+ * change animations.
+ *
+ * @param transitionType A value of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING}, or
+ * {@link #CHANGING}.
+ * @param duration The length of time, in milliseconds, to delay before launching the next
+ * animation in the sequence.
+ */
+ public void setStagger(int transitionType, long duration) {
+ switch (transitionType) {
+ case CHANGE_APPEARING:
+ mChangingAppearingStagger = duration;
+ break;
+ case CHANGE_DISAPPEARING:
+ mChangingDisappearingStagger = duration;
+ break;
+ case CHANGING:
+ mChangingStagger = duration;
+ break;
+ // noop other cases
+ }
+ }
+
+ /**
+ * Gets the length of time to delay between starting each animation during one of the
+ * change animations.
+ *
+ * @param transitionType A value of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING}, or
+ * {@link #CHANGING}.
+ * @return long The length of time, in milliseconds, to delay before launching the next
+ * animation in the sequence.
+ */
+ public long getStagger(int transitionType) {
+ switch (transitionType) {
+ case CHANGE_APPEARING:
+ return mChangingAppearingStagger;
+ case CHANGE_DISAPPEARING:
+ return mChangingDisappearingStagger;
+ case CHANGING:
+ return mChangingStagger;
+ }
+ // shouldn't reach here
+ return 0;
+ }
+
+ /**
+ * Sets the interpolator on one of the animation objects used by this transition. The
+ * <code>transitionType</code> parameter determines the animation whose interpolator
+ * is being set.
+ *
+ * @param transitionType One of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
+ * {@link #CHANGING}, {@link #APPEARING}, or {@link #DISAPPEARING}, which determines
+ * the animation whose interpolator is being set.
+ * @param interpolator The interpolator that the specified animation should use.
+ * @see Animator#setInterpolator(TimeInterpolator)
+ */
+ public void setInterpolator(int transitionType, TimeInterpolator interpolator) {
+ switch (transitionType) {
+ case CHANGE_APPEARING:
+ mChangingAppearingInterpolator = interpolator;
+ break;
+ case CHANGE_DISAPPEARING:
+ mChangingDisappearingInterpolator = interpolator;
+ break;
+ case CHANGING:
+ mChangingInterpolator = interpolator;
+ break;
+ case APPEARING:
+ mAppearingInterpolator = interpolator;
+ break;
+ case DISAPPEARING:
+ mDisappearingInterpolator = interpolator;
+ break;
+ }
+ }
+
+ /**
+ * Gets the interpolator on one of the animation objects used by this transition. The
+ * <code>transitionType</code> parameter determines the animation whose interpolator
+ * is returned.
+ *
+ * @param transitionType One of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
+ * {@link #CHANGING}, {@link #APPEARING}, or {@link #DISAPPEARING}, which determines
+ * the animation whose interpolator is being returned.
+ * @return TimeInterpolator The interpolator that the specified animation uses.
+ * @see Animator#setInterpolator(TimeInterpolator)
+ */
+ public TimeInterpolator getInterpolator(int transitionType) {
+ switch (transitionType) {
+ case CHANGE_APPEARING:
+ return mChangingAppearingInterpolator;
+ case CHANGE_DISAPPEARING:
+ return mChangingDisappearingInterpolator;
+ case CHANGING:
+ return mChangingInterpolator;
+ case APPEARING:
+ return mAppearingInterpolator;
+ case DISAPPEARING:
+ return mDisappearingInterpolator;
+ }
+ // shouldn't reach here
+ return null;
+ }
+
+ /**
+ * Sets the animation used during one of the transition types that may run. Any
+ * Animator object can be used, but to be most useful in the context of layout
+ * transitions, the animation should either be a ObjectAnimator or a AnimatorSet
+ * of animations including PropertyAnimators. Also, these ObjectAnimator objects
+ * should be able to get and set values on their target objects automatically. For
+ * example, a ObjectAnimator that animates the property "left" is able to set and get the
+ * <code>left</code> property from the View objects being animated by the layout
+ * transition. The transition works by setting target objects and properties
+ * dynamically, according to the pre- and post-layoout values of those objects, so
+ * having animations that can handle those properties appropriately will work best
+ * for custom animation. The dynamic setting of values is only the case for the
+ * CHANGE animations; the APPEARING and DISAPPEARING animations are simply run with
+ * the values they have.
+ *
+ * <p>It is also worth noting that any and all animations (and their underlying
+ * PropertyValuesHolder objects) will have their start and end values set according
+ * to the pre- and post-layout values. So, for example, a custom animation on "alpha"
+ * as the CHANGE_APPEARING animation will inherit the real value of alpha on the target
+ * object (presumably 1) as its starting and ending value when the animation begins.
+ * Animations which need to use values at the beginning and end that may not match the
+ * values queried when the transition begins may need to use a different mechanism
+ * than a standard ObjectAnimator object.</p>
+ *
+ * @param transitionType One of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
+ * {@link #CHANGING}, {@link #APPEARING}, or {@link #DISAPPEARING}, which determines the
+ * animation whose animator is being set.
+ * @param animator The animation being assigned. A value of <code>null</code> means that no
+ * animation will be run for the specified transitionType.
+ */
+ public void setAnimator(int transitionType, Animator animator) {
+ switch (transitionType) {
+ case CHANGE_APPEARING:
+ mChangingAppearingAnim = animator;
+ break;
+ case CHANGE_DISAPPEARING:
+ mChangingDisappearingAnim = animator;
+ break;
+ case CHANGING:
+ mChangingAnim = animator;
+ break;
+ case APPEARING:
+ mAppearingAnim = animator;
+ break;
+ case DISAPPEARING:
+ mDisappearingAnim = animator;
+ break;
+ }
+ }
+
+ /**
+ * Gets the animation used during one of the transition types that may run.
+ *
+ * @param transitionType One of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
+ * {@link #CHANGING}, {@link #APPEARING}, or {@link #DISAPPEARING}, which determines
+ * the animation whose animator is being returned.
+ * @return Animator The animation being used for the given transition type.
+ * @see #setAnimator(int, Animator)
+ */
+ public Animator getAnimator(int transitionType) {
+ switch (transitionType) {
+ case CHANGE_APPEARING:
+ return mChangingAppearingAnim;
+ case CHANGE_DISAPPEARING:
+ return mChangingDisappearingAnim;
+ case CHANGING:
+ return mChangingAnim;
+ case APPEARING:
+ return mAppearingAnim;
+ case DISAPPEARING:
+ return mDisappearingAnim;
+ }
+ // shouldn't reach here
+ return null;
+ }
+
+ /**
+ * This function sets up animations on all of the views that change during layout.
+ * For every child in the parent, we create a change animation of the appropriate
+ * type (appearing, disappearing, or changing) and ask it to populate its start values from its
+ * target view. We add layout listeners to all child views and listen for changes. For
+ * those views that change, we populate the end values for those animations and start them.
+ * Animations are not run on unchanging views.
+ *
+ * @param parent The container which is undergoing a change.
+ * @param newView The view being added to or removed from the parent. May be null if the
+ * changeReason is CHANGING.
+ * @param changeReason A value of APPEARING, DISAPPEARING, or CHANGING, indicating whether the
+ * transition is occurring because an item is being added to or removed from the parent, or
+ * if it is running in response to a layout operation (that is, if the value is CHANGING).
+ */
+ private void runChangeTransition(final ViewGroup parent, View newView, final int changeReason) {
+
+ Animator baseAnimator = null;
+ Animator parentAnimator = null;
+ final long duration;
+ switch (changeReason) {
+ case APPEARING:
+ baseAnimator = mChangingAppearingAnim;
+ duration = mChangingAppearingDuration;
+ parentAnimator = defaultChangeIn;
+ break;
+ case DISAPPEARING:
+ baseAnimator = mChangingDisappearingAnim;
+ duration = mChangingDisappearingDuration;
+ parentAnimator = defaultChangeOut;
+ break;
+ case CHANGING:
+ baseAnimator = mChangingAnim;
+ duration = mChangingDuration;
+ parentAnimator = defaultChange;
+ break;
+ default:
+ // Shouldn't reach here
+ duration = 0;
+ break;
+ }
+ // If the animation is null, there's nothing to do
+ if (baseAnimator == null) {
+ return;
+ }
+
+ // reset the inter-animation delay, in case we use it later
+ staggerDelay = 0;
+
+ final ViewTreeObserver observer = parent.getViewTreeObserver();
+ if (!observer.isAlive()) {
+ // If the observer's not in a good state, skip the transition
+ return;
+ }
+ int numChildren = parent.getChildCount();
+
+ for (int i = 0; i < numChildren; ++i) {
+ final View child = parent.getChildAt(i);
+
+ // only animate the views not being added or removed
+ if (child != newView) {
+ setupChangeAnimation(parent, changeReason, baseAnimator, duration, child);
+ }
+ }
+ if (mAnimateParentHierarchy) {
+ ViewGroup tempParent = parent;
+ while (tempParent != null) {
+ ViewParent parentParent = tempParent.getParent();
+ if (parentParent instanceof ViewGroup) {
+ setupChangeAnimation((ViewGroup)parentParent, changeReason, parentAnimator,
+ duration, tempParent);
+ tempParent = (ViewGroup) parentParent;
+ } else {
+ tempParent = null;
+ }
+
+ }
+ }
+
+ // This is the cleanup step. When we get this rendering event, we know that all of
+ // the appropriate animations have been set up and run. Now we can clear out the
+ // layout listeners.
+ CleanupCallback callback = new CleanupCallback(layoutChangeListenerMap, parent);
+ observer.addOnPreDrawListener(callback);
+ parent.addOnAttachStateChangeListener(callback);
+ }
+
+ /**
+ * This flag controls whether CHANGE_APPEARING or CHANGE_DISAPPEARING animations will
+ * cause the default changing animation to be run on the parent hierarchy as well. This allows
+ * containers of transitioning views to also transition, which may be necessary in situations
+ * where the containers bounds change between the before/after states and may clip their
+ * children during the transition animations. For example, layouts with wrap_content will
+ * adjust their bounds according to the dimensions of their children.
+ *
+ * <p>The default changing transitions animate the bounds and scroll positions of the
+ * target views. These are the animations that will run on the parent hierarchy, not
+ * the custom animations that happen to be set on the transition. This allows custom
+ * behavior for the children of the transitioning container, but uses standard behavior
+ * of resizing/rescrolling on any changing parents.
+ *
+ * @param animateParentHierarchy A boolean value indicating whether the parents of
+ * transitioning views should also be animated during the transition. Default value is true.
+ */
+ public void setAnimateParentHierarchy(boolean animateParentHierarchy) {
+ mAnimateParentHierarchy = animateParentHierarchy;
+ }
+
+ /**
+ * Utility function called by runChangingTransition for both the children and the parent
+ * hierarchy.
+ */
+ private void setupChangeAnimation(final ViewGroup parent, final int changeReason,
+ Animator baseAnimator, final long duration, final View child) {
+
+ // If we already have a listener for this child, then we've already set up the
+ // changing animation we need. Multiple calls for a child may occur when several
+ // add/remove operations are run at once on a container; each one will trigger
+ // changes for the existing children in the container.
+ if (layoutChangeListenerMap.get(child) != null) {
+ return;
+ }
+
+ // Don't animate items up from size(0,0); this is likely because the objects
+ // were offscreen/invisible or otherwise measured to be infinitely small. We don't
+ // want to see them animate into their real size; just ignore animation requests
+ // on these views
+ if (child.getWidth() == 0 && child.getHeight() == 0) {
+ return;
+ }
+
+ // Make a copy of the appropriate animation
+ final Animator anim = baseAnimator.clone();
+
+ // Set the target object for the animation
+ anim.setTarget(child);
+
+ // A ObjectAnimator (or AnimatorSet of them) can extract start values from
+ // its target object
+ anim.setupStartValues();
+
+ // If there's an animation running on this view already, cancel it
+ Animator currentAnimation = pendingAnimations.get(child);
+ if (currentAnimation != null) {
+ currentAnimation.cancel();
+ pendingAnimations.remove(child);
+ }
+ // Cache the animation in case we need to cancel it later
+ pendingAnimations.put(child, anim);
+
+ // For the animations which don't get started, we have to have a means of
+ // removing them from the cache, lest we leak them and their target objects.
+ // We run an animator for the default duration+100 (an arbitrary time, but one
+ // which should far surpass the delay between setting them up here and
+ // handling layout events which start them.
+ ValueAnimator pendingAnimRemover = ValueAnimator.ofFloat(0f, 1f).
+ setDuration(duration + 100);
+ pendingAnimRemover.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ pendingAnimations.remove(child);
+ }
+ });
+ pendingAnimRemover.start();
+
+ // Add a listener to track layout changes on this view. If we don't get a callback,
+ // then there's nothing to animate.
+ final View.OnLayoutChangeListener listener = new View.OnLayoutChangeListener() {
+ public void onLayoutChange(View v, int left, int top, int right, int bottom,
+ int oldLeft, int oldTop, int oldRight, int oldBottom) {
+
+ // Tell the animation to extract end values from the changed object
+ anim.setupEndValues();
+ if (anim instanceof ValueAnimator) {
+ boolean valuesDiffer = false;
+ ValueAnimator valueAnim = (ValueAnimator)anim;
+ PropertyValuesHolder[] oldValues = valueAnim.getValues();
+ for (int i = 0; i < oldValues.length; ++i) {
+ PropertyValuesHolder pvh = oldValues[i];
+ if (pvh.mKeyframes instanceof KeyframeSet) {
+ KeyframeSet keyframeSet = (KeyframeSet) pvh.mKeyframes;
+ if (keyframeSet.mFirstKeyframe == null ||
+ keyframeSet.mLastKeyframe == null ||
+ !keyframeSet.mFirstKeyframe.getValue().equals(
+ keyframeSet.mLastKeyframe.getValue())) {
+ valuesDiffer = true;
+ }
+ } else if (!pvh.mKeyframes.getValue(0).equals(pvh.mKeyframes.getValue(1))) {
+ valuesDiffer = true;
+ }
+ }
+ if (!valuesDiffer) {
+ return;
+ }
+ }
+
+ long startDelay = 0;
+ switch (changeReason) {
+ case APPEARING:
+ startDelay = mChangingAppearingDelay + staggerDelay;
+ staggerDelay += mChangingAppearingStagger;
+ if (mChangingAppearingInterpolator != sChangingAppearingInterpolator) {
+ anim.setInterpolator(mChangingAppearingInterpolator);
+ }
+ break;
+ case DISAPPEARING:
+ startDelay = mChangingDisappearingDelay + staggerDelay;
+ staggerDelay += mChangingDisappearingStagger;
+ if (mChangingDisappearingInterpolator !=
+ sChangingDisappearingInterpolator) {
+ anim.setInterpolator(mChangingDisappearingInterpolator);
+ }
+ break;
+ case CHANGING:
+ startDelay = mChangingDelay + staggerDelay;
+ staggerDelay += mChangingStagger;
+ if (mChangingInterpolator != sChangingInterpolator) {
+ anim.setInterpolator(mChangingInterpolator);
+ }
+ break;
+ }
+ anim.setStartDelay(startDelay);
+ anim.setDuration(duration);
+
+ Animator prevAnimation = currentChangingAnimations.get(child);
+ if (prevAnimation != null) {
+ prevAnimation.cancel();
+ }
+ Animator pendingAnimation = pendingAnimations.get(child);
+ if (pendingAnimation != null) {
+ pendingAnimations.remove(child);
+ }
+ // Cache the animation in case we need to cancel it later
+ currentChangingAnimations.put(child, anim);
+
+ parent.requestTransitionStart(LayoutTransition.this);
+
+ // this only removes listeners whose views changed - must clear the
+ // other listeners later
+ child.removeOnLayoutChangeListener(this);
+ layoutChangeListenerMap.remove(child);
+ }
+ };
+ // Remove the animation from the cache when it ends
+ anim.addListener(new AnimatorListenerAdapter() {
+
+ @Override
+ public void onAnimationStart(Animator animator) {
+ if (hasListeners()) {
+ ArrayList<TransitionListener> listeners =
+ (ArrayList<TransitionListener>) mListeners.clone();
+ for (TransitionListener listener : listeners) {
+ listener.startTransition(LayoutTransition.this, parent, child,
+ changeReason == APPEARING ?
+ CHANGE_APPEARING : changeReason == DISAPPEARING ?
+ CHANGE_DISAPPEARING : CHANGING);
+ }
+ }
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animator) {
+ child.removeOnLayoutChangeListener(listener);
+ layoutChangeListenerMap.remove(child);
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ currentChangingAnimations.remove(child);
+ if (hasListeners()) {
+ ArrayList<TransitionListener> listeners =
+ (ArrayList<TransitionListener>) mListeners.clone();
+ for (TransitionListener listener : listeners) {
+ listener.endTransition(LayoutTransition.this, parent, child,
+ changeReason == APPEARING ?
+ CHANGE_APPEARING : changeReason == DISAPPEARING ?
+ CHANGE_DISAPPEARING : CHANGING);
+ }
+ }
+ }
+ });
+
+ child.addOnLayoutChangeListener(listener);
+ // cache the listener for later removal
+ layoutChangeListenerMap.put(child, listener);
+ }
+
+ /**
+ * Starts the animations set up for a CHANGING transition. We separate the setup of these
+ * animations from actually starting them, to avoid side-effects that starting the animations
+ * may have on the properties of the affected objects. After setup, we tell the affected parent
+ * that this transition should be started. The parent informs its ViewAncestor, which then
+ * starts the transition after the current layout/measurement phase, just prior to drawing
+ * the view hierarchy.
+ *
+ * @hide
+ */
+ public void startChangingAnimations() {
+ LinkedHashMap<View, Animator> currentAnimCopy =
+ (LinkedHashMap<View, Animator>) currentChangingAnimations.clone();
+ for (Animator anim : currentAnimCopy.values()) {
+ if (anim instanceof ObjectAnimator) {
+ ((ObjectAnimator) anim).setCurrentPlayTime(0);
+ }
+ anim.start();
+ }
+ }
+
+ /**
+ * Ends the animations that are set up for a CHANGING transition. This is a variant of
+ * startChangingAnimations() which is called when the window the transition is playing in
+ * is not visible. We need to make sure the animations put their targets in their end states
+ * and that the transition finishes to remove any mid-process state (such as isRunning()).
+ *
+ * @hide
+ */
+ public void endChangingAnimations() {
+ LinkedHashMap<View, Animator> currentAnimCopy =
+ (LinkedHashMap<View, Animator>) currentChangingAnimations.clone();
+ for (Animator anim : currentAnimCopy.values()) {
+ anim.start();
+ anim.end();
+ }
+ // listeners should clean up the currentChangingAnimations list, but just in case...
+ currentChangingAnimations.clear();
+ }
+
+ /**
+ * Returns true if animations are running which animate layout-related properties. This
+ * essentially means that either CHANGE_APPEARING or CHANGE_DISAPPEARING animations
+ * are running, since these animations operate on layout-related properties.
+ *
+ * @return true if CHANGE_APPEARING or CHANGE_DISAPPEARING animations are currently
+ * running.
+ */
+ public boolean isChangingLayout() {
+ return (currentChangingAnimations.size() > 0);
+ }
+
+ /**
+ * Returns true if any of the animations in this transition are currently running.
+ *
+ * @return true if any animations in the transition are running.
+ */
+ public boolean isRunning() {
+ return (currentChangingAnimations.size() > 0 || currentAppearingAnimations.size() > 0 ||
+ currentDisappearingAnimations.size() > 0);
+ }
+
+ /**
+ * Cancels the currently running transition. Note that we cancel() the changing animations
+ * but end() the visibility animations. This is because this method is currently called
+ * in the context of starting a new transition, so we want to move things from their mid-
+ * transition positions, but we want them to have their end-transition visibility.
+ *
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+ public void cancel() {
+ if (currentChangingAnimations.size() > 0) {
+ LinkedHashMap<View, Animator> currentAnimCopy =
+ (LinkedHashMap<View, Animator>) currentChangingAnimations.clone();
+ for (Animator anim : currentAnimCopy.values()) {
+ anim.cancel();
+ }
+ currentChangingAnimations.clear();
+ }
+ if (currentAppearingAnimations.size() > 0) {
+ LinkedHashMap<View, Animator> currentAnimCopy =
+ (LinkedHashMap<View, Animator>) currentAppearingAnimations.clone();
+ for (Animator anim : currentAnimCopy.values()) {
+ anim.end();
+ }
+ currentAppearingAnimations.clear();
+ }
+ if (currentDisappearingAnimations.size() > 0) {
+ LinkedHashMap<View, Animator> currentAnimCopy =
+ (LinkedHashMap<View, Animator>) currentDisappearingAnimations.clone();
+ for (Animator anim : currentAnimCopy.values()) {
+ anim.end();
+ }
+ currentDisappearingAnimations.clear();
+ }
+ }
+
+ /**
+ * Cancels the specified type of transition. Note that we cancel() the changing animations
+ * but end() the visibility animations. This is because this method is currently called
+ * in the context of starting a new transition, so we want to move things from their mid-
+ * transition positions, but we want them to have their end-transition visibility.
+ *
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+ public void cancel(int transitionType) {
+ switch (transitionType) {
+ case CHANGE_APPEARING:
+ case CHANGE_DISAPPEARING:
+ case CHANGING:
+ if (currentChangingAnimations.size() > 0) {
+ LinkedHashMap<View, Animator> currentAnimCopy =
+ (LinkedHashMap<View, Animator>) currentChangingAnimations.clone();
+ for (Animator anim : currentAnimCopy.values()) {
+ anim.cancel();
+ }
+ currentChangingAnimations.clear();
+ }
+ break;
+ case APPEARING:
+ if (currentAppearingAnimations.size() > 0) {
+ LinkedHashMap<View, Animator> currentAnimCopy =
+ (LinkedHashMap<View, Animator>) currentAppearingAnimations.clone();
+ for (Animator anim : currentAnimCopy.values()) {
+ anim.end();
+ }
+ currentAppearingAnimations.clear();
+ }
+ break;
+ case DISAPPEARING:
+ if (currentDisappearingAnimations.size() > 0) {
+ LinkedHashMap<View, Animator> currentAnimCopy =
+ (LinkedHashMap<View, Animator>) currentDisappearingAnimations.clone();
+ for (Animator anim : currentAnimCopy.values()) {
+ anim.end();
+ }
+ currentDisappearingAnimations.clear();
+ }
+ break;
+ }
+ }
+
+ /**
+ * This method runs the animation that makes an added item appear.
+ *
+ * @param parent The ViewGroup to which the View is being added.
+ * @param child The View being added to the ViewGroup.
+ */
+ private void runAppearingTransition(final ViewGroup parent, final View child) {
+ Animator currentAnimation = currentDisappearingAnimations.get(child);
+ if (currentAnimation != null) {
+ currentAnimation.cancel();
+ }
+ if (mAppearingAnim == null) {
+ if (hasListeners()) {
+ ArrayList<TransitionListener> listeners =
+ (ArrayList<TransitionListener>) mListeners.clone();
+ for (TransitionListener listener : listeners) {
+ listener.endTransition(LayoutTransition.this, parent, child, APPEARING);
+ }
+ }
+ return;
+ }
+ Animator anim = mAppearingAnim.clone();
+ anim.setTarget(child);
+ anim.setStartDelay(mAppearingDelay);
+ anim.setDuration(mAppearingDuration);
+ if (mAppearingInterpolator != sAppearingInterpolator) {
+ anim.setInterpolator(mAppearingInterpolator);
+ }
+ if (anim instanceof ObjectAnimator) {
+ ((ObjectAnimator) anim).setCurrentPlayTime(0);
+ }
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator anim) {
+ currentAppearingAnimations.remove(child);
+ if (hasListeners()) {
+ ArrayList<TransitionListener> listeners =
+ (ArrayList<TransitionListener>) mListeners.clone();
+ for (TransitionListener listener : listeners) {
+ listener.endTransition(LayoutTransition.this, parent, child, APPEARING);
+ }
+ }
+ }
+ });
+ currentAppearingAnimations.put(child, anim);
+ anim.start();
+ }
+
+ /**
+ * This method runs the animation that makes a removed item disappear.
+ *
+ * @param parent The ViewGroup from which the View is being removed.
+ * @param child The View being removed from the ViewGroup.
+ */
+ private void runDisappearingTransition(final ViewGroup parent, final View child) {
+ Animator currentAnimation = currentAppearingAnimations.get(child);
+ if (currentAnimation != null) {
+ currentAnimation.cancel();
+ }
+ if (mDisappearingAnim == null) {
+ if (hasListeners()) {
+ ArrayList<TransitionListener> listeners =
+ (ArrayList<TransitionListener>) mListeners.clone();
+ for (TransitionListener listener : listeners) {
+ listener.endTransition(LayoutTransition.this, parent, child, DISAPPEARING);
+ }
+ }
+ return;
+ }
+ Animator anim = mDisappearingAnim.clone();
+ anim.setStartDelay(mDisappearingDelay);
+ anim.setDuration(mDisappearingDuration);
+ if (mDisappearingInterpolator != sDisappearingInterpolator) {
+ anim.setInterpolator(mDisappearingInterpolator);
+ }
+ anim.setTarget(child);
+ final float preAnimAlpha = child.getAlpha();
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator anim) {
+ currentDisappearingAnimations.remove(child);
+ child.setAlpha(preAnimAlpha);
+ if (hasListeners()) {
+ ArrayList<TransitionListener> listeners =
+ (ArrayList<TransitionListener>) mListeners.clone();
+ for (TransitionListener listener : listeners) {
+ listener.endTransition(LayoutTransition.this, parent, child, DISAPPEARING);
+ }
+ }
+ }
+ });
+ if (anim instanceof ObjectAnimator) {
+ ((ObjectAnimator) anim).setCurrentPlayTime(0);
+ }
+ currentDisappearingAnimations.put(child, anim);
+ anim.start();
+ }
+
+ /**
+ * This method is called by ViewGroup when a child view is about to be added to the
+ * container. This callback starts the process of a transition; we grab the starting
+ * values, listen for changes to all of the children of the container, and start appropriate
+ * animations.
+ *
+ * @param parent The ViewGroup to which the View is being added.
+ * @param child The View being added to the ViewGroup.
+ * @param changesLayout Whether the removal will cause changes in the layout of other views
+ * in the container. INVISIBLE views becoming VISIBLE will not cause changes and thus will not
+ * affect CHANGE_APPEARING or CHANGE_DISAPPEARING animations.
+ */
+ private void addChild(ViewGroup parent, View child, boolean changesLayout) {
+ if (parent.getWindowVisibility() != View.VISIBLE) {
+ return;
+ }
+ if ((mTransitionTypes & FLAG_APPEARING) == FLAG_APPEARING) {
+ // Want disappearing animations to finish up before proceeding
+ cancel(DISAPPEARING);
+ }
+ if (changesLayout && (mTransitionTypes & FLAG_CHANGE_APPEARING) == FLAG_CHANGE_APPEARING) {
+ // Also, cancel changing animations so that we start fresh ones from current locations
+ cancel(CHANGE_APPEARING);
+ cancel(CHANGING);
+ }
+ if (hasListeners() && (mTransitionTypes & FLAG_APPEARING) == FLAG_APPEARING) {
+ ArrayList<TransitionListener> listeners =
+ (ArrayList<TransitionListener>) mListeners.clone();
+ for (TransitionListener listener : listeners) {
+ listener.startTransition(this, parent, child, APPEARING);
+ }
+ }
+ if (changesLayout && (mTransitionTypes & FLAG_CHANGE_APPEARING) == FLAG_CHANGE_APPEARING) {
+ runChangeTransition(parent, child, APPEARING);
+ }
+ if ((mTransitionTypes & FLAG_APPEARING) == FLAG_APPEARING) {
+ runAppearingTransition(parent, child);
+ }
+ }
+
+ private boolean hasListeners() {
+ return mListeners != null && mListeners.size() > 0;
+ }
+
+ /**
+ * This method is called by ViewGroup when there is a call to layout() on the container
+ * with this LayoutTransition. If the CHANGING transition is enabled and if there is no other
+ * transition currently running on the container, then this call runs a CHANGING transition.
+ * The transition does not start immediately; it just sets up the mechanism to run if any
+ * of the children of the container change their layout parameters (similar to
+ * the CHANGE_APPEARING and CHANGE_DISAPPEARING transitions).
+ *
+ * @param parent The ViewGroup whose layout() method has been called.
+ *
+ * @hide
+ */
+ public void layoutChange(ViewGroup parent) {
+ if (parent.getWindowVisibility() != View.VISIBLE) {
+ return;
+ }
+ if ((mTransitionTypes & FLAG_CHANGING) == FLAG_CHANGING && !isRunning()) {
+ // This method is called for all calls to layout() in the container, including
+ // those caused by add/remove/hide/show events, which will already have set up
+ // transition animations. Avoid setting up CHANGING animations in this case; only
+ // do so when there is not a transition already running on the container.
+ runChangeTransition(parent, null, CHANGING);
+ }
+ }
+
+ /**
+ * This method is called by ViewGroup when a child view is about to be added to the
+ * container. This callback starts the process of a transition; we grab the starting
+ * values, listen for changes to all of the children of the container, and start appropriate
+ * animations.
+ *
+ * @param parent The ViewGroup to which the View is being added.
+ * @param child The View being added to the ViewGroup.
+ */
+ public void addChild(ViewGroup parent, View child) {
+ addChild(parent, child, true);
+ }
+
+ /**
+ * @deprecated Use {@link #showChild(android.view.ViewGroup, android.view.View, int)}.
+ */
+ @Deprecated
+ public void showChild(ViewGroup parent, View child) {
+ addChild(parent, child, true);
+ }
+
+ /**
+ * This method is called by ViewGroup when a child view is about to be made visible in the
+ * container. This callback starts the process of a transition; we grab the starting
+ * values, listen for changes to all of the children of the container, and start appropriate
+ * animations.
+ *
+ * @param parent The ViewGroup in which the View is being made visible.
+ * @param child The View being made visible.
+ * @param oldVisibility The previous visibility value of the child View, either
+ * {@link View#GONE} or {@link View#INVISIBLE}.
+ */
+ public void showChild(ViewGroup parent, View child, int oldVisibility) {
+ addChild(parent, child, oldVisibility == View.GONE);
+ }
+
+ /**
+ * This method is called by ViewGroup when a child view is about to be removed from the
+ * container. This callback starts the process of a transition; we grab the starting
+ * values, listen for changes to all of the children of the container, and start appropriate
+ * animations.
+ *
+ * @param parent The ViewGroup from which the View is being removed.
+ * @param child The View being removed from the ViewGroup.
+ * @param changesLayout Whether the removal will cause changes in the layout of other views
+ * in the container. Views becoming INVISIBLE will not cause changes and thus will not
+ * affect CHANGE_APPEARING or CHANGE_DISAPPEARING animations.
+ */
+ private void removeChild(ViewGroup parent, View child, boolean changesLayout) {
+ if (parent.getWindowVisibility() != View.VISIBLE) {
+ return;
+ }
+ if ((mTransitionTypes & FLAG_DISAPPEARING) == FLAG_DISAPPEARING) {
+ // Want appearing animations to finish up before proceeding
+ cancel(APPEARING);
+ }
+ if (changesLayout &&
+ (mTransitionTypes & FLAG_CHANGE_DISAPPEARING) == FLAG_CHANGE_DISAPPEARING) {
+ // Also, cancel changing animations so that we start fresh ones from current locations
+ cancel(CHANGE_DISAPPEARING);
+ cancel(CHANGING);
+ }
+ if (hasListeners() && (mTransitionTypes & FLAG_DISAPPEARING) == FLAG_DISAPPEARING) {
+ ArrayList<TransitionListener> listeners = (ArrayList<TransitionListener>) mListeners
+ .clone();
+ for (TransitionListener listener : listeners) {
+ listener.startTransition(this, parent, child, DISAPPEARING);
+ }
+ }
+ if (changesLayout &&
+ (mTransitionTypes & FLAG_CHANGE_DISAPPEARING) == FLAG_CHANGE_DISAPPEARING) {
+ runChangeTransition(parent, child, DISAPPEARING);
+ }
+ if ((mTransitionTypes & FLAG_DISAPPEARING) == FLAG_DISAPPEARING) {
+ runDisappearingTransition(parent, child);
+ }
+ }
+
+ /**
+ * This method is called by ViewGroup when a child view is about to be removed from the
+ * container. This callback starts the process of a transition; we grab the starting
+ * values, listen for changes to all of the children of the container, and start appropriate
+ * animations.
+ *
+ * @param parent The ViewGroup from which the View is being removed.
+ * @param child The View being removed from the ViewGroup.
+ */
+ public void removeChild(ViewGroup parent, View child) {
+ removeChild(parent, child, true);
+ }
+
+ /**
+ * @deprecated Use {@link #hideChild(android.view.ViewGroup, android.view.View, int)}.
+ */
+ @Deprecated
+ public void hideChild(ViewGroup parent, View child) {
+ removeChild(parent, child, true);
+ }
+
+ /**
+ * This method is called by ViewGroup when a child view is about to be hidden in
+ * container. This callback starts the process of a transition; we grab the starting
+ * values, listen for changes to all of the children of the container, and start appropriate
+ * animations.
+ *
+ * @param parent The parent ViewGroup of the View being hidden.
+ * @param child The View being hidden.
+ * @param newVisibility The new visibility value of the child View, either
+ * {@link View#GONE} or {@link View#INVISIBLE}.
+ */
+ public void hideChild(ViewGroup parent, View child, int newVisibility) {
+ removeChild(parent, child, newVisibility == View.GONE);
+ }
+
+ /**
+ * Add a listener that will be called when the bounds of the view change due to
+ * layout processing.
+ *
+ * @param listener The listener that will be called when layout bounds change.
+ */
+ public void addTransitionListener(TransitionListener listener) {
+ if (mListeners == null) {
+ mListeners = new ArrayList<TransitionListener>();
+ }
+ mListeners.add(listener);
+ }
+
+ /**
+ * Remove a listener for layout changes.
+ *
+ * @param listener The listener for layout bounds change.
+ */
+ public void removeTransitionListener(TransitionListener listener) {
+ if (mListeners == null) {
+ return;
+ }
+ mListeners.remove(listener);
+ }
+
+ /**
+ * Gets the current list of listeners for layout changes.
+ * @return
+ */
+ public List<TransitionListener> getTransitionListeners() {
+ return mListeners;
+ }
+
+ /**
+ * This interface is used for listening to starting and ending events for transitions.
+ */
+ public interface TransitionListener {
+
+ /**
+ * This event is sent to listeners when any type of transition animation begins.
+ *
+ * @param transition The LayoutTransition sending out the event.
+ * @param container The ViewGroup on which the transition is playing.
+ * @param view The View object being affected by the transition animation.
+ * @param transitionType The type of transition that is beginning,
+ * {@link android.animation.LayoutTransition#APPEARING},
+ * {@link android.animation.LayoutTransition#DISAPPEARING},
+ * {@link android.animation.LayoutTransition#CHANGE_APPEARING}, or
+ * {@link android.animation.LayoutTransition#CHANGE_DISAPPEARING}.
+ */
+ public void startTransition(LayoutTransition transition, ViewGroup container,
+ View view, int transitionType);
+
+ /**
+ * This event is sent to listeners when any type of transition animation ends.
+ *
+ * @param transition The LayoutTransition sending out the event.
+ * @param container The ViewGroup on which the transition is playing.
+ * @param view The View object being affected by the transition animation.
+ * @param transitionType The type of transition that is ending,
+ * {@link android.animation.LayoutTransition#APPEARING},
+ * {@link android.animation.LayoutTransition#DISAPPEARING},
+ * {@link android.animation.LayoutTransition#CHANGE_APPEARING}, or
+ * {@link android.animation.LayoutTransition#CHANGE_DISAPPEARING}.
+ */
+ public void endTransition(LayoutTransition transition, ViewGroup container,
+ View view, int transitionType);
+ }
+
+ /**
+ * Utility class to clean up listeners after animations are setup. Cleanup happens
+ * when either the OnPreDrawListener method is called or when the parent is detached,
+ * whichever comes first.
+ */
+ private static final class CleanupCallback implements ViewTreeObserver.OnPreDrawListener,
+ View.OnAttachStateChangeListener {
+
+ final Map<View, View.OnLayoutChangeListener> layoutChangeListenerMap;
+ final ViewGroup parent;
+
+ CleanupCallback(Map<View, View.OnLayoutChangeListener> listenerMap, ViewGroup parent) {
+ this.layoutChangeListenerMap = listenerMap;
+ this.parent = parent;
+ }
+
+ private void cleanup() {
+ parent.getViewTreeObserver().removeOnPreDrawListener(this);
+ parent.removeOnAttachStateChangeListener(this);
+ int count = layoutChangeListenerMap.size();
+ if (count > 0) {
+ Collection<View> views = layoutChangeListenerMap.keySet();
+ for (View view : views) {
+ View.OnLayoutChangeListener listener = layoutChangeListenerMap.get(view);
+ view.removeOnLayoutChangeListener(listener);
+ }
+ layoutChangeListenerMap.clear();
+ }
+ }
+
+ @Override
+ public void onViewAttachedToWindow(View v) {
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View v) {
+ cleanup();
+ }
+
+ @Override
+ public boolean onPreDraw() {
+ cleanup();
+ return true;
+ }
+ };
+
+}
diff --git a/android-35/android/animation/ObjectAnimator.java b/android-35/android/animation/ObjectAnimator.java
new file mode 100644
index 0000000..5840f02
--- /dev/null
+++ b/android-35/android/animation/ObjectAnimator.java
@@ -0,0 +1,1004 @@
+/*
+ * Copyright (C) 2010 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.animation;
+
+import android.annotation.CallSuper;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Path;
+import android.graphics.PointF;
+import android.util.Log;
+import android.util.Property;
+import android.view.animation.AccelerateDecelerateInterpolator;
+
+/**
+ * This subclass of {@link ValueAnimator} provides support for animating properties on target objects.
+ * The constructors of this class take parameters to define the target object that will be animated
+ * as well as the name of the property that will be animated. Appropriate set/get functions
+ * are then determined internally and the animation will call these functions as necessary to
+ * animate the property.
+ *
+ * <p>Animators can be created from either code or resource files, as shown here:</p>
+ *
+ * {@sample development/samples/ApiDemos/res/anim/object_animator.xml ObjectAnimatorResources}
+ *
+ * <p>Starting from API 23, it is possible to use {@link PropertyValuesHolder} and
+ * {@link Keyframe} in resource files to create more complex animations. Using PropertyValuesHolders
+ * allows animators to animate several properties in parallel, as shown in this sample:</p>
+ *
+ * {@sample development/samples/ApiDemos/res/anim/object_animator_pvh.xml
+ * PropertyValuesHolderResources}
+ *
+ * <p>Using Keyframes allows animations to follow more complex paths from the start
+ * to the end values. Note that you can specify explicit fractional values (from 0 to 1) for
+ * each keyframe to determine when, in the overall duration, the animation should arrive at that
+ * value. Alternatively, you can leave the fractions off and the keyframes will be equally
+ * distributed within the total duration. Also, a keyframe with no value will derive its value
+ * from the target object when the animator starts, just like animators with only one
+ * value specified. In addition, an optional interpolator can be specified. The interpolator will
+ * be applied on the interval between the keyframe that the interpolator is set on and the previous
+ * keyframe. When no interpolator is supplied, the default {@link AccelerateDecelerateInterpolator}
+ * will be used. </p>
+ *
+ * {@sample development/samples/ApiDemos/res/anim/object_animator_pvh_kf_interpolated.xml KeyframeResources}
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For more information about animating with {@code ObjectAnimator}, read the
+ * <a href="{@docRoot}guide/topics/graphics/prop-animation.html#object-animator">Property
+ * Animation</a> developer guide.</p>
+ * </div>
+ *
+ * @see #setPropertyName(String)
+ *
+ */
+public final class ObjectAnimator extends ValueAnimator {
+ private static final String LOG_TAG = "ObjectAnimator";
+
+ private static final boolean DBG = false;
+
+ private Object mTarget;
+
+ private String mPropertyName;
+
+ private Property mProperty;
+
+ private boolean mAutoCancel = false;
+
+ /**
+ * Sets the name of the property that will be animated. This name is used to derive
+ * a setter function that will be called to set animated values.
+ * For example, a property name of <code>foo</code> will result
+ * in a call to the function <code>setFoo()</code> on the target object. If either
+ * <code>valueFrom</code> or <code>valueTo</code> is null, then a getter function will
+ * also be derived and called.
+ *
+ * <p>For best performance of the mechanism that calls the setter function determined by the
+ * name of the property being animated, use <code>float</code> or <code>int</code> typed values,
+ * and make the setter function for those properties have a <code>void</code> return value. This
+ * will cause the code to take an optimized path for these constrained circumstances. Other
+ * property types and return types will work, but will have more overhead in processing
+ * the requests due to normal reflection mechanisms.</p>
+ *
+ * <p>Note that the setter function derived from this property name
+ * must take the same parameter type as the
+ * <code>valueFrom</code> and <code>valueTo</code> properties, otherwise the call to
+ * the setter function will fail.</p>
+ *
+ * <p>If this ObjectAnimator has been set up to animate several properties together,
+ * using more than one PropertyValuesHolder objects, then setting the propertyName simply
+ * sets the propertyName in the first of those PropertyValuesHolder objects.</p>
+ *
+ * @param propertyName The name of the property being animated. Should not be null.
+ */
+ public void setPropertyName(@NonNull String propertyName) {
+ // mValues could be null if this is being constructed piecemeal. Just record the
+ // propertyName to be used later when setValues() is called if so.
+ if (mValues != null) {
+ PropertyValuesHolder valuesHolder = mValues[0];
+ String oldName = valuesHolder.getPropertyName();
+ valuesHolder.setPropertyName(propertyName);
+ mValuesMap.remove(oldName);
+ mValuesMap.put(propertyName, valuesHolder);
+ }
+ mPropertyName = propertyName;
+ // New property/values/target should cause re-initialization prior to starting
+ mInitialized = false;
+ }
+
+ /**
+ * Sets the property that will be animated. Property objects will take precedence over
+ * properties specified by the {@link #setPropertyName(String)} method. Animations should
+ * be set up to use one or the other, not both.
+ *
+ * @param property The property being animated. Should not be null.
+ */
+ public void setProperty(@NonNull Property property) {
+ // mValues could be null if this is being constructed piecemeal. Just record the
+ // propertyName to be used later when setValues() is called if so.
+ if (mValues != null) {
+ PropertyValuesHolder valuesHolder = mValues[0];
+ String oldName = valuesHolder.getPropertyName();
+ valuesHolder.setProperty(property);
+ mValuesMap.remove(oldName);
+ mValuesMap.put(mPropertyName, valuesHolder);
+ }
+ if (mProperty != null) {
+ mPropertyName = property.getName();
+ }
+ mProperty = property;
+ // New property/values/target should cause re-initialization prior to starting
+ mInitialized = false;
+ }
+
+ /**
+ * Gets the name of the property that will be animated. This name will be used to derive
+ * a setter function that will be called to set animated values.
+ * For example, a property name of <code>foo</code> will result
+ * in a call to the function <code>setFoo()</code> on the target object. If either
+ * <code>valueFrom</code> or <code>valueTo</code> is null, then a getter function will
+ * also be derived and called.
+ *
+ * <p>If this animator was created with a {@link Property} object instead of the
+ * string name of a property, then this method will return the {@link
+ * Property#getName() name} of that Property object instead. If this animator was
+ * created with one or more {@link PropertyValuesHolder} objects, then this method
+ * will return the {@link PropertyValuesHolder#getPropertyName() name} of that
+ * object (if there was just one) or a comma-separated list of all of the
+ * names (if there are more than one).</p>
+ */
+ @Nullable
+ public String getPropertyName() {
+ String propertyName = null;
+ if (mPropertyName != null) {
+ propertyName = mPropertyName;
+ } else if (mProperty != null) {
+ propertyName = mProperty.getName();
+ } else if (mValues != null && mValues.length > 0) {
+ for (int i = 0; i < mValues.length; ++i) {
+ if (i == 0) {
+ propertyName = "";
+ } else {
+ propertyName += ",";
+ }
+ propertyName += mValues[i].getPropertyName();
+ }
+ }
+ return propertyName;
+ }
+
+ @Override
+ String getNameForTrace() {
+ return "animator:" + getPropertyName();
+ }
+
+ /**
+ * Creates a new ObjectAnimator object. This default constructor is primarily for
+ * use internally; the other constructors which take parameters are more generally
+ * useful.
+ */
+ public ObjectAnimator() {
+ }
+
+ /**
+ * Private utility constructor that initializes the target object and name of the
+ * property being animated.
+ *
+ * @param target The object whose property is to be animated. This object should
+ * have a public method on it called <code>setName()</code>, where <code>name</code> is
+ * the value of the <code>propertyName</code> parameter.
+ * @param propertyName The name of the property being animated.
+ */
+ private ObjectAnimator(Object target, String propertyName) {
+ setTarget(target);
+ setPropertyName(propertyName);
+ }
+
+ /**
+ * Private utility constructor that initializes the target object and property being animated.
+ *
+ * @param target The object whose property is to be animated.
+ * @param property The property being animated.
+ */
+ private <T> ObjectAnimator(T target, Property<T, ?> property) {
+ setTarget(target);
+ setProperty(property);
+ }
+
+ /**
+ * Constructs and returns an ObjectAnimator that animates between int values. A single
+ * value implies that that value is the one being animated to, in which case the start value
+ * will be derived from the property being animated and the target object when {@link #start()}
+ * is called for the first time. Two values imply starting and ending values. More than two
+ * values imply a starting value, values to animate through along the way, and an ending value
+ * (these values will be distributed evenly across the duration of the animation).
+ *
+ * @param target The object whose property is to be animated. This object should
+ * have a public method on it called <code>setName()</code>, where <code>name</code> is
+ * the value of the <code>propertyName</code> parameter.
+ * @param propertyName The name of the property being animated.
+ * @param values A set of values that the animation will animate between over time.
+ * @return An ObjectAnimator object that is set up to animate between the given values.
+ */
+ public static ObjectAnimator ofInt(Object target, String propertyName, int... values) {
+ ObjectAnimator anim = new ObjectAnimator(target, propertyName);
+ anim.setIntValues(values);
+ return anim;
+ }
+
+ /**
+ * Constructs and returns an ObjectAnimator that animates coordinates along a <code>Path</code>
+ * using two properties. A <code>Path</code></> animation moves in two dimensions, animating
+ * coordinates <code>(x, y)</code> together to follow the line. In this variation, the
+ * coordinates are integers that are set to separate properties designated by
+ * <code>xPropertyName</code> and <code>yPropertyName</code>.
+ *
+ * @param target The object whose properties are to be animated. This object should
+ * have public methods on it called <code>setNameX()</code> and
+ * <code>setNameY</code>, where <code>nameX</code> and <code>nameY</code>
+ * are the value of <code>xPropertyName</code> and <code>yPropertyName</code>
+ * parameters, respectively.
+ * @param xPropertyName The name of the property for the x coordinate being animated.
+ * @param yPropertyName The name of the property for the y coordinate being animated.
+ * @param path The <code>Path</code> to animate values along.
+ * @return An ObjectAnimator object that is set up to animate along <code>path</code>.
+ */
+ public static ObjectAnimator ofInt(Object target, String xPropertyName, String yPropertyName,
+ Path path) {
+ PathKeyframes keyframes = KeyframeSet.ofPath(path);
+ PropertyValuesHolder x = PropertyValuesHolder.ofKeyframes(xPropertyName,
+ keyframes.createXIntKeyframes());
+ PropertyValuesHolder y = PropertyValuesHolder.ofKeyframes(yPropertyName,
+ keyframes.createYIntKeyframes());
+ return ofPropertyValuesHolder(target, x, y);
+ }
+
+ /**
+ * Constructs and returns an ObjectAnimator that animates between int values. A single
+ * value implies that that value is the one being animated to, in which case the start value
+ * will be derived from the property being animated and the target object when {@link #start()}
+ * is called for the first time. Two values imply starting and ending values. More than two
+ * values imply a starting value, values to animate through along the way, and an ending value
+ * (these values will be distributed evenly across the duration of the animation).
+ *
+ * @param target The object whose property is to be animated.
+ * @param property The property being animated.
+ * @param values A set of values that the animation will animate between over time.
+ * @return An ObjectAnimator object that is set up to animate between the given values.
+ */
+ public static <T> ObjectAnimator ofInt(T target, Property<T, Integer> property, int... values) {
+ ObjectAnimator anim = new ObjectAnimator(target, property);
+ anim.setIntValues(values);
+ return anim;
+ }
+
+ /**
+ * Constructs and returns an ObjectAnimator that animates coordinates along a <code>Path</code>
+ * using two properties. A <code>Path</code></> animation moves in two dimensions, animating
+ * coordinates <code>(x, y)</code> together to follow the line. In this variation, the
+ * coordinates are integers that are set to separate properties, <code>xProperty</code> and
+ * <code>yProperty</code>.
+ *
+ * @param target The object whose properties are to be animated.
+ * @param xProperty The property for the x coordinate being animated.
+ * @param yProperty The property for the y coordinate being animated.
+ * @param path The <code>Path</code> to animate values along.
+ * @return An ObjectAnimator object that is set up to animate along <code>path</code>.
+ */
+ public static <T> ObjectAnimator ofInt(T target, Property<T, Integer> xProperty,
+ Property<T, Integer> yProperty, Path path) {
+ PathKeyframes keyframes = KeyframeSet.ofPath(path);
+ PropertyValuesHolder x = PropertyValuesHolder.ofKeyframes(xProperty,
+ keyframes.createXIntKeyframes());
+ PropertyValuesHolder y = PropertyValuesHolder.ofKeyframes(yProperty,
+ keyframes.createYIntKeyframes());
+ return ofPropertyValuesHolder(target, x, y);
+ }
+
+ /**
+ * Constructs and returns an ObjectAnimator that animates over int values for a multiple
+ * parameters setter. Only public methods that take only int parameters are supported.
+ * Each <code>int[]</code> contains a complete set of parameters to the setter method.
+ * At least two <code>int[]</code> values must be provided, a start and end. More than two
+ * values imply a starting value, values to animate through along the way, and an ending
+ * value (these values will be distributed evenly across the duration of the animation).
+ *
+ * @param target The object whose property is to be animated. This object may
+ * have a public method on it called <code>setName()</code>, where <code>name</code> is
+ * the value of the <code>propertyName</code> parameter. <code>propertyName</code> may also
+ * be the case-sensitive complete name of the public setter method.
+ * @param propertyName The name of the property being animated or the name of the setter method.
+ * @param values A set of values that the animation will animate between over time.
+ * @return An ObjectAnimator object that is set up to animate between the given values.
+ */
+ public static ObjectAnimator ofMultiInt(Object target, String propertyName, int[][] values) {
+ PropertyValuesHolder pvh = PropertyValuesHolder.ofMultiInt(propertyName, values);
+ return ofPropertyValuesHolder(target, pvh);
+ }
+
+ /**
+ * Constructs and returns an ObjectAnimator that animates the target using a multi-int setter
+ * along the given <code>Path</code>. A <code>Path</code></> animation moves in two dimensions,
+ * animating coordinates <code>(x, y)</code> together to follow the line. In this variation, the
+ * coordinates are integer x and y coordinates used in the first and second parameter of the
+ * setter, respectively.
+ *
+ * @param target The object whose property is to be animated. This object may
+ * have a public method on it called <code>setName()</code>, where <code>name</code> is
+ * the value of the <code>propertyName</code> parameter. <code>propertyName</code> may also
+ * be the case-sensitive complete name of the public setter method.
+ * @param propertyName The name of the property being animated or the name of the setter method.
+ * @param path The <code>Path</code> to animate values along.
+ * @return An ObjectAnimator object that is set up to animate along <code>path</code>.
+ */
+ public static ObjectAnimator ofMultiInt(Object target, String propertyName, Path path) {
+ PropertyValuesHolder pvh = PropertyValuesHolder.ofMultiInt(propertyName, path);
+ return ofPropertyValuesHolder(target, pvh);
+ }
+
+ /**
+ * Constructs and returns an ObjectAnimator that animates over values for a multiple int
+ * parameters setter. Only public methods that take only int parameters are supported.
+ * <p>At least two values must be provided, a start and end. More than two
+ * values imply a starting value, values to animate through along the way, and an ending
+ * value (these values will be distributed evenly across the duration of the animation).</p>
+ *
+ * @param target The object whose property is to be animated. This object may
+ * have a public method on it called <code>setName()</code>, where <code>name</code> is
+ * the value of the <code>propertyName</code> parameter. <code>propertyName</code> may also
+ * be the case-sensitive complete name of the public setter method.
+ * @param propertyName The name of the property being animated or the name of the setter method.
+ * @param converter Converts T objects into int parameters for the multi-value setter.
+ * @param evaluator A TypeEvaluator that will be called on each animation frame to
+ * provide the necessary interpolation between the Object values to derive the animated
+ * value.
+ * @param values A set of values that the animation will animate between over time.
+ * @return An ObjectAnimator object that is set up to animate between the given values.
+ */
+ @SafeVarargs
+ public static <T> ObjectAnimator ofMultiInt(Object target, String propertyName,
+ TypeConverter<T, int[]> converter, TypeEvaluator<T> evaluator, T... values) {
+ PropertyValuesHolder pvh = PropertyValuesHolder.ofMultiInt(propertyName, converter,
+ evaluator, values);
+ return ObjectAnimator.ofPropertyValuesHolder(target, pvh);
+ }
+
+ /**
+ * Constructs and returns an ObjectAnimator that animates between color values. A single
+ * value implies that that value is the one being animated to, in which case the start value
+ * will be derived from the property being animated and the target object when {@link #start()}
+ * is called for the first time. Two values imply starting and ending values. More than two
+ * values imply a starting value, values to animate through along the way, and an ending value
+ * (these values will be distributed evenly across the duration of the animation).
+ *
+ * @param target The object whose property is to be animated. This object should
+ * have a public method on it called <code>setName()</code>, where <code>name</code> is
+ * the value of the <code>propertyName</code> parameter.
+ * @param propertyName The name of the property being animated.
+ * @param values A set of values that the animation will animate between over time.
+ * @return An ObjectAnimator object that is set up to animate between the given values.
+ */
+ public static ObjectAnimator ofArgb(Object target, String propertyName, int... values) {
+ ObjectAnimator animator = ofInt(target, propertyName, values);
+ animator.setEvaluator(ArgbEvaluator.getInstance());
+ return animator;
+ }
+
+ /**
+ * Constructs and returns an ObjectAnimator that animates between color values. A single
+ * value implies that that value is the one being animated to, in which case the start value
+ * will be derived from the property being animated and the target object when {@link #start()}
+ * is called for the first time. Two values imply starting and ending values. More than two
+ * values imply a starting value, values to animate through along the way, and an ending value
+ * (these values will be distributed evenly across the duration of the animation).
+ *
+ * @param target The object whose property is to be animated.
+ * @param property The property being animated.
+ * @param values A set of values that the animation will animate between over time.
+ * @return An ObjectAnimator object that is set up to animate between the given values.
+ */
+ public static <T> ObjectAnimator ofArgb(T target, Property<T, Integer> property,
+ int... values) {
+ ObjectAnimator animator = ofInt(target, property, values);
+ animator.setEvaluator(ArgbEvaluator.getInstance());
+ return animator;
+ }
+
+ /**
+ * Constructs and returns an ObjectAnimator that animates between float values. A single
+ * value implies that that value is the one being animated to, in which case the start value
+ * will be derived from the property being animated and the target object when {@link #start()}
+ * is called for the first time. Two values imply starting and ending values. More than two
+ * values imply a starting value, values to animate through along the way, and an ending value
+ * (these values will be distributed evenly across the duration of the animation).
+ *
+ * @param target The object whose property is to be animated. This object should
+ * have a public method on it called <code>setName()</code>, where <code>name</code> is
+ * the value of the <code>propertyName</code> parameter.
+ * @param propertyName The name of the property being animated.
+ * @param values A set of values that the animation will animate between over time.
+ * @return An ObjectAnimator object that is set up to animate between the given values.
+ */
+ public static ObjectAnimator ofFloat(Object target, String propertyName, float... values) {
+ ObjectAnimator anim = new ObjectAnimator(target, propertyName);
+ anim.setFloatValues(values);
+ return anim;
+ }
+
+ /**
+ * Constructs and returns an ObjectAnimator that animates coordinates along a <code>Path</code>
+ * using two properties. A <code>Path</code></> animation moves in two dimensions, animating
+ * coordinates <code>(x, y)</code> together to follow the line. In this variation, the
+ * coordinates are floats that are set to separate properties designated by
+ * <code>xPropertyName</code> and <code>yPropertyName</code>.
+ *
+ * @param target The object whose properties are to be animated. This object should
+ * have public methods on it called <code>setNameX()</code> and
+ * <code>setNameY</code>, where <code>nameX</code> and <code>nameY</code>
+ * are the value of the <code>xPropertyName</code> and <code>yPropertyName</code>
+ * parameters, respectively.
+ * @param xPropertyName The name of the property for the x coordinate being animated.
+ * @param yPropertyName The name of the property for the y coordinate being animated.
+ * @param path The <code>Path</code> to animate values along.
+ * @return An ObjectAnimator object that is set up to animate along <code>path</code>.
+ */
+ public static ObjectAnimator ofFloat(Object target, String xPropertyName, String yPropertyName,
+ Path path) {
+ PathKeyframes keyframes = KeyframeSet.ofPath(path);
+ PropertyValuesHolder x = PropertyValuesHolder.ofKeyframes(xPropertyName,
+ keyframes.createXFloatKeyframes());
+ PropertyValuesHolder y = PropertyValuesHolder.ofKeyframes(yPropertyName,
+ keyframes.createYFloatKeyframes());
+ return ofPropertyValuesHolder(target, x, y);
+ }
+
+ /**
+ * Constructs and returns an ObjectAnimator that animates between float values. A single
+ * value implies that that value is the one being animated to, in which case the start value
+ * will be derived from the property being animated and the target object when {@link #start()}
+ * is called for the first time. Two values imply starting and ending values. More than two
+ * values imply a starting value, values to animate through along the way, and an ending value
+ * (these values will be distributed evenly across the duration of the animation).
+ *
+ * @param target The object whose property is to be animated.
+ * @param property The property being animated.
+ * @param values A set of values that the animation will animate between over time.
+ * @return An ObjectAnimator object that is set up to animate between the given values.
+ */
+ public static <T> ObjectAnimator ofFloat(T target, Property<T, Float> property,
+ float... values) {
+ ObjectAnimator anim = new ObjectAnimator(target, property);
+ anim.setFloatValues(values);
+ return anim;
+ }
+
+ /**
+ * Constructs and returns an ObjectAnimator that animates coordinates along a <code>Path</code>
+ * using two properties. A <code>Path</code></> animation moves in two dimensions, animating
+ * coordinates <code>(x, y)</code> together to follow the line. In this variation, the
+ * coordinates are floats that are set to separate properties, <code>xProperty</code> and
+ * <code>yProperty</code>.
+ *
+ * @param target The object whose properties are to be animated.
+ * @param xProperty The property for the x coordinate being animated.
+ * @param yProperty The property for the y coordinate being animated.
+ * @param path The <code>Path</code> to animate values along.
+ * @return An ObjectAnimator object that is set up to animate along <code>path</code>.
+ */
+ public static <T> ObjectAnimator ofFloat(T target, Property<T, Float> xProperty,
+ Property<T, Float> yProperty, Path path) {
+ PathKeyframes keyframes = KeyframeSet.ofPath(path);
+ PropertyValuesHolder x = PropertyValuesHolder.ofKeyframes(xProperty,
+ keyframes.createXFloatKeyframes());
+ PropertyValuesHolder y = PropertyValuesHolder.ofKeyframes(yProperty,
+ keyframes.createYFloatKeyframes());
+ return ofPropertyValuesHolder(target, x, y);
+ }
+
+ /**
+ * Constructs and returns an ObjectAnimator that animates over float values for a multiple
+ * parameters setter. Only public methods that take only float parameters are supported.
+ * Each <code>float[]</code> contains a complete set of parameters to the setter method.
+ * At least two <code>float[]</code> values must be provided, a start and end. More than two
+ * values imply a starting value, values to animate through along the way, and an ending
+ * value (these values will be distributed evenly across the duration of the animation).
+ *
+ * @param target The object whose property is to be animated. This object may
+ * have a public method on it called <code>setName()</code>, where <code>name</code> is
+ * the value of the <code>propertyName</code> parameter. <code>propertyName</code> may also
+ * be the case-sensitive complete name of the public setter method.
+ * @param propertyName The name of the property being animated or the name of the setter method.
+ * @param values A set of values that the animation will animate between over time.
+ * @return An ObjectAnimator object that is set up to animate between the given values.
+ */
+ public static ObjectAnimator ofMultiFloat(Object target, String propertyName,
+ float[][] values) {
+ PropertyValuesHolder pvh = PropertyValuesHolder.ofMultiFloat(propertyName, values);
+ return ofPropertyValuesHolder(target, pvh);
+ }
+
+ /**
+ * Constructs and returns an ObjectAnimator that animates the target using a multi-float setter
+ * along the given <code>Path</code>. A <code>Path</code></> animation moves in two dimensions,
+ * animating coordinates <code>(x, y)</code> together to follow the line. In this variation, the
+ * coordinates are float x and y coordinates used in the first and second parameter of the
+ * setter, respectively.
+ *
+ * @param target The object whose property is to be animated. This object may
+ * have a public method on it called <code>setName()</code>, where <code>name</code> is
+ * the value of the <code>propertyName</code> parameter. <code>propertyName</code> may also
+ * be the case-sensitive complete name of the public setter method.
+ * @param propertyName The name of the property being animated or the name of the setter method.
+ * @param path The <code>Path</code> to animate values along.
+ * @return An ObjectAnimator object that is set up to animate along <code>path</code>.
+ */
+ public static ObjectAnimator ofMultiFloat(Object target, String propertyName, Path path) {
+ PropertyValuesHolder pvh = PropertyValuesHolder.ofMultiFloat(propertyName, path);
+ return ofPropertyValuesHolder(target, pvh);
+ }
+
+ /**
+ * Constructs and returns an ObjectAnimator that animates over values for a multiple float
+ * parameters setter. Only public methods that take only float parameters are supported.
+ * <p>At least two values must be provided, a start and end. More than two
+ * values imply a starting value, values to animate through along the way, and an ending
+ * value (these values will be distributed evenly across the duration of the animation).</p>
+ *
+ * @param target The object whose property is to be animated. This object may
+ * have a public method on it called <code>setName()</code>, where <code>name</code> is
+ * the value of the <code>propertyName</code> parameter. <code>propertyName</code> may also
+ * be the case-sensitive complete name of the public setter method.
+ * @param propertyName The name of the property being animated or the name of the setter method.
+ * @param converter Converts T objects into float parameters for the multi-value setter.
+ * @param evaluator A TypeEvaluator that will be called on each animation frame to
+ * provide the necessary interpolation between the Object values to derive the animated
+ * value.
+ * @param values A set of values that the animation will animate between over time.
+ * @return An ObjectAnimator object that is set up to animate between the given values.
+ */
+ @SafeVarargs
+ public static <T> ObjectAnimator ofMultiFloat(Object target, String propertyName,
+ TypeConverter<T, float[]> converter, TypeEvaluator<T> evaluator, T... values) {
+ PropertyValuesHolder pvh = PropertyValuesHolder.ofMultiFloat(propertyName, converter,
+ evaluator, values);
+ return ObjectAnimator.ofPropertyValuesHolder(target, pvh);
+ }
+
+ /**
+ * Constructs and returns an ObjectAnimator that animates between Object values. A single
+ * value implies that that value is the one being animated to, in which case the start value
+ * will be derived from the property being animated and the target object when {@link #start()}
+ * is called for the first time. Two values imply starting and ending values. More than two
+ * values imply a starting value, values to animate through along the way, and an ending value
+ * (these values will be distributed evenly across the duration of the animation).
+ *
+ * <p><strong>Note:</strong> The values are stored as references to the original
+ * objects, which means that changes to those objects after this method is called will
+ * affect the values on the animator. If the objects will be mutated externally after
+ * this method is called, callers should pass a copy of those objects instead.
+ *
+ * @param target The object whose property is to be animated. This object should
+ * have a public method on it called <code>setName()</code>, where <code>name</code> is
+ * the value of the <code>propertyName</code> parameter.
+ * @param propertyName The name of the property being animated.
+ * @param evaluator A TypeEvaluator that will be called on each animation frame to
+ * provide the necessary interpolation between the Object values to derive the animated
+ * value.
+ * @param values A set of values that the animation will animate between over time.
+ * @return An ObjectAnimator object that is set up to animate between the given values.
+ */
+ public static ObjectAnimator ofObject(Object target, String propertyName,
+ TypeEvaluator evaluator, Object... values) {
+ ObjectAnimator anim = new ObjectAnimator(target, propertyName);
+ anim.setObjectValues(values);
+ anim.setEvaluator(evaluator);
+ return anim;
+ }
+
+ /**
+ * Constructs and returns an ObjectAnimator that animates a property along a <code>Path</code>.
+ * A <code>Path</code></> animation moves in two dimensions, animating coordinates
+ * <code>(x, y)</code> together to follow the line. This variant animates the coordinates
+ * in a <code>PointF</code> to follow the <code>Path</code>. If the <code>Property</code>
+ * associated with <code>propertyName</code> uses a type other than <code>PointF</code>,
+ * <code>converter</code> can be used to change from <code>PointF</code> to the type
+ * associated with the <code>Property</code>.
+ *
+ * @param target The object whose property is to be animated. This object should
+ * have a public method on it called <code>setName()</code>, where <code>name</code> is
+ * the value of the <code>propertyName</code> parameter.
+ * @param propertyName The name of the property being animated.
+ * @param converter Converts a PointF to the type associated with the setter. May be
+ * null if conversion is unnecessary.
+ * @param path The <code>Path</code> to animate values along.
+ * @return An ObjectAnimator object that is set up to animate along <code>path</code>.
+ */
+ @NonNull
+ public static ObjectAnimator ofObject(Object target, String propertyName,
+ @Nullable TypeConverter<PointF, ?> converter, Path path) {
+ PropertyValuesHolder pvh = PropertyValuesHolder.ofObject(propertyName, converter, path);
+ return ofPropertyValuesHolder(target, pvh);
+ }
+
+ /**
+ * Constructs and returns an ObjectAnimator that animates between Object values. A single
+ * value implies that that value is the one being animated to, in which case the start value
+ * will be derived from the property being animated and the target object when {@link #start()}
+ * is called for the first time. Two values imply starting and ending values. More than two
+ * values imply a starting value, values to animate through along the way, and an ending value
+ * (these values will be distributed evenly across the duration of the animation).
+ *
+ * <p><strong>Note:</strong> The values are stored as references to the original
+ * objects, which means that changes to those objects after this method is called will
+ * affect the values on the animator. If the objects will be mutated externally after
+ * this method is called, callers should pass a copy of those objects instead.
+ *
+ * @param target The object whose property is to be animated.
+ * @param property The property being animated.
+ * @param evaluator A TypeEvaluator that will be called on each animation frame to
+ * provide the necessary interpolation between the Object values to derive the animated
+ * value.
+ * @param values A set of values that the animation will animate between over time.
+ * @return An ObjectAnimator object that is set up to animate between the given values.
+ */
+ @NonNull
+ @SafeVarargs
+ public static <T, V> ObjectAnimator ofObject(T target, Property<T, V> property,
+ TypeEvaluator<V> evaluator, V... values) {
+ ObjectAnimator anim = new ObjectAnimator(target, property);
+ anim.setObjectValues(values);
+ anim.setEvaluator(evaluator);
+ return anim;
+ }
+
+ /**
+ * Constructs and returns an ObjectAnimator that animates between Object values. A single
+ * value implies that that value is the one being animated to, in which case the start value
+ * will be derived from the property being animated and the target object when {@link #start()}
+ * is called for the first time. Two values imply starting and ending values. More than two
+ * values imply a starting value, values to animate through along the way, and an ending value
+ * (these values will be distributed evenly across the duration of the animation).
+ * This variant supplies a <code>TypeConverter</code> to convert from the animated values to the
+ * type of the property. If only one value is supplied, the <code>TypeConverter</code> must be a
+ * {@link android.animation.BidirectionalTypeConverter} to retrieve the current value.
+ *
+ * <p><strong>Note:</strong> The values are stored as references to the original
+ * objects, which means that changes to those objects after this method is called will
+ * affect the values on the animator. If the objects will be mutated externally after
+ * this method is called, callers should pass a copy of those objects instead.
+ *
+ * @param target The object whose property is to be animated.
+ * @param property The property being animated.
+ * @param converter Converts the animated object to the Property type.
+ * @param evaluator A TypeEvaluator that will be called on each animation frame to
+ * provide the necessary interpolation between the Object values to derive the animated
+ * value.
+ * @param values A set of values that the animation will animate between over time.
+ * @return An ObjectAnimator object that is set up to animate between the given values.
+ */
+ @NonNull
+ @SafeVarargs
+ public static <T, V, P> ObjectAnimator ofObject(T target, Property<T, P> property,
+ TypeConverter<V, P> converter, TypeEvaluator<V> evaluator, V... values) {
+ PropertyValuesHolder pvh = PropertyValuesHolder.ofObject(property, converter, evaluator,
+ values);
+ return ofPropertyValuesHolder(target, pvh);
+ }
+
+ /**
+ * Constructs and returns an ObjectAnimator that animates a property along a <code>Path</code>.
+ * A <code>Path</code></> animation moves in two dimensions, animating coordinates
+ * <code>(x, y)</code> together to follow the line. This variant animates the coordinates
+ * in a <code>PointF</code> to follow the <code>Path</code>. If <code>property</code>
+ * uses a type other than <code>PointF</code>, <code>converter</code> can be used to change
+ * from <code>PointF</code> to the type associated with the <code>Property</code>.
+ *
+ * <p>The PointF passed to <code>converter</code> or <code>property</code>, if
+ * <code>converter</code> is <code>null</code>, is reused on each animation frame and should
+ * not be stored by the setter or TypeConverter.</p>
+ *
+ * @param target The object whose property is to be animated.
+ * @param property The property being animated. Should not be null.
+ * @param converter Converts a PointF to the type associated with the setter. May be
+ * null if conversion is unnecessary.
+ * @param path The <code>Path</code> to animate values along.
+ * @return An ObjectAnimator object that is set up to animate along <code>path</code>.
+ */
+ @NonNull
+ public static <T, V> ObjectAnimator ofObject(T target, @NonNull Property<T, V> property,
+ @Nullable TypeConverter<PointF, V> converter, Path path) {
+ PropertyValuesHolder pvh = PropertyValuesHolder.ofObject(property, converter, path);
+ return ofPropertyValuesHolder(target, pvh);
+ }
+
+ /**
+ * Constructs and returns an ObjectAnimator that animates between the sets of values specified
+ * in <code>PropertyValueHolder</code> objects. This variant should be used when animating
+ * several properties at once with the same ObjectAnimator, since PropertyValuesHolder allows
+ * you to associate a set of animation values with a property name.
+ *
+ * @param target The object whose property is to be animated. Depending on how the
+ * PropertyValuesObjects were constructed, the target object should either have the {@link
+ * android.util.Property} objects used to construct the PropertyValuesHolder objects or (if the
+ * PropertyValuesHOlder objects were created with property names) the target object should have
+ * public methods on it called <code>setName()</code>, where <code>name</code> is the name of
+ * the property passed in as the <code>propertyName</code> parameter for each of the
+ * PropertyValuesHolder objects.
+ * @param values A set of PropertyValuesHolder objects whose values will be animated between
+ * over time.
+ * @return An ObjectAnimator object that is set up to animate between the given values.
+ */
+ @NonNull
+ public static ObjectAnimator ofPropertyValuesHolder(Object target,
+ PropertyValuesHolder... values) {
+ ObjectAnimator anim = new ObjectAnimator();
+ anim.setTarget(target);
+ anim.setValues(values);
+ return anim;
+ }
+
+ @Override
+ public void setIntValues(int... values) {
+ if (mValues == null || mValues.length == 0) {
+ // No values yet - this animator is being constructed piecemeal. Init the values with
+ // whatever the current propertyName is
+ if (mProperty != null) {
+ setValues(PropertyValuesHolder.ofInt(mProperty, values));
+ } else {
+ setValues(PropertyValuesHolder.ofInt(mPropertyName, values));
+ }
+ } else {
+ super.setIntValues(values);
+ }
+ }
+
+ @Override
+ public void setFloatValues(float... values) {
+ if (mValues == null || mValues.length == 0) {
+ // No values yet - this animator is being constructed piecemeal. Init the values with
+ // whatever the current propertyName is
+ if (mProperty != null) {
+ setValues(PropertyValuesHolder.ofFloat(mProperty, values));
+ } else {
+ setValues(PropertyValuesHolder.ofFloat(mPropertyName, values));
+ }
+ } else {
+ super.setFloatValues(values);
+ }
+ }
+
+ @Override
+ public void setObjectValues(Object... values) {
+ if (mValues == null || mValues.length == 0) {
+ // No values yet - this animator is being constructed piecemeal. Init the values with
+ // whatever the current propertyName is
+ if (mProperty != null) {
+ setValues(PropertyValuesHolder.ofObject(mProperty, (TypeEvaluator) null, values));
+ } else {
+ setValues(PropertyValuesHolder.ofObject(mPropertyName,
+ (TypeEvaluator) null, values));
+ }
+ } else {
+ super.setObjectValues(values);
+ }
+ }
+
+ /**
+ * autoCancel controls whether an ObjectAnimator will be canceled automatically
+ * when any other ObjectAnimator with the same target and properties is started.
+ * Setting this flag may make it easier to run different animators on the same target
+ * object without having to keep track of whether there are conflicting animators that
+ * need to be manually canceled. Canceling animators must have the same exact set of
+ * target properties, in the same order.
+ *
+ * @param cancel Whether future ObjectAnimators with the same target and properties
+ * as this ObjectAnimator will cause this ObjectAnimator to be canceled.
+ */
+ public void setAutoCancel(boolean cancel) {
+ mAutoCancel = cancel;
+ }
+
+ private boolean hasSameTargetAndProperties(@Nullable Animator anim) {
+ if (anim instanceof ObjectAnimator) {
+ PropertyValuesHolder[] theirValues = ((ObjectAnimator) anim).getValues();
+ if (((ObjectAnimator) anim).getTarget() == getTarget() &&
+ mValues.length == theirValues.length) {
+ for (int i = 0; i < mValues.length; ++i) {
+ PropertyValuesHolder pvhMine = mValues[i];
+ PropertyValuesHolder pvhTheirs = theirValues[i];
+ if (pvhMine.getPropertyName() == null ||
+ !pvhMine.getPropertyName().equals(pvhTheirs.getPropertyName())) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void start() {
+ AnimationHandler.getInstance().autoCancelBasedOn(this);
+ if (DBG) {
+ Log.d(LOG_TAG, "Anim target, duration: " + getTarget() + ", " + getDuration());
+ for (int i = 0; i < mValues.length; ++i) {
+ PropertyValuesHolder pvh = mValues[i];
+ Log.d(LOG_TAG, " Values[" + i + "]: " +
+ pvh.getPropertyName() + ", " + pvh.mKeyframes.getValue(0) + ", " +
+ pvh.mKeyframes.getValue(1));
+ }
+ }
+ super.start();
+ }
+
+ boolean shouldAutoCancel(AnimationHandler.AnimationFrameCallback anim) {
+ if (anim == null) {
+ return false;
+ }
+
+ if (anim instanceof ObjectAnimator) {
+ ObjectAnimator objAnim = (ObjectAnimator) anim;
+ if (objAnim.mAutoCancel && hasSameTargetAndProperties(objAnim)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * This function is called immediately before processing the first animation
+ * frame of an animation. If there is a nonzero <code>startDelay</code>, the
+ * function is called after that delay ends.
+ * It takes care of the final initialization steps for the
+ * animation. This includes setting mEvaluator, if the user has not yet
+ * set it up, and the setter/getter methods, if the user did not supply
+ * them.
+ *
+ * <p>Overriders of this method should call the superclass method to cause
+ * internal mechanisms to be set up correctly.</p>
+ */
+ @CallSuper
+ @Override
+ void initAnimation() {
+ if (!mInitialized) {
+ // mValueType may change due to setter/getter setup; do this before calling super.init(),
+ // which uses mValueType to set up the default type evaluator.
+ final Object target = getTarget();
+ if (target != null) {
+ final int numValues = mValues.length;
+ for (int i = 0; i < numValues; ++i) {
+ mValues[i].setupSetterAndGetter(target);
+ }
+ }
+ super.initAnimation();
+ }
+ }
+
+ /**
+ * Sets the length of the animation. The default duration is 300 milliseconds.
+ *
+ * @param duration The length of the animation, in milliseconds.
+ * @return ObjectAnimator The object called with setDuration(). This return
+ * value makes it easier to compose statements together that construct and then set the
+ * duration, as in
+ * <code>ObjectAnimator.ofInt(target, propertyName, 0, 10).setDuration(500).start()</code>.
+ */
+ @Override
+ @NonNull
+ public ObjectAnimator setDuration(long duration) {
+ super.setDuration(duration);
+ return this;
+ }
+
+
+ /**
+ * The target object whose property will be animated by this animation
+ *
+ * @return The object being animated
+ */
+ @Nullable
+ public Object getTarget() {
+ return mTarget;
+ }
+
+ @Override
+ public void setTarget(@Nullable Object target) {
+ final Object oldTarget = getTarget();
+ if (oldTarget != target) {
+ if (isStarted()) {
+ cancel();
+ }
+ mTarget = target;
+ // New target should cause re-initialization prior to starting
+ mInitialized = false;
+ }
+ }
+
+ @Override
+ public void setupStartValues() {
+ initAnimation();
+
+ final Object target = getTarget();
+ if (target != null) {
+ final int numValues = mValues.length;
+ for (int i = 0; i < numValues; ++i) {
+ mValues[i].setupStartValue(target);
+ }
+ }
+ }
+
+ @Override
+ public void setupEndValues() {
+ initAnimation();
+
+ final Object target = getTarget();
+ if (target != null) {
+ final int numValues = mValues.length;
+ for (int i = 0; i < numValues; ++i) {
+ mValues[i].setupEndValue(target);
+ }
+ }
+ }
+
+ /**
+ * This method is called with the elapsed fraction of the animation during every
+ * animation frame. This function turns the elapsed fraction into an interpolated fraction
+ * and then into an animated value (from the evaluator. The function is called mostly during
+ * animation updates, but it is also called when the <code>end()</code>
+ * function is called, to set the final value on the property.
+ *
+ * <p>Overrides of this method must call the superclass to perform the calculation
+ * of the animated value.</p>
+ *
+ * @param fraction The elapsed fraction of the animation.
+ */
+ @CallSuper
+ @Override
+ void animateValue(float fraction) {
+ final Object target = getTarget();
+ super.animateValue(fraction);
+ int numValues = mValues.length;
+ for (int i = 0; i < numValues; ++i) {
+ mValues[i].setAnimatedValue(target);
+ }
+ }
+
+ @Override
+ boolean isInitialized() {
+ return mInitialized;
+ }
+
+ @Override
+ public ObjectAnimator clone() {
+ final ObjectAnimator anim = (ObjectAnimator) super.clone();
+ return anim;
+ }
+
+ @Override
+ @NonNull
+ public String toString() {
+ String returnVal = "ObjectAnimator@" + Integer.toHexString(hashCode()) + ", target " +
+ getTarget();
+ if (mValues != null) {
+ for (int i = 0; i < mValues.length; ++i) {
+ returnVal += "\n " + mValues[i].toString();
+ }
+ }
+ return returnVal;
+ }
+}
diff --git a/android-35/android/animation/PathKeyframes.java b/android-35/android/animation/PathKeyframes.java
new file mode 100644
index 0000000..b362904
--- /dev/null
+++ b/android-35/android/animation/PathKeyframes.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2014 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.animation;
+
+import android.graphics.Path;
+import android.graphics.PointF;
+
+import java.util.ArrayList;
+
+/**
+ * PathKeyframes relies on approximating the Path as a series of line segments.
+ * The line segments are recursively divided until there is less than 1/2 pixel error
+ * between the lines and the curve. Each point of the line segment is converted
+ * to a Keyframe and a linear interpolation between Keyframes creates a good approximation
+ * of the curve.
+ * <p>
+ * PathKeyframes is optimized to reduce the number of objects created when there are
+ * many keyframes for a curve.
+ * </p>
+ * <p>
+ * Typically, the returned type is a PointF, but the individual components can be extracted
+ * as either an IntKeyframes or FloatKeyframes.
+ * </p>
+ * @hide
+ */
+public class PathKeyframes implements Keyframes {
+ private static final int FRACTION_OFFSET = 0;
+ private static final int X_OFFSET = 1;
+ private static final int Y_OFFSET = 2;
+ private static final int NUM_COMPONENTS = 3;
+ private static final ArrayList<Keyframe> EMPTY_KEYFRAMES = new ArrayList<Keyframe>();
+
+ private PointF mTempPointF = new PointF();
+ private float[] mKeyframeData;
+
+ public PathKeyframes(Path path) {
+ this(path, 0.5f);
+ }
+
+ public PathKeyframes(Path path, float error) {
+ if (path == null || path.isEmpty()) {
+ throw new IllegalArgumentException("The path must not be null or empty");
+ }
+ mKeyframeData = path.approximate(error);
+ }
+
+ @Override
+ public ArrayList<Keyframe> getKeyframes() {
+ return EMPTY_KEYFRAMES;
+ }
+
+ @Override
+ public Object getValue(float fraction) {
+ int numPoints = mKeyframeData.length / 3;
+ if (fraction < 0) {
+ return interpolateInRange(fraction, 0, 1);
+ } else if (fraction > 1) {
+ return interpolateInRange(fraction, numPoints - 2, numPoints - 1);
+ } else if (fraction == 0) {
+ return pointForIndex(0);
+ } else if (fraction == 1) {
+ return pointForIndex(numPoints - 1);
+ } else {
+ // Binary search for the correct section
+ int low = 0;
+ int high = numPoints - 1;
+
+ while (low <= high) {
+ int mid = (low + high) / 2;
+ float midFraction = mKeyframeData[(mid * NUM_COMPONENTS) + FRACTION_OFFSET];
+
+ if (fraction < midFraction) {
+ high = mid - 1;
+ } else if (fraction > midFraction) {
+ low = mid + 1;
+ } else {
+ return pointForIndex(mid);
+ }
+ }
+
+ // now high is below the fraction and low is above the fraction
+ return interpolateInRange(fraction, high, low);
+ }
+ }
+
+ private PointF interpolateInRange(float fraction, int startIndex, int endIndex) {
+ int startBase = (startIndex * NUM_COMPONENTS);
+ int endBase = (endIndex * NUM_COMPONENTS);
+
+ float startFraction = mKeyframeData[startBase + FRACTION_OFFSET];
+ float endFraction = mKeyframeData[endBase + FRACTION_OFFSET];
+
+ float intervalFraction = (fraction - startFraction)/(endFraction - startFraction);
+
+ float startX = mKeyframeData[startBase + X_OFFSET];
+ float endX = mKeyframeData[endBase + X_OFFSET];
+ float startY = mKeyframeData[startBase + Y_OFFSET];
+ float endY = mKeyframeData[endBase + Y_OFFSET];
+
+ float x = interpolate(intervalFraction, startX, endX);
+ float y = interpolate(intervalFraction, startY, endY);
+
+ mTempPointF.set(x, y);
+ return mTempPointF;
+ }
+
+ @Override
+ public void setEvaluator(TypeEvaluator evaluator) {
+ }
+
+ @Override
+ public Class getType() {
+ return PointF.class;
+ }
+
+ @Override
+ public Keyframes clone() {
+ Keyframes clone = null;
+ try {
+ clone = (Keyframes) super.clone();
+ } catch (CloneNotSupportedException e) {}
+ return clone;
+ }
+
+ private PointF pointForIndex(int index) {
+ int base = (index * NUM_COMPONENTS);
+ int xOffset = base + X_OFFSET;
+ int yOffset = base + Y_OFFSET;
+ mTempPointF.set(mKeyframeData[xOffset], mKeyframeData[yOffset]);
+ return mTempPointF;
+ }
+
+ private static float interpolate(float fraction, float startValue, float endValue) {
+ float diff = endValue - startValue;
+ return startValue + (diff * fraction);
+ }
+
+ /**
+ * Returns a FloatKeyframes for the X component of the Path.
+ * @return a FloatKeyframes for the X component of the Path.
+ */
+ public FloatKeyframes createXFloatKeyframes() {
+ return new FloatKeyframesBase() {
+ @Override
+ public float getFloatValue(float fraction) {
+ PointF pointF = (PointF) PathKeyframes.this.getValue(fraction);
+ return pointF.x;
+ }
+ };
+ }
+
+ /**
+ * Returns a FloatKeyframes for the Y component of the Path.
+ * @return a FloatKeyframes for the Y component of the Path.
+ */
+ public FloatKeyframes createYFloatKeyframes() {
+ return new FloatKeyframesBase() {
+ @Override
+ public float getFloatValue(float fraction) {
+ PointF pointF = (PointF) PathKeyframes.this.getValue(fraction);
+ return pointF.y;
+ }
+ };
+ }
+
+ /**
+ * Returns an IntKeyframes for the X component of the Path.
+ * @return an IntKeyframes for the X component of the Path.
+ */
+ public IntKeyframes createXIntKeyframes() {
+ return new IntKeyframesBase() {
+ @Override
+ public int getIntValue(float fraction) {
+ PointF pointF = (PointF) PathKeyframes.this.getValue(fraction);
+ return Math.round(pointF.x);
+ }
+ };
+ }
+
+ /**
+ * Returns an IntKeyframeSet for the Y component of the Path.
+ * @return an IntKeyframeSet for the Y component of the Path.
+ */
+ public IntKeyframes createYIntKeyframes() {
+ return new IntKeyframesBase() {
+ @Override
+ public int getIntValue(float fraction) {
+ PointF pointF = (PointF) PathKeyframes.this.getValue(fraction);
+ return Math.round(pointF.y);
+ }
+ };
+ }
+
+ private abstract static class SimpleKeyframes implements Keyframes {
+ @Override
+ public void setEvaluator(TypeEvaluator evaluator) {
+ }
+
+ @Override
+ public ArrayList<Keyframe> getKeyframes() {
+ return EMPTY_KEYFRAMES;
+ }
+
+ @Override
+ public Keyframes clone() {
+ Keyframes clone = null;
+ try {
+ clone = (Keyframes) super.clone();
+ } catch (CloneNotSupportedException e) {}
+ return clone;
+ }
+ }
+
+ abstract static class IntKeyframesBase extends SimpleKeyframes implements IntKeyframes {
+ @Override
+ public Class getType() {
+ return Integer.class;
+ }
+
+ @Override
+ public Object getValue(float fraction) {
+ return getIntValue(fraction);
+ }
+ }
+
+ abstract static class FloatKeyframesBase extends SimpleKeyframes
+ implements FloatKeyframes {
+ @Override
+ public Class getType() {
+ return Float.class;
+ }
+
+ @Override
+ public Object getValue(float fraction) {
+ return getFloatValue(fraction);
+ }
+ }
+}
diff --git a/android-35/android/animation/PointFEvaluator.java b/android-35/android/animation/PointFEvaluator.java
new file mode 100644
index 0000000..91d501f
--- /dev/null
+++ b/android-35/android/animation/PointFEvaluator.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2013 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.animation;
+
+import android.graphics.PointF;
+
+/**
+ * This evaluator can be used to perform type interpolation between <code>PointF</code> values.
+ */
+public class PointFEvaluator implements TypeEvaluator<PointF> {
+
+ /**
+ * When null, a new PointF is returned on every evaluate call. When non-null,
+ * mPoint will be modified and returned on every evaluate.
+ */
+ private PointF mPoint;
+
+ /**
+ * Construct a PointFEvaluator that returns a new PointF on every evaluate call.
+ * To avoid creating an object for each evaluate call,
+ * {@link PointFEvaluator#PointFEvaluator(android.graphics.PointF)} should be used
+ * whenever possible.
+ */
+ public PointFEvaluator() {
+ }
+
+ /**
+ * Constructs a PointFEvaluator that modifies and returns <code>reuse</code>
+ * in {@link #evaluate(float, android.graphics.PointF, android.graphics.PointF)} calls.
+ * The value returned from
+ * {@link #evaluate(float, android.graphics.PointF, android.graphics.PointF)} should
+ * not be cached because it will change over time as the object is reused on each
+ * call.
+ *
+ * @param reuse A PointF to be modified and returned by evaluate.
+ */
+ public PointFEvaluator(PointF reuse) {
+ mPoint = reuse;
+ }
+
+ /**
+ * This function returns the result of linearly interpolating the start and
+ * end PointF values, with <code>fraction</code> representing the proportion
+ * between the start and end values. The calculation is a simple parametric
+ * calculation on each of the separate components in the PointF objects
+ * (x, y).
+ *
+ * <p>If {@link #PointFEvaluator(android.graphics.PointF)} was used to construct
+ * this PointFEvaluator, the object returned will be the <code>reuse</code>
+ * passed into the constructor.</p>
+ *
+ * @param fraction The fraction from the starting to the ending values
+ * @param startValue The start PointF
+ * @param endValue The end PointF
+ * @return A linear interpolation between the start and end values, given the
+ * <code>fraction</code> parameter.
+ */
+ @Override
+ public PointF evaluate(float fraction, PointF startValue, PointF endValue) {
+ float x = startValue.x + (fraction * (endValue.x - startValue.x));
+ float y = startValue.y + (fraction * (endValue.y - startValue.y));
+
+ if (mPoint != null) {
+ mPoint.set(x, y);
+ return mPoint;
+ } else {
+ return new PointF(x, y);
+ }
+ }
+}
diff --git a/android-35/android/animation/PropertyValuesHolder.java b/android-35/android/animation/PropertyValuesHolder.java
new file mode 100644
index 0000000..76806a2
--- /dev/null
+++ b/android-35/android/animation/PropertyValuesHolder.java
@@ -0,0 +1,1729 @@
+/*
+ * Copyright (C) 2010 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.animation;
+
+import android.graphics.Path;
+import android.graphics.PointF;
+import android.util.FloatProperty;
+import android.util.IntProperty;
+import android.util.Log;
+import android.util.PathParser;
+import android.util.Property;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * This class holds information about a property and the values that that property
+ * should take on during an animation. PropertyValuesHolder objects can be used to create
+ * animations with ValueAnimator or ObjectAnimator that operate on several different properties
+ * in parallel.
+ */
+public class PropertyValuesHolder implements Cloneable {
+
+ /**
+ * The name of the property associated with the values. This need not be a real property,
+ * unless this object is being used with ObjectAnimator. But this is the name by which
+ * aniamted values are looked up with getAnimatedValue(String) in ValueAnimator.
+ */
+ String mPropertyName;
+
+ /**
+ * @hide
+ */
+ protected Property mProperty;
+
+ /**
+ * The setter function, if needed. ObjectAnimator hands off this functionality to
+ * PropertyValuesHolder, since it holds all of the per-property information. This
+ * property is automatically
+ * derived when the animation starts in setupSetterAndGetter() if using ObjectAnimator.
+ */
+ Method mSetter = null;
+
+ /**
+ * The getter function, if needed. ObjectAnimator hands off this functionality to
+ * PropertyValuesHolder, since it holds all of the per-property information. This
+ * property is automatically
+ * derived when the animation starts in setupSetterAndGetter() if using ObjectAnimator.
+ * The getter is only derived and used if one of the values is null.
+ */
+ private Method mGetter = null;
+
+ /**
+ * The type of values supplied. This information is used both in deriving the setter/getter
+ * functions and in deriving the type of TypeEvaluator.
+ */
+ Class mValueType;
+
+ /**
+ * The set of keyframes (time/value pairs) that define this animation.
+ */
+ Keyframes mKeyframes = null;
+
+
+ // type evaluators for the primitive types handled by this implementation
+ private static final TypeEvaluator sIntEvaluator = new IntEvaluator();
+ private static final TypeEvaluator sFloatEvaluator = new FloatEvaluator();
+
+ // We try several different types when searching for appropriate setter/getter functions.
+ // The caller may have supplied values in a type that does not match the setter/getter
+ // functions (such as the integers 0 and 1 to represent floating point values for alpha).
+ // Also, the use of generics in constructors means that we end up with the Object versions
+ // of primitive types (Float vs. float). But most likely, the setter/getter functions
+ // will take primitive types instead.
+ // So we supply an ordered array of other types to try before giving up.
+ private static Class[] FLOAT_VARIANTS = {float.class, Float.class, double.class, int.class,
+ Double.class, Integer.class};
+ private static Class[] INTEGER_VARIANTS = {int.class, Integer.class, float.class, double.class,
+ Float.class, Double.class};
+ private static Class[] DOUBLE_VARIANTS = {double.class, Double.class, float.class, int.class,
+ Float.class, Integer.class};
+
+ // These maps hold all property entries for a particular class. This map
+ // is used to speed up property/setter/getter lookups for a given class/property
+ // combination. No need to use reflection on the combination more than once.
+ private static final HashMap<Class, HashMap<String, Method>> sSetterPropertyMap =
+ new HashMap<Class, HashMap<String, Method>>();
+ private static final HashMap<Class, HashMap<String, Method>> sGetterPropertyMap =
+ new HashMap<Class, HashMap<String, Method>>();
+
+ // Used to pass single value to varargs parameter in setter invocation
+ final Object[] mTmpValueArray = new Object[1];
+
+ /**
+ * The type evaluator used to calculate the animated values. This evaluator is determined
+ * automatically based on the type of the start/end objects passed into the constructor,
+ * but the system only knows about the primitive types int and float. Any other
+ * type will need to set the evaluator to a custom evaluator for that type.
+ */
+ private TypeEvaluator mEvaluator;
+
+ /**
+ * The value most recently calculated by calculateValue(). This is set during
+ * that function and might be retrieved later either by ValueAnimator.animatedValue() or
+ * by the property-setting logic in ObjectAnimator.animatedValue().
+ */
+ private Object mAnimatedValue;
+
+ /**
+ * Converts from the source Object type to the setter Object type.
+ */
+ private TypeConverter mConverter;
+
+ /**
+ * Internal utility constructor, used by the factory methods to set the property name.
+ * @param propertyName The name of the property for this holder.
+ */
+ private PropertyValuesHolder(String propertyName) {
+ mPropertyName = propertyName;
+ }
+
+ /**
+ * Internal utility constructor, used by the factory methods to set the property.
+ * @param property The property for this holder.
+ */
+ private PropertyValuesHolder(Property property) {
+ mProperty = property;
+ if (property != null) {
+ mPropertyName = property.getName();
+ }
+ }
+
+ /**
+ * Constructs and returns a PropertyValuesHolder with a given property name and
+ * set of int values.
+ * @param propertyName The name of the property being animated.
+ * @param values The values that the named property will animate between.
+ * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
+ */
+ public static PropertyValuesHolder ofInt(String propertyName, int... values) {
+ return new IntPropertyValuesHolder(propertyName, values);
+ }
+
+ /**
+ * Constructs and returns a PropertyValuesHolder with a given property and
+ * set of int values.
+ * @param property The property being animated. Should not be null.
+ * @param values The values that the property will animate between.
+ * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
+ */
+ public static PropertyValuesHolder ofInt(Property<?, Integer> property, int... values) {
+ return new IntPropertyValuesHolder(property, values);
+ }
+
+ /**
+ * Constructs and returns a PropertyValuesHolder with a given property name and
+ * set of <code>int[]</code> values. At least two <code>int[]</code> values must be supplied,
+ * a start and end value. If more values are supplied, the values will be animated from the
+ * start, through all intermediate values to the end value. When used with ObjectAnimator,
+ * the elements of the array represent the parameters of the setter function.
+ *
+ * @param propertyName The name of the property being animated. Can also be the
+ * case-sensitive name of the entire setter method. Should not be null.
+ * @param values The values that the property will animate between.
+ * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
+ * @see IntArrayEvaluator#IntArrayEvaluator(int[])
+ * @see ObjectAnimator#ofMultiInt(Object, String, TypeConverter, TypeEvaluator, Object[])
+ */
+ public static PropertyValuesHolder ofMultiInt(String propertyName, int[][] values) {
+ if (values.length < 2) {
+ throw new IllegalArgumentException("At least 2 values must be supplied");
+ }
+ int numParameters = 0;
+ for (int i = 0; i < values.length; i++) {
+ if (values[i] == null) {
+ throw new IllegalArgumentException("values must not be null");
+ }
+ int length = values[i].length;
+ if (i == 0) {
+ numParameters = length;
+ } else if (length != numParameters) {
+ throw new IllegalArgumentException("Values must all have the same length");
+ }
+ }
+ IntArrayEvaluator evaluator = new IntArrayEvaluator(new int[numParameters]);
+ return new MultiIntValuesHolder(propertyName, null, evaluator, (Object[]) values);
+ }
+
+ /**
+ * Constructs and returns a PropertyValuesHolder with a given property name to use
+ * as a multi-int setter. The values are animated along the path, with the first
+ * parameter of the setter set to the x coordinate and the second set to the y coordinate.
+ *
+ * @param propertyName The name of the property being animated. Can also be the
+ * case-sensitive name of the entire setter method. Should not be null.
+ * The setter must take exactly two <code>int</code> parameters.
+ * @param path The Path along which the values should be animated.
+ * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
+ * @see ObjectAnimator#ofPropertyValuesHolder(Object, PropertyValuesHolder...)
+ */
+ public static PropertyValuesHolder ofMultiInt(String propertyName, Path path) {
+ Keyframes keyframes = KeyframeSet.ofPath(path);
+ PointFToIntArray converter = new PointFToIntArray();
+ return new MultiIntValuesHolder(propertyName, converter, null, keyframes);
+ }
+
+ /**
+ * Constructs and returns a PropertyValuesHolder with a given property and
+ * set of Object values for use with ObjectAnimator multi-value setters. The Object
+ * values are converted to <code>int[]</code> using the converter.
+ *
+ * @param propertyName The property being animated or complete name of the setter.
+ * Should not be null.
+ * @param converter Used to convert the animated value to setter parameters.
+ * @param evaluator A TypeEvaluator that will be called on each animation frame to
+ * provide the necessary interpolation between the Object values to derive the animated
+ * value.
+ * @param values The values that the property will animate between.
+ * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
+ * @see ObjectAnimator#ofMultiInt(Object, String, TypeConverter, TypeEvaluator, Object[])
+ * @see ObjectAnimator#ofPropertyValuesHolder(Object, PropertyValuesHolder...)
+ */
+ @SafeVarargs
+ public static <V> PropertyValuesHolder ofMultiInt(String propertyName,
+ TypeConverter<V, int[]> converter, TypeEvaluator<V> evaluator, V... values) {
+ return new MultiIntValuesHolder(propertyName, converter, evaluator, values);
+ }
+
+ /**
+ * Constructs and returns a PropertyValuesHolder object with the specified property name or
+ * setter name for use in a multi-int setter function using ObjectAnimator. The values can be
+ * of any type, but the type should be consistent so that the supplied
+ * {@link android.animation.TypeEvaluator} can be used to to evaluate the animated value. The
+ * <code>converter</code> converts the values to parameters in the setter function.
+ *
+ * <p>At least two values must be supplied, a start and an end value.</p>
+ *
+ * @param propertyName The name of the property to associate with the set of values. This
+ * may also be the complete name of a setter function.
+ * @param converter Converts <code>values</code> into int parameters for the setter.
+ * Can be null if the Keyframes have int[] values.
+ * @param evaluator Used to interpolate between values.
+ * @param values The values at specific fractional times to evaluate between
+ * @return A PropertyValuesHolder for a multi-int parameter setter.
+ */
+ public static <T> PropertyValuesHolder ofMultiInt(String propertyName,
+ TypeConverter<T, int[]> converter, TypeEvaluator<T> evaluator, Keyframe... values) {
+ KeyframeSet keyframeSet = KeyframeSet.ofKeyframe(values);
+ return new MultiIntValuesHolder(propertyName, converter, evaluator, keyframeSet);
+ }
+
+ /**
+ * Constructs and returns a PropertyValuesHolder with a given property name and
+ * set of float values.
+ * @param propertyName The name of the property being animated.
+ * @param values The values that the named property will animate between.
+ * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
+ */
+ public static PropertyValuesHolder ofFloat(String propertyName, float... values) {
+ return new FloatPropertyValuesHolder(propertyName, values);
+ }
+
+ /**
+ * Constructs and returns a PropertyValuesHolder with a given property and
+ * set of float values.
+ * @param property The property being animated. Should not be null.
+ * @param values The values that the property will animate between.
+ * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
+ */
+ public static PropertyValuesHolder ofFloat(Property<?, Float> property, float... values) {
+ return new FloatPropertyValuesHolder(property, values);
+ }
+
+ /**
+ * Constructs and returns a PropertyValuesHolder with a given property name and
+ * set of <code>float[]</code> values. At least two <code>float[]</code> values must be supplied,
+ * a start and end value. If more values are supplied, the values will be animated from the
+ * start, through all intermediate values to the end value. When used with ObjectAnimator,
+ * the elements of the array represent the parameters of the setter function.
+ *
+ * @param propertyName The name of the property being animated. Can also be the
+ * case-sensitive name of the entire setter method. Should not be null.
+ * @param values The values that the property will animate between.
+ * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
+ * @see FloatArrayEvaluator#FloatArrayEvaluator(float[])
+ * @see ObjectAnimator#ofMultiFloat(Object, String, TypeConverter, TypeEvaluator, Object[])
+ */
+ public static PropertyValuesHolder ofMultiFloat(String propertyName, float[][] values) {
+ if (values.length < 2) {
+ throw new IllegalArgumentException("At least 2 values must be supplied");
+ }
+ int numParameters = 0;
+ for (int i = 0; i < values.length; i++) {
+ if (values[i] == null) {
+ throw new IllegalArgumentException("values must not be null");
+ }
+ int length = values[i].length;
+ if (i == 0) {
+ numParameters = length;
+ } else if (length != numParameters) {
+ throw new IllegalArgumentException("Values must all have the same length");
+ }
+ }
+ FloatArrayEvaluator evaluator = new FloatArrayEvaluator(new float[numParameters]);
+ return new MultiFloatValuesHolder(propertyName, null, evaluator, (Object[]) values);
+ }
+
+ /**
+ * Constructs and returns a PropertyValuesHolder with a given property name to use
+ * as a multi-float setter. The values are animated along the path, with the first
+ * parameter of the setter set to the x coordinate and the second set to the y coordinate.
+ *
+ * @param propertyName The name of the property being animated. Can also be the
+ * case-sensitive name of the entire setter method. Should not be null.
+ * The setter must take exactly two <code>float</code> parameters.
+ * @param path The Path along which the values should be animated.
+ * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
+ * @see ObjectAnimator#ofPropertyValuesHolder(Object, PropertyValuesHolder...)
+ */
+ public static PropertyValuesHolder ofMultiFloat(String propertyName, Path path) {
+ Keyframes keyframes = KeyframeSet.ofPath(path);
+ PointFToFloatArray converter = new PointFToFloatArray();
+ return new MultiFloatValuesHolder(propertyName, converter, null, keyframes);
+ }
+
+ /**
+ * Constructs and returns a PropertyValuesHolder with a given property and
+ * set of Object values for use with ObjectAnimator multi-value setters. The Object
+ * values are converted to <code>float[]</code> using the converter.
+ *
+ * @param propertyName The property being animated or complete name of the setter.
+ * Should not be null.
+ * @param converter Used to convert the animated value to setter parameters.
+ * @param evaluator A TypeEvaluator that will be called on each animation frame to
+ * provide the necessary interpolation between the Object values to derive the animated
+ * value.
+ * @param values The values that the property will animate between.
+ * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
+ * @see ObjectAnimator#ofMultiFloat(Object, String, TypeConverter, TypeEvaluator, Object[])
+ */
+ @SafeVarargs
+ public static <V> PropertyValuesHolder ofMultiFloat(String propertyName,
+ TypeConverter<V, float[]> converter, TypeEvaluator<V> evaluator, V... values) {
+ return new MultiFloatValuesHolder(propertyName, converter, evaluator, values);
+ }
+
+ /**
+ * Constructs and returns a PropertyValuesHolder object with the specified property name or
+ * setter name for use in a multi-float setter function using ObjectAnimator. The values can be
+ * of any type, but the type should be consistent so that the supplied
+ * {@link android.animation.TypeEvaluator} can be used to to evaluate the animated value. The
+ * <code>converter</code> converts the values to parameters in the setter function.
+ *
+ * <p>At least two values must be supplied, a start and an end value.</p>
+ *
+ * @param propertyName The name of the property to associate with the set of values. This
+ * may also be the complete name of a setter function.
+ * @param converter Converts <code>values</code> into float parameters for the setter.
+ * Can be null if the Keyframes have float[] values.
+ * @param evaluator Used to interpolate between values.
+ * @param values The values at specific fractional times to evaluate between
+ * @return A PropertyValuesHolder for a multi-float parameter setter.
+ */
+ public static <T> PropertyValuesHolder ofMultiFloat(String propertyName,
+ TypeConverter<T, float[]> converter, TypeEvaluator<T> evaluator, Keyframe... values) {
+ KeyframeSet keyframeSet = KeyframeSet.ofKeyframe(values);
+ return new MultiFloatValuesHolder(propertyName, converter, evaluator, keyframeSet);
+ }
+
+ /**
+ * Constructs and returns a PropertyValuesHolder with a given property name and
+ * set of Object values. This variant also takes a TypeEvaluator because the system
+ * cannot automatically interpolate between objects of unknown type.
+ *
+ * <p><strong>Note:</strong> The Object values are stored as references to the original
+ * objects, which means that changes to those objects after this method is called will
+ * affect the values on the PropertyValuesHolder. If the objects will be mutated externally
+ * after this method is called, callers should pass a copy of those objects instead.
+ *
+ * @param propertyName The name of the property being animated.
+ * @param evaluator A TypeEvaluator that will be called on each animation frame to
+ * provide the necessary interpolation between the Object values to derive the animated
+ * value.
+ * @param values The values that the named property will animate between.
+ * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
+ */
+ public static PropertyValuesHolder ofObject(String propertyName, TypeEvaluator evaluator,
+ Object... values) {
+ PropertyValuesHolder pvh = new PropertyValuesHolder(propertyName);
+ pvh.setObjectValues(values);
+ pvh.setEvaluator(evaluator);
+ return pvh;
+ }
+
+ /**
+ * Constructs and returns a PropertyValuesHolder with a given property name and
+ * a Path along which the values should be animated. This variant supports a
+ * <code>TypeConverter</code> to convert from <code>PointF</code> to the target
+ * type.
+ *
+ * <p>The PointF passed to <code>converter</code> or <code>property</code>, if
+ * <code>converter</code> is <code>null</code>, is reused on each animation frame and should
+ * not be stored by the setter or TypeConverter.</p>
+ *
+ * @param propertyName The name of the property being animated.
+ * @param converter Converts a PointF to the type associated with the setter. May be
+ * null if conversion is unnecessary.
+ * @param path The Path along which the values should be animated.
+ * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
+ */
+ public static PropertyValuesHolder ofObject(String propertyName,
+ TypeConverter<PointF, ?> converter, Path path) {
+ PropertyValuesHolder pvh = new PropertyValuesHolder(propertyName);
+ pvh.mKeyframes = KeyframeSet.ofPath(path);
+ pvh.mValueType = PointF.class;
+ pvh.setConverter(converter);
+ return pvh;
+ }
+
+ /**
+ * Constructs and returns a PropertyValuesHolder with a given property and
+ * set of Object values. This variant also takes a TypeEvaluator because the system
+ * cannot automatically interpolate between objects of unknown type.
+ *
+ * <p><strong>Note:</strong> The Object values are stored as references to the original
+ * objects, which means that changes to those objects after this method is called will
+ * affect the values on the PropertyValuesHolder. If the objects will be mutated externally
+ * after this method is called, callers should pass a copy of those objects instead.
+ *
+ * @param property The property being animated. Should not be null.
+ * @param evaluator A TypeEvaluator that will be called on each animation frame to
+ * provide the necessary interpolation between the Object values to derive the animated
+ * value.
+ * @param values The values that the property will animate between.
+ * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
+ */
+ @SafeVarargs
+ public static <V> PropertyValuesHolder ofObject(Property property,
+ TypeEvaluator<V> evaluator, V... values) {
+ PropertyValuesHolder pvh = new PropertyValuesHolder(property);
+ pvh.setObjectValues(values);
+ pvh.setEvaluator(evaluator);
+ return pvh;
+ }
+
+ /**
+ * Constructs and returns a PropertyValuesHolder with a given property and
+ * set of Object values. This variant also takes a TypeEvaluator because the system
+ * cannot automatically interpolate between objects of unknown type. This variant also
+ * takes a <code>TypeConverter</code> to convert from animated values to the type
+ * of the property. If only one value is supplied, the <code>TypeConverter</code>
+ * must be a {@link android.animation.BidirectionalTypeConverter} to retrieve the current
+ * value.
+ *
+ * <p><strong>Note:</strong> The Object values are stored as references to the original
+ * objects, which means that changes to those objects after this method is called will
+ * affect the values on the PropertyValuesHolder. If the objects will be mutated externally
+ * after this method is called, callers should pass a copy of those objects instead.
+ *
+ * @param property The property being animated. Should not be null.
+ * @param converter Converts the animated object to the Property type.
+ * @param evaluator A TypeEvaluator that will be called on each animation frame to
+ * provide the necessary interpolation between the Object values to derive the animated
+ * value.
+ * @param values The values that the property will animate between.
+ * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
+ * @see #setConverter(TypeConverter)
+ * @see TypeConverter
+ */
+ @SafeVarargs
+ public static <T, V> PropertyValuesHolder ofObject(Property<?, V> property,
+ TypeConverter<T, V> converter, TypeEvaluator<T> evaluator, T... values) {
+ PropertyValuesHolder pvh = new PropertyValuesHolder(property);
+ pvh.setConverter(converter);
+ pvh.setObjectValues(values);
+ pvh.setEvaluator(evaluator);
+ return pvh;
+ }
+
+ /**
+ * Constructs and returns a PropertyValuesHolder with a given property and
+ * a Path along which the values should be animated. This variant supports a
+ * <code>TypeConverter</code> to convert from <code>PointF</code> to the target
+ * type.
+ *
+ * <p>The PointF passed to <code>converter</code> or <code>property</code>, if
+ * <code>converter</code> is <code>null</code>, is reused on each animation frame and should
+ * not be stored by the setter or TypeConverter.</p>
+ *
+ * @param property The property being animated. Should not be null.
+ * @param converter Converts a PointF to the type associated with the setter. May be
+ * null if conversion is unnecessary.
+ * @param path The Path along which the values should be animated.
+ * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
+ */
+ public static <V> PropertyValuesHolder ofObject(Property<?, V> property,
+ TypeConverter<PointF, V> converter, Path path) {
+ PropertyValuesHolder pvh = new PropertyValuesHolder(property);
+ pvh.mKeyframes = KeyframeSet.ofPath(path);
+ pvh.mValueType = PointF.class;
+ pvh.setConverter(converter);
+ return pvh;
+ }
+
+ /**
+ * Constructs and returns a PropertyValuesHolder object with the specified property name and set
+ * of values. These values can be of any type, but the type should be consistent so that
+ * an appropriate {@link android.animation.TypeEvaluator} can be found that matches
+ * the common type.
+ * <p>If there is only one value, it is assumed to be the end value of an animation,
+ * and an initial value will be derived, if possible, by calling a getter function
+ * on the object. Also, if any value is null, the value will be filled in when the animation
+ * starts in the same way. This mechanism of automatically getting null values only works
+ * if the PropertyValuesHolder object is used in conjunction
+ * {@link ObjectAnimator}, and with a getter function
+ * derived automatically from <code>propertyName</code>, since otherwise PropertyValuesHolder has
+ * no way of determining what the value should be.
+ * @param propertyName The name of the property associated with this set of values. This
+ * can be the actual property name to be used when using a ObjectAnimator object, or
+ * just a name used to get animated values, such as if this object is used with an
+ * ValueAnimator object.
+ * @param values The set of values to animate between.
+ */
+ public static PropertyValuesHolder ofKeyframe(String propertyName, Keyframe... values) {
+ KeyframeSet keyframeSet = KeyframeSet.ofKeyframe(values);
+ return ofKeyframes(propertyName, keyframeSet);
+ }
+
+ /**
+ * Constructs and returns a PropertyValuesHolder object with the specified property and set
+ * of values. These values can be of any type, but the type should be consistent so that
+ * an appropriate {@link android.animation.TypeEvaluator} can be found that matches
+ * the common type.
+ * <p>If there is only one value, it is assumed to be the end value of an animation,
+ * and an initial value will be derived, if possible, by calling the property's
+ * {@link android.util.Property#get(Object)} function.
+ * Also, if any value is null, the value will be filled in when the animation
+ * starts in the same way. This mechanism of automatically getting null values only works
+ * if the PropertyValuesHolder object is used in conjunction with
+ * {@link ObjectAnimator}, since otherwise PropertyValuesHolder has
+ * no way of determining what the value should be.
+ * @param property The property associated with this set of values. Should not be null.
+ * @param values The set of values to animate between.
+ */
+ public static PropertyValuesHolder ofKeyframe(Property property, Keyframe... values) {
+ KeyframeSet keyframeSet = KeyframeSet.ofKeyframe(values);
+ return ofKeyframes(property, keyframeSet);
+ }
+
+ static PropertyValuesHolder ofKeyframes(String propertyName, Keyframes keyframes) {
+ if (keyframes instanceof Keyframes.IntKeyframes) {
+ return new IntPropertyValuesHolder(propertyName, (Keyframes.IntKeyframes) keyframes);
+ } else if (keyframes instanceof Keyframes.FloatKeyframes) {
+ return new FloatPropertyValuesHolder(propertyName,
+ (Keyframes.FloatKeyframes) keyframes);
+ } else {
+ PropertyValuesHolder pvh = new PropertyValuesHolder(propertyName);
+ pvh.mKeyframes = keyframes;
+ pvh.mValueType = keyframes.getType();
+ return pvh;
+ }
+ }
+
+ static PropertyValuesHolder ofKeyframes(Property property, Keyframes keyframes) {
+ if (keyframes instanceof Keyframes.IntKeyframes) {
+ return new IntPropertyValuesHolder(property, (Keyframes.IntKeyframes) keyframes);
+ } else if (keyframes instanceof Keyframes.FloatKeyframes) {
+ return new FloatPropertyValuesHolder(property, (Keyframes.FloatKeyframes) keyframes);
+ } else {
+ PropertyValuesHolder pvh = new PropertyValuesHolder(property);
+ pvh.mKeyframes = keyframes;
+ pvh.mValueType = keyframes.getType();
+ return pvh;
+ }
+ }
+
+ /**
+ * Set the animated values for this object to this set of ints.
+ * If there is only one value, it is assumed to be the end value of an animation,
+ * and an initial value will be derived, if possible, by calling a getter function
+ * on the object. Also, if any value is null, the value will be filled in when the animation
+ * starts in the same way. This mechanism of automatically getting null values only works
+ * if the PropertyValuesHolder object is used in conjunction
+ * {@link ObjectAnimator}, and with a getter function
+ * derived automatically from <code>propertyName</code>, since otherwise PropertyValuesHolder has
+ * no way of determining what the value should be.
+ *
+ * @param values One or more values that the animation will animate between.
+ */
+ public void setIntValues(int... values) {
+ mValueType = int.class;
+ mKeyframes = KeyframeSet.ofInt(values);
+ }
+
+ /**
+ * Set the animated values for this object to this set of floats.
+ * If there is only one value, it is assumed to be the end value of an animation,
+ * and an initial value will be derived, if possible, by calling a getter function
+ * on the object. Also, if any value is null, the value will be filled in when the animation
+ * starts in the same way. This mechanism of automatically getting null values only works
+ * if the PropertyValuesHolder object is used in conjunction
+ * {@link ObjectAnimator}, and with a getter function
+ * derived automatically from <code>propertyName</code>, since otherwise PropertyValuesHolder has
+ * no way of determining what the value should be.
+ *
+ * @param values One or more values that the animation will animate between.
+ */
+ public void setFloatValues(float... values) {
+ mValueType = float.class;
+ mKeyframes = KeyframeSet.ofFloat(values);
+ }
+
+ /**
+ * Set the animated values for this object to this set of Keyframes.
+ *
+ * @param values One or more values that the animation will animate between.
+ */
+ public void setKeyframes(Keyframe... values) {
+ int numKeyframes = values.length;
+ Keyframe keyframes[] = new Keyframe[Math.max(numKeyframes,2)];
+ mValueType = ((Keyframe)values[0]).getType();
+ for (int i = 0; i < numKeyframes; ++i) {
+ keyframes[i] = (Keyframe)values[i];
+ }
+ mKeyframes = new KeyframeSet(keyframes);
+ }
+
+ /**
+ * Set the animated values for this object to this set of Objects.
+ * If there is only one value, it is assumed to be the end value of an animation,
+ * and an initial value will be derived, if possible, by calling a getter function
+ * on the object. Also, if any value is null, the value will be filled in when the animation
+ * starts in the same way. This mechanism of automatically getting null values only works
+ * if the PropertyValuesHolder object is used in conjunction
+ * {@link ObjectAnimator}, and with a getter function
+ * derived automatically from <code>propertyName</code>, since otherwise PropertyValuesHolder has
+ * no way of determining what the value should be.
+ *
+ * <p><strong>Note:</strong> The Object values are stored as references to the original
+ * objects, which means that changes to those objects after this method is called will
+ * affect the values on the PropertyValuesHolder. If the objects will be mutated externally
+ * after this method is called, callers should pass a copy of those objects instead.
+ *
+ * @param values One or more values that the animation will animate between.
+ */
+ public void setObjectValues(Object... values) {
+ mValueType = values[0].getClass();
+ mKeyframes = KeyframeSet.ofObject(values);
+ if (mEvaluator != null) {
+ mKeyframes.setEvaluator(mEvaluator);
+ }
+ }
+
+ /**
+ * Sets the converter to convert from the values type to the setter's parameter type.
+ * If only one value is supplied, <var>converter</var> must be a
+ * {@link android.animation.BidirectionalTypeConverter}.
+ * @param converter The converter to use to convert values.
+ */
+ public void setConverter(TypeConverter converter) {
+ mConverter = converter;
+ }
+
+ /**
+ * Determine the setter or getter function using the JavaBeans convention of setFoo or
+ * getFoo for a property named 'foo'. This function figures out what the name of the
+ * function should be and uses reflection to find the Method with that name on the
+ * target object.
+ *
+ * @param targetClass The class to search for the method
+ * @param prefix "set" or "get", depending on whether we need a setter or getter.
+ * @param valueType The type of the parameter (in the case of a setter). This type
+ * is derived from the values set on this PropertyValuesHolder. This type is used as
+ * a first guess at the parameter type, but we check for methods with several different
+ * types to avoid problems with slight mis-matches between supplied values and actual
+ * value types used on the setter.
+ * @return Method the method associated with mPropertyName.
+ */
+ private Method getPropertyFunction(Class targetClass, String prefix, Class valueType) {
+ // TODO: faster implementation...
+ Method returnVal = null;
+ String methodName = getMethodName(prefix, mPropertyName);
+ Class args[] = null;
+ if (valueType == null) {
+ try {
+ returnVal = targetClass.getMethod(methodName, args);
+ } catch (NoSuchMethodException e) {
+ // Swallow the error, log it later
+ }
+ } else {
+ args = new Class[1];
+ Class typeVariants[];
+ if (valueType.equals(Float.class)) {
+ typeVariants = FLOAT_VARIANTS;
+ } else if (valueType.equals(Integer.class)) {
+ typeVariants = INTEGER_VARIANTS;
+ } else if (valueType.equals(Double.class)) {
+ typeVariants = DOUBLE_VARIANTS;
+ } else {
+ typeVariants = new Class[1];
+ typeVariants[0] = valueType;
+ }
+ for (Class typeVariant : typeVariants) {
+ args[0] = typeVariant;
+ try {
+ returnVal = targetClass.getMethod(methodName, args);
+ if (mConverter == null) {
+ // change the value type to suit
+ mValueType = typeVariant;
+ }
+ return returnVal;
+ } catch (NoSuchMethodException e) {
+ // Swallow the error and keep trying other variants
+ }
+ }
+ // If we got here, then no appropriate function was found
+ }
+
+ if (returnVal == null) {
+ Log.w("PropertyValuesHolder", "Method " +
+ getMethodName(prefix, mPropertyName) + "() with type " + valueType +
+ " not found on target class " + targetClass);
+ }
+
+ return returnVal;
+ }
+
+
+ /**
+ * Returns the setter or getter requested. This utility function checks whether the
+ * requested method exists in the propertyMapMap cache. If not, it calls another
+ * utility function to request the Method from the targetClass directly.
+ * @param targetClass The Class on which the requested method should exist.
+ * @param propertyMapMap The cache of setters/getters derived so far.
+ * @param prefix "set" or "get", for the setter or getter.
+ * @param valueType The type of parameter passed into the method (null for getter).
+ * @return Method the method associated with mPropertyName.
+ */
+ private Method setupSetterOrGetter(Class targetClass,
+ HashMap<Class, HashMap<String, Method>> propertyMapMap,
+ String prefix, Class valueType) {
+ Method setterOrGetter = null;
+ synchronized(propertyMapMap) {
+ // Have to lock property map prior to reading it, to guard against
+ // another thread putting something in there after we've checked it
+ // but before we've added an entry to it
+ HashMap<String, Method> propertyMap = propertyMapMap.get(targetClass);
+ boolean wasInMap = false;
+ if (propertyMap != null) {
+ wasInMap = propertyMap.containsKey(mPropertyName);
+ if (wasInMap) {
+ setterOrGetter = propertyMap.get(mPropertyName);
+ }
+ }
+ if (!wasInMap) {
+ setterOrGetter = getPropertyFunction(targetClass, prefix, valueType);
+ if (propertyMap == null) {
+ propertyMap = new HashMap<String, Method>();
+ propertyMapMap.put(targetClass, propertyMap);
+ }
+ propertyMap.put(mPropertyName, setterOrGetter);
+ }
+ }
+ return setterOrGetter;
+ }
+
+ /**
+ * Utility function to get the setter from targetClass
+ * @param targetClass The Class on which the requested method should exist.
+ */
+ void setupSetter(Class targetClass) {
+ Class<?> propertyType = mConverter == null ? mValueType : mConverter.getTargetType();
+ mSetter = setupSetterOrGetter(targetClass, sSetterPropertyMap, "set", propertyType);
+ }
+
+ /**
+ * Utility function to get the getter from targetClass
+ */
+ private void setupGetter(Class targetClass) {
+ mGetter = setupSetterOrGetter(targetClass, sGetterPropertyMap, "get", null);
+ }
+
+ /**
+ * Internal function (called from ObjectAnimator) to set up the setter and getter
+ * prior to running the animation. If the setter has not been manually set for this
+ * object, it will be derived automatically given the property name, target object, and
+ * types of values supplied. If no getter has been set, it will be supplied iff any of the
+ * supplied values was null. If there is a null value, then the getter (supplied or derived)
+ * will be called to set those null values to the current value of the property
+ * on the target object.
+ * @param target The object on which the setter (and possibly getter) exist.
+ */
+ void setupSetterAndGetter(Object target) {
+ if (mProperty != null) {
+ // check to make sure that mProperty is on the class of target
+ try {
+ Object testValue = null;
+ List<Keyframe> keyframes = mKeyframes.getKeyframes();
+ int keyframeCount = keyframes == null ? 0 : keyframes.size();
+ for (int i = 0; i < keyframeCount; i++) {
+ Keyframe kf = keyframes.get(i);
+ if (!kf.hasValue() || kf.valueWasSetOnStart()) {
+ if (testValue == null) {
+ testValue = convertBack(mProperty.get(target));
+ }
+ kf.setValue(testValue);
+ kf.setValueWasSetOnStart(true);
+ }
+ }
+ return;
+ } catch (ClassCastException e) {
+ Log.w("PropertyValuesHolder","No such property (" + mProperty.getName() +
+ ") on target object " + target + ". Trying reflection instead");
+ mProperty = null;
+ }
+ }
+ // We can't just say 'else' here because the catch statement sets mProperty to null.
+ if (mProperty == null) {
+ Class targetClass = target.getClass();
+ if (mSetter == null) {
+ setupSetter(targetClass);
+ }
+ List<Keyframe> keyframes = mKeyframes.getKeyframes();
+ int keyframeCount = keyframes == null ? 0 : keyframes.size();
+ for (int i = 0; i < keyframeCount; i++) {
+ Keyframe kf = keyframes.get(i);
+ if (!kf.hasValue() || kf.valueWasSetOnStart()) {
+ if (mGetter == null) {
+ setupGetter(targetClass);
+ if (mGetter == null) {
+ // Already logged the error - just return to avoid NPE
+ return;
+ }
+ }
+ try {
+ Object value = convertBack(mGetter.invoke(target));
+ kf.setValue(value);
+ kf.setValueWasSetOnStart(true);
+ } catch (InvocationTargetException e) {
+ Log.e("PropertyValuesHolder", e.toString());
+ } catch (IllegalAccessException e) {
+ Log.e("PropertyValuesHolder", e.toString());
+ }
+ }
+ }
+ }
+ }
+
+ private Object convertBack(Object value) {
+ if (mConverter != null) {
+ if (!(mConverter instanceof BidirectionalTypeConverter)) {
+ throw new IllegalArgumentException("Converter "
+ + mConverter.getClass().getName()
+ + " must be a BidirectionalTypeConverter");
+ }
+ value = ((BidirectionalTypeConverter) mConverter).convertBack(value);
+ }
+ return value;
+ }
+
+ /**
+ * Utility function to set the value stored in a particular Keyframe. The value used is
+ * whatever the value is for the property name specified in the keyframe on the target object.
+ *
+ * @param target The target object from which the current value should be extracted.
+ * @param kf The keyframe which holds the property name and value.
+ */
+ private void setupValue(Object target, Keyframe kf) {
+ if (mProperty != null) {
+ Object value = convertBack(mProperty.get(target));
+ kf.setValue(value);
+ } else {
+ try {
+ if (mGetter == null) {
+ Class targetClass = target.getClass();
+ setupGetter(targetClass);
+ if (mGetter == null) {
+ // Already logged the error - just return to avoid NPE
+ return;
+ }
+ }
+ Object value = convertBack(mGetter.invoke(target));
+ kf.setValue(value);
+ } catch (InvocationTargetException e) {
+ Log.e("PropertyValuesHolder", e.toString());
+ } catch (IllegalAccessException e) {
+ Log.e("PropertyValuesHolder", e.toString());
+ }
+ }
+ }
+
+ /**
+ * This function is called by ObjectAnimator when setting the start values for an animation.
+ * The start values are set according to the current values in the target object. The
+ * property whose value is extracted is whatever is specified by the propertyName of this
+ * PropertyValuesHolder object.
+ *
+ * @param target The object which holds the start values that should be set.
+ */
+ void setupStartValue(Object target) {
+ List<Keyframe> keyframes = mKeyframes.getKeyframes();
+ if (!keyframes.isEmpty()) {
+ setupValue(target, keyframes.get(0));
+ }
+ }
+
+ /**
+ * This function is called by ObjectAnimator when setting the end values for an animation.
+ * The end values are set according to the current values in the target object. The
+ * property whose value is extracted is whatever is specified by the propertyName of this
+ * PropertyValuesHolder object.
+ *
+ * @param target The object which holds the start values that should be set.
+ */
+ void setupEndValue(Object target) {
+ List<Keyframe> keyframes = mKeyframes.getKeyframes();
+ if (!keyframes.isEmpty()) {
+ setupValue(target, keyframes.get(keyframes.size() - 1));
+ }
+ }
+
+ @Override
+ public PropertyValuesHolder clone() {
+ try {
+ PropertyValuesHolder newPVH = (PropertyValuesHolder) super.clone();
+ newPVH.mPropertyName = mPropertyName;
+ newPVH.mProperty = mProperty;
+ newPVH.mKeyframes = mKeyframes.clone();
+ newPVH.mEvaluator = mEvaluator;
+ return newPVH;
+ } catch (CloneNotSupportedException e) {
+ // won't reach here
+ return null;
+ }
+ }
+
+ /**
+ * Internal function to set the value on the target object, using the setter set up
+ * earlier on this PropertyValuesHolder object. This function is called by ObjectAnimator
+ * to handle turning the value calculated by ValueAnimator into a value set on the object
+ * according to the name of the property.
+ * @param target The target object on which the value is set
+ */
+ void setAnimatedValue(Object target) {
+ if (mProperty != null) {
+ mProperty.set(target, getAnimatedValue());
+ }
+ if (mSetter != null) {
+ try {
+ mTmpValueArray[0] = getAnimatedValue();
+ mSetter.invoke(target, mTmpValueArray);
+ } catch (InvocationTargetException e) {
+ Log.e("PropertyValuesHolder", e.toString());
+ } catch (IllegalAccessException e) {
+ Log.e("PropertyValuesHolder", e.toString());
+ }
+ }
+ }
+
+ /**
+ * Internal function, called by ValueAnimator, to set up the TypeEvaluator that will be used
+ * to calculate animated values.
+ */
+ void init() {
+ if (mEvaluator == null) {
+ // We already handle int and float automatically, but not their Object
+ // equivalents
+ mEvaluator = (mValueType == Integer.class) ? sIntEvaluator :
+ (mValueType == Float.class) ? sFloatEvaluator :
+ null;
+ }
+ if (mEvaluator != null) {
+ // KeyframeSet knows how to evaluate the common types - only give it a custom
+ // evaluator if one has been set on this class
+ mKeyframes.setEvaluator(mEvaluator);
+ }
+ }
+
+ /**
+ * The TypeEvaluator will be automatically determined based on the type of values
+ * supplied to PropertyValuesHolder. The evaluator can be manually set, however, if so
+ * desired. This may be important in cases where either the type of the values supplied
+ * do not match the way that they should be interpolated between, or if the values
+ * are of a custom type or one not currently understood by the animation system. Currently,
+ * only values of type float and int (and their Object equivalents: Float
+ * and Integer) are correctly interpolated; all other types require setting a TypeEvaluator.
+ * @param evaluator
+ */
+ public void setEvaluator(TypeEvaluator evaluator) {
+ mEvaluator = evaluator;
+ mKeyframes.setEvaluator(evaluator);
+ }
+
+ /**
+ * Function used to calculate the value according to the evaluator set up for
+ * this PropertyValuesHolder object. This function is called by ValueAnimator.animateValue().
+ *
+ * @param fraction The elapsed, interpolated fraction of the animation.
+ */
+ void calculateValue(float fraction) {
+ Object value = mKeyframes.getValue(fraction);
+ mAnimatedValue = mConverter == null ? value : mConverter.convert(value);
+ }
+
+ /**
+ * Sets the name of the property that will be animated. This name is used to derive
+ * a setter function that will be called to set animated values.
+ * For example, a property name of <code>foo</code> will result
+ * in a call to the function <code>setFoo()</code> on the target object. If either
+ * <code>valueFrom</code> or <code>valueTo</code> is null, then a getter function will
+ * also be derived and called.
+ *
+ * <p>Note that the setter function derived from this property name
+ * must take the same parameter type as the
+ * <code>valueFrom</code> and <code>valueTo</code> properties, otherwise the call to
+ * the setter function will fail.</p>
+ *
+ * @param propertyName The name of the property being animated.
+ */
+ public void setPropertyName(String propertyName) {
+ mPropertyName = propertyName;
+ }
+
+ /**
+ * Sets the property that will be animated.
+ *
+ * <p>Note that if this PropertyValuesHolder object is used with ObjectAnimator, the property
+ * must exist on the target object specified in that ObjectAnimator.</p>
+ *
+ * @param property The property being animated.
+ */
+ public void setProperty(Property property) {
+ mProperty = property;
+ }
+
+ /**
+ * Gets the name of the property that will be animated. This name will be used to derive
+ * a setter function that will be called to set animated values.
+ * For example, a property name of <code>foo</code> will result
+ * in a call to the function <code>setFoo()</code> on the target object. If either
+ * <code>valueFrom</code> or <code>valueTo</code> is null, then a getter function will
+ * also be derived and called.
+ */
+ public String getPropertyName() {
+ return mPropertyName;
+ }
+
+ /**
+ * Internal function, called by ValueAnimator and ObjectAnimator, to retrieve the value
+ * most recently calculated in calculateValue().
+ * @return
+ */
+ Object getAnimatedValue() {
+ return mAnimatedValue;
+ }
+
+ /**
+ * PropertyValuesHolder is Animators use to hold internal animation related data.
+ * Therefore, in order to replicate the animation behavior, we need to get data out of
+ * PropertyValuesHolder.
+ * @hide
+ */
+ public void getPropertyValues(PropertyValues values) {
+ init();
+ values.propertyName = mPropertyName;
+ values.type = mValueType;
+ values.startValue = mKeyframes.getValue(0);
+ if (values.startValue instanceof PathParser.PathData) {
+ // PathData evaluator returns the same mutable PathData object when query fraction,
+ // so we have to make a copy here.
+ values.startValue = new PathParser.PathData((PathParser.PathData) values.startValue);
+ }
+ values.endValue = mKeyframes.getValue(1);
+ if (values.endValue instanceof PathParser.PathData) {
+ // PathData evaluator returns the same mutable PathData object when query fraction,
+ // so we have to make a copy here.
+ values.endValue = new PathParser.PathData((PathParser.PathData) values.endValue);
+ }
+ // TODO: We need a better way to get data out of keyframes.
+ if (mKeyframes instanceof PathKeyframes.FloatKeyframesBase
+ || mKeyframes instanceof PathKeyframes.IntKeyframesBase
+ || (mKeyframes.getKeyframes() != null && mKeyframes.getKeyframes().size() > 2)) {
+ // When a pvh has more than 2 keyframes, that means there are intermediate values in
+ // addition to start/end values defined for animators. Another case where such
+ // intermediate values are defined is when animator has a path to animate along. In
+ // these cases, a data source is needed to capture these intermediate values.
+ values.dataSource = new PropertyValues.DataSource() {
+ @Override
+ public Object getValueAtFraction(float fraction) {
+ return mKeyframes.getValue(fraction);
+ }
+ };
+ } else {
+ values.dataSource = null;
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public Class getValueType() {
+ return mValueType;
+ }
+
+ @Override
+ public String toString() {
+ return mPropertyName + ": " + mKeyframes.toString();
+ }
+
+ /**
+ * Utility method to derive a setter/getter method name from a property name, where the
+ * prefix is typically "set" or "get" and the first letter of the property name is
+ * capitalized.
+ *
+ * @param prefix The precursor to the method name, before the property name begins, typically
+ * "set" or "get".
+ * @param propertyName The name of the property that represents the bulk of the method name
+ * after the prefix. The first letter of this word will be capitalized in the resulting
+ * method name.
+ * @return String the property name converted to a method name according to the conventions
+ * specified above.
+ */
+ static String getMethodName(String prefix, String propertyName) {
+ if (propertyName == null || propertyName.length() == 0) {
+ // shouldn't get here
+ return prefix;
+ }
+ char firstLetter = Character.toUpperCase(propertyName.charAt(0));
+ String theRest = propertyName.substring(1);
+ return prefix + firstLetter + theRest;
+ }
+
+ static class IntPropertyValuesHolder extends PropertyValuesHolder {
+
+ // Cache JNI functions to avoid looking them up twice
+ private static final HashMap<Class, HashMap<String, Long>> sJNISetterPropertyMap =
+ new HashMap<Class, HashMap<String, Long>>();
+ long mJniSetter;
+ private IntProperty mIntProperty;
+
+ Keyframes.IntKeyframes mIntKeyframes;
+ int mIntAnimatedValue;
+
+ public IntPropertyValuesHolder(String propertyName, Keyframes.IntKeyframes keyframes) {
+ super(propertyName);
+ mValueType = int.class;
+ mKeyframes = keyframes;
+ mIntKeyframes = keyframes;
+ }
+
+ public IntPropertyValuesHolder(Property property, Keyframes.IntKeyframes keyframes) {
+ super(property);
+ mValueType = int.class;
+ mKeyframes = keyframes;
+ mIntKeyframes = keyframes;
+ if (property instanceof IntProperty) {
+ mIntProperty = (IntProperty) mProperty;
+ }
+ }
+
+ public IntPropertyValuesHolder(String propertyName, int... values) {
+ super(propertyName);
+ setIntValues(values);
+ }
+
+ public IntPropertyValuesHolder(Property property, int... values) {
+ super(property);
+ setIntValues(values);
+ if (property instanceof IntProperty) {
+ mIntProperty = (IntProperty) mProperty;
+ }
+ }
+
+ @Override
+ public void setProperty(Property property) {
+ if (property instanceof IntProperty) {
+ mIntProperty = (IntProperty) property;
+ } else {
+ super.setProperty(property);
+ }
+ }
+
+ @Override
+ public void setIntValues(int... values) {
+ super.setIntValues(values);
+ mIntKeyframes = (Keyframes.IntKeyframes) mKeyframes;
+ }
+
+ @Override
+ void calculateValue(float fraction) {
+ mIntAnimatedValue = mIntKeyframes.getIntValue(fraction);
+ }
+
+ @Override
+ Object getAnimatedValue() {
+ return mIntAnimatedValue;
+ }
+
+ @Override
+ public IntPropertyValuesHolder clone() {
+ IntPropertyValuesHolder newPVH = (IntPropertyValuesHolder) super.clone();
+ newPVH.mIntKeyframes = (Keyframes.IntKeyframes) newPVH.mKeyframes;
+ return newPVH;
+ }
+
+ /**
+ * Internal function to set the value on the target object, using the setter set up
+ * earlier on this PropertyValuesHolder object. This function is called by ObjectAnimator
+ * to handle turning the value calculated by ValueAnimator into a value set on the object
+ * according to the name of the property.
+ * @param target The target object on which the value is set
+ */
+ @Override
+ void setAnimatedValue(Object target) {
+ if (mIntProperty != null) {
+ mIntProperty.setValue(target, mIntAnimatedValue);
+ return;
+ }
+ if (mProperty != null) {
+ mProperty.set(target, mIntAnimatedValue);
+ return;
+ }
+ if (mJniSetter != 0) {
+ nCallIntMethod(target, mJniSetter, mIntAnimatedValue);
+ return;
+ }
+ if (mSetter != null) {
+ try {
+ mTmpValueArray[0] = mIntAnimatedValue;
+ mSetter.invoke(target, mTmpValueArray);
+ } catch (InvocationTargetException e) {
+ Log.e("PropertyValuesHolder", e.toString());
+ } catch (IllegalAccessException e) {
+ Log.e("PropertyValuesHolder", e.toString());
+ }
+ }
+ }
+
+ @Override
+ void setupSetter(Class targetClass) {
+ if (mProperty != null) {
+ return;
+ }
+ // Check new static hashmap<propName, int> for setter method
+ synchronized(sJNISetterPropertyMap) {
+ HashMap<String, Long> propertyMap = sJNISetterPropertyMap.get(targetClass);
+ boolean wasInMap = false;
+ if (propertyMap != null) {
+ wasInMap = propertyMap.containsKey(mPropertyName);
+ if (wasInMap) {
+ Long jniSetter = propertyMap.get(mPropertyName);
+ if (jniSetter != null) {
+ mJniSetter = jniSetter;
+ }
+ }
+ }
+ if (!wasInMap) {
+ String methodName = getMethodName("set", mPropertyName);
+ try {
+ mJniSetter = nGetIntMethod(targetClass, methodName);
+ } catch (NoSuchMethodError e) {
+ // Couldn't find it via JNI - try reflection next. Probably means the method
+ // doesn't exist, or the type is wrong. An error will be logged later if
+ // reflection fails as well.
+ }
+ if (propertyMap == null) {
+ propertyMap = new HashMap<String, Long>();
+ sJNISetterPropertyMap.put(targetClass, propertyMap);
+ }
+ propertyMap.put(mPropertyName, mJniSetter);
+ }
+ }
+ if (mJniSetter == 0) {
+ // Couldn't find method through fast JNI approach - just use reflection
+ super.setupSetter(targetClass);
+ }
+ }
+ }
+
+ static class FloatPropertyValuesHolder extends PropertyValuesHolder {
+
+ // Cache JNI functions to avoid looking them up twice
+ private static final HashMap<Class, HashMap<String, Long>> sJNISetterPropertyMap =
+ new HashMap<Class, HashMap<String, Long>>();
+ long mJniSetter;
+ private FloatProperty mFloatProperty;
+
+ Keyframes.FloatKeyframes mFloatKeyframes;
+ float mFloatAnimatedValue;
+
+ public FloatPropertyValuesHolder(String propertyName, Keyframes.FloatKeyframes keyframes) {
+ super(propertyName);
+ mValueType = float.class;
+ mKeyframes = keyframes;
+ mFloatKeyframes = keyframes;
+ }
+
+ public FloatPropertyValuesHolder(Property property, Keyframes.FloatKeyframes keyframes) {
+ super(property);
+ mValueType = float.class;
+ mKeyframes = keyframes;
+ mFloatKeyframes = keyframes;
+ if (property instanceof FloatProperty) {
+ mFloatProperty = (FloatProperty) mProperty;
+ }
+ }
+
+ public FloatPropertyValuesHolder(String propertyName, float... values) {
+ super(propertyName);
+ setFloatValues(values);
+ }
+
+ public FloatPropertyValuesHolder(Property property, float... values) {
+ super(property);
+ setFloatValues(values);
+ if (property instanceof FloatProperty) {
+ mFloatProperty = (FloatProperty) mProperty;
+ }
+ }
+
+ @Override
+ public void setProperty(Property property) {
+ if (property instanceof FloatProperty) {
+ mFloatProperty = (FloatProperty) property;
+ } else {
+ super.setProperty(property);
+ }
+ }
+
+ @Override
+ public void setFloatValues(float... values) {
+ super.setFloatValues(values);
+ mFloatKeyframes = (Keyframes.FloatKeyframes) mKeyframes;
+ }
+
+ @Override
+ void calculateValue(float fraction) {
+ mFloatAnimatedValue = mFloatKeyframes.getFloatValue(fraction);
+ }
+
+ @Override
+ Object getAnimatedValue() {
+ return mFloatAnimatedValue;
+ }
+
+ @Override
+ public FloatPropertyValuesHolder clone() {
+ FloatPropertyValuesHolder newPVH = (FloatPropertyValuesHolder) super.clone();
+ newPVH.mFloatKeyframes = (Keyframes.FloatKeyframes) newPVH.mKeyframes;
+ return newPVH;
+ }
+
+ /**
+ * Internal function to set the value on the target object, using the setter set up
+ * earlier on this PropertyValuesHolder object. This function is called by ObjectAnimator
+ * to handle turning the value calculated by ValueAnimator into a value set on the object
+ * according to the name of the property.
+ * @param target The target object on which the value is set
+ */
+ @Override
+ void setAnimatedValue(Object target) {
+ if (mFloatProperty != null) {
+ mFloatProperty.setValue(target, mFloatAnimatedValue);
+ return;
+ }
+ if (mProperty != null) {
+ mProperty.set(target, mFloatAnimatedValue);
+ return;
+ }
+ if (mJniSetter != 0) {
+ nCallFloatMethod(target, mJniSetter, mFloatAnimatedValue);
+ return;
+ }
+ if (mSetter != null) {
+ try {
+ mTmpValueArray[0] = mFloatAnimatedValue;
+ mSetter.invoke(target, mTmpValueArray);
+ } catch (InvocationTargetException e) {
+ Log.e("PropertyValuesHolder", e.toString());
+ } catch (IllegalAccessException e) {
+ Log.e("PropertyValuesHolder", e.toString());
+ }
+ }
+ }
+
+ @Override
+ void setupSetter(Class targetClass) {
+ if (mProperty != null) {
+ return;
+ }
+ // Check new static hashmap<propName, int> for setter method
+ synchronized (sJNISetterPropertyMap) {
+ HashMap<String, Long> propertyMap = sJNISetterPropertyMap.get(targetClass);
+ boolean wasInMap = false;
+ if (propertyMap != null) {
+ wasInMap = propertyMap.containsKey(mPropertyName);
+ if (wasInMap) {
+ Long jniSetter = propertyMap.get(mPropertyName);
+ if (jniSetter != null) {
+ mJniSetter = jniSetter;
+ }
+ }
+ }
+ if (!wasInMap) {
+ String methodName = getMethodName("set", mPropertyName);
+ try {
+ mJniSetter = nGetFloatMethod(targetClass, methodName);
+ } catch (NoSuchMethodError e) {
+ // Couldn't find it via JNI - try reflection next. Probably means the method
+ // doesn't exist, or the type is wrong. An error will be logged later if
+ // reflection fails as well.
+ }
+ if (propertyMap == null) {
+ propertyMap = new HashMap<String, Long>();
+ sJNISetterPropertyMap.put(targetClass, propertyMap);
+ }
+ propertyMap.put(mPropertyName, mJniSetter);
+ }
+ }
+ if (mJniSetter == 0) {
+ // Couldn't find method through fast JNI approach - just use reflection
+ super.setupSetter(targetClass);
+ }
+ }
+
+ }
+
+ static class MultiFloatValuesHolder extends PropertyValuesHolder {
+ private long mJniSetter;
+ private static final HashMap<Class, HashMap<String, Long>> sJNISetterPropertyMap =
+ new HashMap<Class, HashMap<String, Long>>();
+
+ public MultiFloatValuesHolder(String propertyName, TypeConverter converter,
+ TypeEvaluator evaluator, Object... values) {
+ super(propertyName);
+ setConverter(converter);
+ setObjectValues(values);
+ setEvaluator(evaluator);
+ }
+
+ public MultiFloatValuesHolder(String propertyName, TypeConverter converter,
+ TypeEvaluator evaluator, Keyframes keyframes) {
+ super(propertyName);
+ setConverter(converter);
+ mKeyframes = keyframes;
+ setEvaluator(evaluator);
+ }
+
+ /**
+ * Internal function to set the value on the target object, using the setter set up
+ * earlier on this PropertyValuesHolder object. This function is called by ObjectAnimator
+ * to handle turning the value calculated by ValueAnimator into a value set on the object
+ * according to the name of the property.
+ *
+ * @param target The target object on which the value is set
+ */
+ @Override
+ void setAnimatedValue(Object target) {
+ float[] values = (float[]) getAnimatedValue();
+ int numParameters = values.length;
+ if (mJniSetter != 0) {
+ switch (numParameters) {
+ case 1:
+ nCallFloatMethod(target, mJniSetter, values[0]);
+ break;
+ case 2:
+ nCallTwoFloatMethod(target, mJniSetter, values[0], values[1]);
+ break;
+ case 4:
+ nCallFourFloatMethod(target, mJniSetter, values[0], values[1],
+ values[2], values[3]);
+ break;
+ default: {
+ nCallMultipleFloatMethod(target, mJniSetter, values);
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * Internal function (called from ObjectAnimator) to set up the setter and getter
+ * prior to running the animation. No getter can be used for multiple parameters.
+ *
+ * @param target The object on which the setter exists.
+ */
+ @Override
+ void setupSetterAndGetter(Object target) {
+ setupSetter(target.getClass());
+ }
+
+ @Override
+ void setupSetter(Class targetClass) {
+ if (mJniSetter != 0) {
+ return;
+ }
+ synchronized(sJNISetterPropertyMap) {
+ HashMap<String, Long> propertyMap = sJNISetterPropertyMap.get(targetClass);
+ boolean wasInMap = false;
+ if (propertyMap != null) {
+ wasInMap = propertyMap.containsKey(mPropertyName);
+ if (wasInMap) {
+ Long jniSetter = propertyMap.get(mPropertyName);
+ if (jniSetter != null) {
+ mJniSetter = jniSetter;
+ }
+ }
+ }
+ if (!wasInMap) {
+ String methodName = getMethodName("set", mPropertyName);
+ calculateValue(0f);
+ float[] values = (float[]) getAnimatedValue();
+ int numParams = values.length;
+ try {
+ mJniSetter = nGetMultipleFloatMethod(targetClass, methodName, numParams);
+ } catch (NoSuchMethodError e) {
+ // try without the 'set' prefix
+ try {
+ mJniSetter = nGetMultipleFloatMethod(targetClass, mPropertyName,
+ numParams);
+ } catch (NoSuchMethodError e2) {
+ // just try reflection next
+ }
+ }
+ if (propertyMap == null) {
+ propertyMap = new HashMap<String, Long>();
+ sJNISetterPropertyMap.put(targetClass, propertyMap);
+ }
+ propertyMap.put(mPropertyName, mJniSetter);
+ }
+ }
+ }
+ }
+
+ static class MultiIntValuesHolder extends PropertyValuesHolder {
+ private long mJniSetter;
+ private static final HashMap<Class, HashMap<String, Long>> sJNISetterPropertyMap =
+ new HashMap<Class, HashMap<String, Long>>();
+
+ public MultiIntValuesHolder(String propertyName, TypeConverter converter,
+ TypeEvaluator evaluator, Object... values) {
+ super(propertyName);
+ setConverter(converter);
+ setObjectValues(values);
+ setEvaluator(evaluator);
+ }
+
+ public MultiIntValuesHolder(String propertyName, TypeConverter converter,
+ TypeEvaluator evaluator, Keyframes keyframes) {
+ super(propertyName);
+ setConverter(converter);
+ mKeyframes = keyframes;
+ setEvaluator(evaluator);
+ }
+
+ /**
+ * Internal function to set the value on the target object, using the setter set up
+ * earlier on this PropertyValuesHolder object. This function is called by ObjectAnimator
+ * to handle turning the value calculated by ValueAnimator into a value set on the object
+ * according to the name of the property.
+ *
+ * @param target The target object on which the value is set
+ */
+ @Override
+ void setAnimatedValue(Object target) {
+ int[] values = (int[]) getAnimatedValue();
+ int numParameters = values.length;
+ if (mJniSetter != 0) {
+ switch (numParameters) {
+ case 1:
+ nCallIntMethod(target, mJniSetter, values[0]);
+ break;
+ case 2:
+ nCallTwoIntMethod(target, mJniSetter, values[0], values[1]);
+ break;
+ case 4:
+ nCallFourIntMethod(target, mJniSetter, values[0], values[1],
+ values[2], values[3]);
+ break;
+ default: {
+ nCallMultipleIntMethod(target, mJniSetter, values);
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * Internal function (called from ObjectAnimator) to set up the setter and getter
+ * prior to running the animation. No getter can be used for multiple parameters.
+ *
+ * @param target The object on which the setter exists.
+ */
+ @Override
+ void setupSetterAndGetter(Object target) {
+ setupSetter(target.getClass());
+ }
+
+ @Override
+ void setupSetter(Class targetClass) {
+ if (mJniSetter != 0) {
+ return;
+ }
+ synchronized(sJNISetterPropertyMap) {
+ HashMap<String, Long> propertyMap = sJNISetterPropertyMap.get(targetClass);
+ boolean wasInMap = false;
+ if (propertyMap != null) {
+ wasInMap = propertyMap.containsKey(mPropertyName);
+ if (wasInMap) {
+ Long jniSetter = propertyMap.get(mPropertyName);
+ if (jniSetter != null) {
+ mJniSetter = jniSetter;
+ }
+ }
+ }
+ if (!wasInMap) {
+ String methodName = getMethodName("set", mPropertyName);
+ calculateValue(0f);
+ int[] values = (int[]) getAnimatedValue();
+ int numParams = values.length;
+ try {
+ mJniSetter = nGetMultipleIntMethod(targetClass, methodName, numParams);
+ } catch (NoSuchMethodError e) {
+ // try without the 'set' prefix
+ try {
+ mJniSetter = nGetMultipleIntMethod(targetClass, mPropertyName,
+ numParams);
+ } catch (NoSuchMethodError e2) {
+ // couldn't find it.
+ }
+ }
+ if (propertyMap == null) {
+ propertyMap = new HashMap<String, Long>();
+ sJNISetterPropertyMap.put(targetClass, propertyMap);
+ }
+ propertyMap.put(mPropertyName, mJniSetter);
+ }
+ }
+ }
+ }
+
+ /**
+ * Convert from PointF to float[] for multi-float setters along a Path.
+ */
+ private static class PointFToFloatArray extends TypeConverter<PointF, float[]> {
+ private float[] mCoordinates = new float[2];
+
+ public PointFToFloatArray() {
+ super(PointF.class, float[].class);
+ }
+
+ @Override
+ public float[] convert(PointF value) {
+ mCoordinates[0] = value.x;
+ mCoordinates[1] = value.y;
+ return mCoordinates;
+ }
+ };
+
+ /**
+ * Convert from PointF to int[] for multi-int setters along a Path.
+ */
+ private static class PointFToIntArray extends TypeConverter<PointF, int[]> {
+ private int[] mCoordinates = new int[2];
+
+ public PointFToIntArray() {
+ super(PointF.class, int[].class);
+ }
+
+ @Override
+ public int[] convert(PointF value) {
+ mCoordinates[0] = Math.round(value.x);
+ mCoordinates[1] = Math.round(value.y);
+ return mCoordinates;
+ }
+ };
+
+ /**
+ * @hide
+ */
+ public static class PropertyValues {
+ public String propertyName;
+ public Class type;
+ public Object startValue;
+ public Object endValue;
+ public DataSource dataSource = null;
+ public interface DataSource {
+ Object getValueAtFraction(float fraction);
+ }
+ public String toString() {
+ return ("property name: " + propertyName + ", type: " + type + ", startValue: "
+ + startValue.toString() + ", endValue: " + endValue.toString());
+ }
+ }
+
+ native static private long nGetIntMethod(Class targetClass, String methodName);
+ native static private long nGetFloatMethod(Class targetClass, String methodName);
+ native static private long nGetMultipleIntMethod(Class targetClass, String methodName,
+ int numParams);
+ native static private long nGetMultipleFloatMethod(Class targetClass, String methodName,
+ int numParams);
+ native static private void nCallIntMethod(Object target, long methodID, int arg);
+ native static private void nCallFloatMethod(Object target, long methodID, float arg);
+ native static private void nCallTwoIntMethod(Object target, long methodID, int arg1, int arg2);
+ native static private void nCallFourIntMethod(Object target, long methodID, int arg1, int arg2,
+ int arg3, int arg4);
+ native static private void nCallMultipleIntMethod(Object target, long methodID, int[] args);
+ native static private void nCallTwoFloatMethod(Object target, long methodID, float arg1,
+ float arg2);
+ native static private void nCallFourFloatMethod(Object target, long methodID, float arg1,
+ float arg2, float arg3, float arg4);
+ native static private void nCallMultipleFloatMethod(Object target, long methodID, float[] args);
+}
diff --git a/android-35/android/animation/RectEvaluator.java b/android-35/android/animation/RectEvaluator.java
new file mode 100644
index 0000000..23eb766
--- /dev/null
+++ b/android-35/android/animation/RectEvaluator.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2013 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.animation;
+
+import android.graphics.Rect;
+
+/**
+ * This evaluator can be used to perform type interpolation between <code>Rect</code> values.
+ */
+public class RectEvaluator implements TypeEvaluator<Rect> {
+
+ /**
+ * When null, a new Rect is returned on every evaluate call. When non-null,
+ * mRect will be modified and returned on every evaluate.
+ */
+ private Rect mRect;
+
+ /**
+ * Construct a RectEvaluator that returns a new Rect on every evaluate call.
+ * To avoid creating an object for each evaluate call,
+ * {@link RectEvaluator#RectEvaluator(android.graphics.Rect)} should be used
+ * whenever possible.
+ */
+ public RectEvaluator() {
+ }
+
+ /**
+ * Constructs a RectEvaluator that modifies and returns <code>reuseRect</code>
+ * in {@link #evaluate(float, android.graphics.Rect, android.graphics.Rect)} calls.
+ * The value returned from
+ * {@link #evaluate(float, android.graphics.Rect, android.graphics.Rect)} should
+ * not be cached because it will change over time as the object is reused on each
+ * call.
+ *
+ * @param reuseRect A Rect to be modified and returned by evaluate.
+ */
+ public RectEvaluator(Rect reuseRect) {
+ mRect = reuseRect;
+ }
+
+ /**
+ * This function returns the result of linearly interpolating the start and
+ * end Rect values, with <code>fraction</code> representing the proportion
+ * between the start and end values. The calculation is a simple parametric
+ * calculation on each of the separate components in the Rect objects
+ * (left, top, right, and bottom).
+ *
+ * <p>If {@link #RectEvaluator(android.graphics.Rect)} was used to construct
+ * this RectEvaluator, the object returned will be the <code>reuseRect</code>
+ * passed into the constructor.</p>
+ *
+ * @param fraction The fraction from the starting to the ending values
+ * @param startValue The start Rect
+ * @param endValue The end Rect
+ * @return A linear interpolation between the start and end values, given the
+ * <code>fraction</code> parameter.
+ */
+ @Override
+ public Rect evaluate(float fraction, Rect startValue, Rect endValue) {
+ int left = startValue.left + (int) ((endValue.left - startValue.left) * fraction);
+ int top = startValue.top + (int) ((endValue.top - startValue.top) * fraction);
+ int right = startValue.right + (int) ((endValue.right - startValue.right) * fraction);
+ int bottom = startValue.bottom + (int) ((endValue.bottom - startValue.bottom) * fraction);
+ if (mRect == null) {
+ return new Rect(left, top, right, bottom);
+ } else {
+ mRect.set(left, top, right, bottom);
+ return mRect;
+ }
+ }
+}
diff --git a/android-35/android/animation/RevealAnimator.java b/android-35/android/animation/RevealAnimator.java
new file mode 100644
index 0000000..0f85f49
--- /dev/null
+++ b/android-35/android/animation/RevealAnimator.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2014 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.animation;
+
+import android.view.RenderNodeAnimator;
+import android.view.View;
+
+/**
+ * Reveals a View with an animated clipping circle.
+ * The clipping is implemented efficiently by talking to a private reveal API on View.
+ * This hidden class currently only accessed by the {@link android.view.View}.
+ *
+ * @hide
+ */
+public class RevealAnimator extends RenderNodeAnimator {
+
+ private View mClipView;
+
+ public RevealAnimator(View clipView, int x, int y,
+ float startRadius, float endRadius) {
+ super(x, y, startRadius, endRadius);
+ mClipView = clipView;
+ setTarget(mClipView);
+ }
+
+ @Override
+ protected void onFinished() {
+ mClipView.setRevealClip(false, 0, 0, 0);
+ super.onFinished();
+ }
+
+}
diff --git a/android-35/android/animation/StateListAnimator.java b/android-35/android/animation/StateListAnimator.java
new file mode 100644
index 0000000..b6d6910
--- /dev/null
+++ b/android-35/android/animation/StateListAnimator.java
@@ -0,0 +1,333 @@
+/*
+ * Copyright (C) 2014 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.animation;
+
+import android.content.pm.ActivityInfo.Config;
+import android.content.res.ConstantState;
+import android.util.StateSet;
+import android.view.View;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+
+/**
+ * Lets you define a number of Animators that will run on the attached View depending on the View's
+ * drawable state.
+ * <p>
+ * It can be defined in an XML file with the <code><selector></code> element.
+ * Each State Animator is defined in a nested <code><item></code> element.
+ *
+ * @attr ref android.R.styleable#DrawableStates_state_focused
+ * @attr ref android.R.styleable#DrawableStates_state_window_focused
+ * @attr ref android.R.styleable#DrawableStates_state_enabled
+ * @attr ref android.R.styleable#DrawableStates_state_checkable
+ * @attr ref android.R.styleable#DrawableStates_state_checked
+ * @attr ref android.R.styleable#DrawableStates_state_selected
+ * @attr ref android.R.styleable#DrawableStates_state_activated
+ * @attr ref android.R.styleable#DrawableStates_state_active
+ * @attr ref android.R.styleable#DrawableStates_state_single
+ * @attr ref android.R.styleable#DrawableStates_state_first
+ * @attr ref android.R.styleable#DrawableStates_state_middle
+ * @attr ref android.R.styleable#DrawableStates_state_last
+ * @attr ref android.R.styleable#DrawableStates_state_pressed
+ * @attr ref android.R.styleable#StateListAnimatorItem_animation
+ */
+public class StateListAnimator implements Cloneable {
+
+ private ArrayList<Tuple> mTuples = new ArrayList<Tuple>();
+ private Tuple mLastMatch = null;
+ private Animator mRunningAnimator = null;
+ private WeakReference<View> mViewRef;
+ private StateListAnimatorConstantState mConstantState;
+ private AnimatorListenerAdapter mAnimatorListener;
+ private @Config int mChangingConfigurations;
+
+ public StateListAnimator() {
+ initAnimatorListener();
+ }
+
+ private void initAnimatorListener() {
+ mAnimatorListener = new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ animation.setTarget(null);
+ if (mRunningAnimator == animation) {
+ mRunningAnimator = null;
+ }
+ }
+ };
+ }
+
+ /**
+ * Associates the given animator with the provided drawable state specs so that it will be run
+ * when the View's drawable state matches the specs.
+ *
+ * @param specs The drawable state specs to match against
+ * @param animator The animator to run when the specs match
+ */
+ public void addState(int[] specs, Animator animator) {
+ Tuple tuple = new Tuple(specs, animator);
+ tuple.mAnimator.addListener(mAnimatorListener);
+ mTuples.add(tuple);
+ mChangingConfigurations |= animator.getChangingConfigurations();
+ }
+
+ /**
+ * Returns the current {@link android.animation.Animator} which is started because of a state
+ * change.
+ *
+ * @return The currently running Animator or null if no Animator is running
+ * @hide
+ */
+ public Animator getRunningAnimator() {
+ return mRunningAnimator;
+ }
+
+ /**
+ * @hide
+ */
+ public View getTarget() {
+ return mViewRef == null ? null : mViewRef.get();
+ }
+
+ /**
+ * Called by View
+ * @hide
+ */
+ public void setTarget(View view) {
+ final View current = getTarget();
+ if (current == view) {
+ return;
+ }
+ if (current != null) {
+ clearTarget();
+ }
+ if (view != null) {
+ mViewRef = new WeakReference<View>(view);
+ }
+
+ }
+
+ private void clearTarget() {
+ final int size = mTuples.size();
+ for (int i = 0; i < size; i++) {
+ mTuples.get(i).mAnimator.setTarget(null);
+ }
+ mViewRef = null;
+ mLastMatch = null;
+ mRunningAnimator = null;
+ }
+
+ @Override
+ public StateListAnimator clone() {
+ try {
+ StateListAnimator clone = (StateListAnimator) super.clone();
+ clone.mTuples = new ArrayList<Tuple>(mTuples.size());
+ clone.mLastMatch = null;
+ clone.mRunningAnimator = null;
+ clone.mViewRef = null;
+ clone.mAnimatorListener = null;
+ clone.initAnimatorListener();
+ final int tupleSize = mTuples.size();
+ for (int i = 0; i < tupleSize; i++) {
+ final Tuple tuple = mTuples.get(i);
+ final Animator animatorClone = tuple.mAnimator.clone();
+ animatorClone.removeListener(mAnimatorListener);
+ clone.addState(tuple.mSpecs, animatorClone);
+ }
+ clone.setChangingConfigurations(getChangingConfigurations());
+ return clone;
+ } catch (CloneNotSupportedException e) {
+ throw new AssertionError("cannot clone state list animator", e);
+ }
+ }
+
+ /**
+ * Called by View
+ * @hide
+ */
+ public void setState(int[] state) {
+ Tuple match = null;
+ final int count = mTuples.size();
+ for (int i = 0; i < count; i++) {
+ final Tuple tuple = mTuples.get(i);
+ if (StateSet.stateSetMatches(tuple.mSpecs, state)) {
+ match = tuple;
+ break;
+ }
+ }
+ if (match == mLastMatch) {
+ return;
+ }
+ if (mLastMatch != null) {
+ cancel();
+ }
+ mLastMatch = match;
+ if (match != null) {
+ start(match);
+ }
+ }
+
+ private void start(Tuple match) {
+ match.mAnimator.setTarget(getTarget());
+ mRunningAnimator = match.mAnimator;
+ mRunningAnimator.start();
+ }
+
+ private void cancel() {
+ if (mRunningAnimator != null) {
+ mRunningAnimator.cancel();
+ mRunningAnimator = null;
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public ArrayList<Tuple> getTuples() {
+ return mTuples;
+ }
+
+ /**
+ * If there is an animation running for a recent state change, ends it.
+ * <p>
+ * This causes the animation to assign the end value(s) to the View.
+ */
+ public void jumpToCurrentState() {
+ if (mRunningAnimator != null) {
+ mRunningAnimator.end();
+ }
+ }
+
+ /**
+ * Return a mask of the configuration parameters for which this animator may change, requiring
+ * that it be re-created. The default implementation returns whatever was provided through
+ * {@link #setChangingConfigurations(int)} or 0 by default.
+ *
+ * @return Returns a mask of the changing configuration parameters, as defined by
+ * {@link android.content.pm.ActivityInfo}.
+ *
+ * @see android.content.pm.ActivityInfo
+ * @hide
+ */
+ public @Config int getChangingConfigurations() {
+ return mChangingConfigurations;
+ }
+
+ /**
+ * Set a mask of the configuration parameters for which this animator may change, requiring
+ * that it should be recreated from resources instead of being cloned.
+ *
+ * @param configs A mask of the changing configuration parameters, as
+ * defined by {@link android.content.pm.ActivityInfo}.
+ *
+ * @see android.content.pm.ActivityInfo
+ * @hide
+ */
+ public void setChangingConfigurations(@Config int configs) {
+ mChangingConfigurations = configs;
+ }
+
+ /**
+ * Sets the changing configurations value to the union of the current changing configurations
+ * and the provided configs.
+ * This method is called while loading the animator.
+ * @hide
+ */
+ public void appendChangingConfigurations(@Config int configs) {
+ mChangingConfigurations |= configs;
+ }
+
+ /**
+ * Return a {@link android.content.res.ConstantState} instance that holds the shared state of
+ * this Animator.
+ * <p>
+ * This constant state is used to create new instances of this animator when needed. Default
+ * implementation creates a new {@link StateListAnimatorConstantState}. You can override this
+ * method to provide your custom logic or return null if you don't want this animator to be
+ * cached.
+ *
+ * @return The {@link android.content.res.ConstantState} associated to this Animator.
+ * @see android.content.res.ConstantState
+ * @see #clone()
+ * @hide
+ */
+ public ConstantState<StateListAnimator> createConstantState() {
+ return new StateListAnimatorConstantState(this);
+ }
+
+ /**
+ * @hide
+ */
+ public static class Tuple {
+
+ final int[] mSpecs;
+
+ final Animator mAnimator;
+
+ private Tuple(int[] specs, Animator animator) {
+ mSpecs = specs;
+ mAnimator = animator;
+ }
+
+ /**
+ * @hide
+ */
+ public int[] getSpecs() {
+ return mSpecs;
+ }
+
+ /**
+ * @hide
+ */
+ public Animator getAnimator() {
+ return mAnimator;
+ }
+ }
+
+ /**
+ * Creates a constant state which holds changing configurations information associated with the
+ * given Animator.
+ * <p>
+ * When new instance is called, default implementation clones the Animator.
+ */
+ private static class StateListAnimatorConstantState
+ extends ConstantState<StateListAnimator> {
+
+ final StateListAnimator mAnimator;
+
+ @Config int mChangingConf;
+
+ public StateListAnimatorConstantState(StateListAnimator animator) {
+ mAnimator = animator;
+ mAnimator.mConstantState = this;
+ mChangingConf = mAnimator.getChangingConfigurations();
+ }
+
+ @Override
+ public @Config int getChangingConfigurations() {
+ return mChangingConf;
+ }
+
+ @Override
+ public StateListAnimator newInstance() {
+ final StateListAnimator clone = mAnimator.clone();
+ clone.mConstantState = this;
+ return clone;
+ }
+ }
+}
diff --git a/android-35/android/animation/TimeAnimator.java b/android-35/android/animation/TimeAnimator.java
new file mode 100644
index 0000000..113a21f
--- /dev/null
+++ b/android-35/android/animation/TimeAnimator.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2010 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.animation;
+
+import android.view.animation.AnimationUtils;
+
+/**
+ * This class provides a simple callback mechanism to listeners that is synchronized with all
+ * other animators in the system. There is no duration, interpolation, or object value-setting
+ * with this Animator. Instead, it is simply started, after which it proceeds to send out events
+ * on every animation frame to its TimeListener (if set), with information about this animator,
+ * the total elapsed time, and the elapsed time since the previous animation frame.
+ */
+public class TimeAnimator extends ValueAnimator {
+
+ private TimeListener mListener;
+ private long mPreviousTime = -1;
+
+ @Override
+ public void start() {
+ mPreviousTime = -1;
+ super.start();
+ }
+
+ @Override
+ boolean animateBasedOnTime(long currentTime) {
+ if (mListener != null) {
+ long totalTime = currentTime - mStartTime;
+ long deltaTime = (mPreviousTime < 0) ? 0 : (currentTime - mPreviousTime);
+ mPreviousTime = currentTime;
+ mListener.onTimeUpdate(this, totalTime, deltaTime);
+ }
+ return false;
+ }
+
+ @Override
+ public void setCurrentPlayTime(long playTime) {
+ long currentTime = AnimationUtils.currentAnimationTimeMillis();
+ mStartTime = Math.max(mStartTime, currentTime - playTime);
+ mStartTimeCommitted = true; // do not allow start time to be compensated for jank
+ animateBasedOnTime(currentTime);
+ }
+
+ /**
+ * Sets a listener that is sent update events throughout the life of
+ * an animation.
+ *
+ * @param listener the listener to be set.
+ */
+ public void setTimeListener(TimeListener listener) {
+ mListener = listener;
+ }
+
+ @Override
+ void animateValue(float fraction) {
+ // Noop
+ }
+
+ @Override
+ void initAnimation() {
+ // noop
+ }
+
+ /**
+ * Implementors of this interface can set themselves as update listeners
+ * to a <code>TimeAnimator</code> instance to receive callbacks on every animation
+ * frame to receive the total time since the animator started and the delta time
+ * since the last frame. The first time the listener is called,
+ * deltaTime will be zero. The same is true for totalTime, unless the animator was
+ * set to a specific {@link ValueAnimator#setCurrentPlayTime(long) currentPlayTime}
+ * prior to starting.
+ */
+ public static interface TimeListener {
+ /**
+ * <p>Notifies listeners of the occurrence of another frame of the animation,
+ * along with information about the elapsed time.</p>
+ *
+ * @param animation The animator sending out the notification.
+ * @param totalTime The total time elapsed since the animator started, in milliseconds.
+ * @param deltaTime The time elapsed since the previous frame, in milliseconds.
+ */
+ void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime);
+
+ }
+}
diff --git a/android-35/android/animation/TimeInterpolator.java b/android-35/android/animation/TimeInterpolator.java
new file mode 100644
index 0000000..0f5d8bf
--- /dev/null
+++ b/android-35/android/animation/TimeInterpolator.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2010 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.animation;
+
+/**
+ * A time interpolator defines the rate of change of an animation. This allows animations
+ * to have non-linear motion, such as acceleration and deceleration.
+ */
+public interface TimeInterpolator {
+
+ /**
+ * Maps a value representing the elapsed fraction of an animation to a value that represents
+ * the interpolated fraction. This interpolated value is then multiplied by the change in
+ * value of an animation to derive the animated value at the current elapsed animation time.
+ *
+ * @param input A value between 0 and 1.0 indicating our current point
+ * in the animation where 0 represents the start and 1.0 represents
+ * the end
+ * @return The interpolation value. This value can be more than 1.0 for
+ * interpolators which overshoot their targets, or less than 0 for
+ * interpolators that undershoot their targets.
+ */
+ float getInterpolation(float input);
+}
diff --git a/android-35/android/animation/TypeConverter.java b/android-35/android/animation/TypeConverter.java
new file mode 100644
index 0000000..9ead2ad
--- /dev/null
+++ b/android-35/android/animation/TypeConverter.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2013 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.animation;
+
+/**
+ * Abstract base class used convert type T to another type V. This
+ * is necessary when the value types of in animation are different
+ * from the property type.
+ * @see PropertyValuesHolder#setConverter(TypeConverter)
+ */
+public abstract class TypeConverter<T, V> {
+ private Class<T> mFromClass;
+ private Class<V> mToClass;
+
+ public TypeConverter(Class<T> fromClass, Class<V> toClass) {
+ mFromClass = fromClass;
+ mToClass = toClass;
+ }
+
+ /**
+ * Returns the target converted type. Used by the animation system to determine
+ * the proper setter function to call.
+ * @return The Class to convert the input to.
+ */
+ Class<V> getTargetType() {
+ return mToClass;
+ }
+
+ /**
+ * Returns the source conversion type.
+ */
+ Class<T> getSourceType() {
+ return mFromClass;
+ }
+
+ /**
+ * Converts a value from one type to another.
+ * @param value The Object to convert.
+ * @return A value of type V, converted from <code>value</code>.
+ */
+ public abstract V convert(T value);
+}
diff --git a/android-35/android/animation/TypeEvaluator.java b/android-35/android/animation/TypeEvaluator.java
new file mode 100644
index 0000000..429c435
--- /dev/null
+++ b/android-35/android/animation/TypeEvaluator.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2010 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.animation;
+
+/**
+ * Interface for use with the {@link ValueAnimator#setEvaluator(TypeEvaluator)} function. Evaluators
+ * allow developers to create animations on arbitrary property types, by allowing them to supply
+ * custom evaluators for types that are not automatically understood and used by the animation
+ * system.
+ *
+ * @see ValueAnimator#setEvaluator(TypeEvaluator)
+ */
+public interface TypeEvaluator<T> {
+
+ /**
+ * This function returns the result of linearly interpolating the start and end values, with
+ * <code>fraction</code> representing the proportion between the start and end values. The
+ * calculation is a simple parametric calculation: <code>result = x0 + t * (x1 - x0)</code>,
+ * where <code>x0</code> is <code>startValue</code>, <code>x1</code> is <code>endValue</code>,
+ * and <code>t</code> is <code>fraction</code>.
+ *
+ * @param fraction The fraction from the starting to the ending values
+ * @param startValue The start value.
+ * @param endValue The end value.
+ * @return A linear interpolation between the start and end values, given the
+ * <code>fraction</code> parameter.
+ */
+ public T evaluate(float fraction, T startValue, T endValue);
+
+}
diff --git a/android-35/android/animation/ValueAnimator.java b/android-35/android/animation/ValueAnimator.java
new file mode 100644
index 0000000..5de7f38
--- /dev/null
+++ b/android-35/android/animation/ValueAnimator.java
@@ -0,0 +1,1795 @@
+/*
+ * Copyright (C) 2010 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.animation;
+
+import android.annotation.CallSuper;
+import android.annotation.FloatRange;
+import android.annotation.IntDef;
+import android.annotation.MainThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Build;
+import android.os.Looper;
+import android.os.SystemProperties;
+import android.os.Trace;
+import android.util.AndroidRuntimeException;
+import android.util.Log;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.view.animation.LinearInterpolator;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * This class provides a simple timing engine for running animations
+ * which calculate animated values and set them on target objects.
+ *
+ * <p>There is a single timing pulse that all animations use. It runs in a
+ * custom handler to ensure that property changes happen on the UI thread.</p>
+ *
+ * <p>By default, ValueAnimator uses non-linear time interpolation, via the
+ * {@link AccelerateDecelerateInterpolator} class, which accelerates into and decelerates
+ * out of an animation. This behavior can be changed by calling
+ * {@link ValueAnimator#setInterpolator(TimeInterpolator)}.</p>
+ *
+ * <p>Animators can be created from either code or resource files. Here is an example
+ * of a ValueAnimator resource file:</p>
+ *
+ * {@sample development/samples/ApiDemos/res/anim/animator.xml ValueAnimatorResources}
+ *
+ * <p>Starting from API 23, it is also possible to use a combination of {@link PropertyValuesHolder}
+ * and {@link Keyframe} resource tags to create a multi-step animation.
+ * Note that you can specify explicit fractional values (from 0 to 1) for
+ * each keyframe to determine when, in the overall duration, the animation should arrive at that
+ * value. Alternatively, you can leave the fractions off and the keyframes will be equally
+ * distributed within the total duration:</p>
+ *
+ * {@sample development/samples/ApiDemos/res/anim/value_animator_pvh_kf.xml
+ * ValueAnimatorKeyframeResources}
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For more information about animating with {@code ValueAnimator}, read the
+ * <a href="{@docRoot}guide/topics/graphics/prop-animation.html#value-animator">Property
+ * Animation</a> developer guide.</p>
+ * </div>
+ */
+@SuppressWarnings("unchecked")
+public class ValueAnimator extends Animator implements AnimationHandler.AnimationFrameCallback {
+ private static final String TAG = "ValueAnimator";
+ private static final boolean DEBUG = false;
+ private static final boolean TRACE_ANIMATION_FRACTION = SystemProperties.getBoolean(
+ "persist.debug.animator.trace_fraction", false);
+
+ /**
+ * Internal constants
+ */
+
+ /**
+ * System-wide animation scale.
+ *
+ * <p>To check whether animations are enabled system-wise use {@link #areAnimatorsEnabled()}.
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+ private static float sDurationScale = 1.0f;
+
+ private static final ArrayList<WeakReference<DurationScaleChangeListener>>
+ sDurationScaleChangeListeners = new ArrayList<>();
+
+ /**
+ * Internal variables
+ * NOTE: This object implements the clone() method, making a deep copy of any referenced
+ * objects. As other non-trivial fields are added to this class, make sure to add logic
+ * to clone() to make deep copies of them.
+ */
+
+ /**
+ * The first time that the animation's animateFrame() method is called. This time is used to
+ * determine elapsed time (and therefore the elapsed fraction) in subsequent calls
+ * to animateFrame().
+ *
+ * Whenever mStartTime is set, you must also update mStartTimeCommitted.
+ */
+ long mStartTime = -1;
+
+ /**
+ * When true, the start time has been firmly committed as a chosen reference point in
+ * time by which the progress of the animation will be evaluated. When false, the
+ * start time may be updated when the first animation frame is committed so as
+ * to compensate for jank that may have occurred between when the start time was
+ * initialized and when the frame was actually drawn.
+ *
+ * This flag is generally set to false during the first frame of the animation
+ * when the animation playing state transitions from STOPPED to RUNNING or
+ * resumes after having been paused. This flag is set to true when the start time
+ * is firmly committed and should not be further compensated for jank.
+ */
+ boolean mStartTimeCommitted;
+
+ /**
+ * Set when setCurrentPlayTime() is called. If negative, animation is not currently seeked
+ * to a value.
+ */
+ float mSeekFraction = -1;
+
+ /**
+ * Set on the next frame after pause() is called, used to calculate a new startTime
+ * or delayStartTime which allows the animator to continue from the point at which
+ * it was paused. If negative, has not yet been set.
+ */
+ private long mPauseTime;
+
+ /**
+ * Set when an animator is resumed. This triggers logic in the next frame which
+ * actually resumes the animator.
+ */
+ private boolean mResumed = false;
+
+ // The time interpolator to be used if none is set on the animation
+ private static final TimeInterpolator sDefaultInterpolator =
+ new AccelerateDecelerateInterpolator();
+
+ /**
+ * Flag to indicate whether this animator is playing in reverse mode, specifically
+ * by being started or interrupted by a call to reverse(). This flag is different than
+ * mPlayingBackwards, which indicates merely whether the current iteration of the
+ * animator is playing in reverse. It is used in corner cases to determine proper end
+ * behavior.
+ */
+ private boolean mReversing;
+
+ /**
+ * Tracks the overall fraction of the animation, ranging from 0 to mRepeatCount + 1
+ */
+ private float mOverallFraction = 0f;
+
+ /**
+ * Tracks current elapsed/eased fraction, for querying in getAnimatedFraction().
+ * This is calculated by interpolating the fraction (range: [0, 1]) in the current iteration.
+ */
+ private float mCurrentFraction = 0f;
+
+ /**
+ * Tracks the time (in milliseconds) when the last frame arrived.
+ */
+ private long mLastFrameTime = -1;
+
+ /**
+ * Tracks the time (in milliseconds) when the first frame arrived. Note the frame may arrive
+ * during the start delay.
+ */
+ private long mFirstFrameTime = -1;
+
+ /**
+ * Additional playing state to indicate whether an animator has been start()'d. There is
+ * some lag between a call to start() and the first animation frame. We should still note
+ * that the animation has been started, even if it's first animation frame has not yet
+ * happened, and reflect that state in isRunning().
+ * Note that delayed animations are different: they are not started until their first
+ * animation frame, which occurs after their delay elapses.
+ */
+ private boolean mRunning = false;
+
+ /**
+ * Additional playing state to indicate whether an animator has been start()'d, whether or
+ * not there is a nonzero startDelay.
+ */
+ private boolean mStarted = false;
+
+ /**
+ * Flag that denotes whether the animation is set up and ready to go. Used to
+ * set up animation that has not yet been started.
+ */
+ boolean mInitialized = false;
+
+ /**
+ * Flag that tracks whether animation has been requested to end.
+ */
+ private boolean mAnimationEndRequested = false;
+
+ //
+ // Backing variables
+ //
+
+ // How long the animation should last in ms
+ @UnsupportedAppUsage
+ private long mDuration = 300;
+
+ // The amount of time in ms to delay starting the animation after start() is called. Note
+ // that this start delay is unscaled. When there is a duration scale set on the animator, the
+ // scaling factor will be applied to this delay.
+ private long mStartDelay = 0;
+
+ // The number of times the animation will repeat. The default is 0, which means the animation
+ // will play only once
+ private int mRepeatCount = 0;
+
+ /**
+ * The type of repetition that will occur when repeatMode is nonzero. RESTART means the
+ * animation will start from the beginning on every new cycle. REVERSE means the animation
+ * will reverse directions on each iteration.
+ */
+ private int mRepeatMode = RESTART;
+
+ /**
+ * Whether or not the animator should register for its own animation callback to receive
+ * animation pulse.
+ */
+ private boolean mSelfPulse = true;
+
+ /**
+ * Whether or not the animator has been requested to start without pulsing. This flag gets set
+ * in startWithoutPulsing(), and reset in start().
+ */
+ private boolean mSuppressSelfPulseRequested = false;
+
+ /**
+ * The time interpolator to be used. The elapsed fraction of the animation will be passed
+ * through this interpolator to calculate the interpolated fraction, which is then used to
+ * calculate the animated values.
+ */
+ private TimeInterpolator mInterpolator = sDefaultInterpolator;
+
+ /**
+ * The set of listeners to be sent events through the life of an animation.
+ */
+ ArrayList<AnimatorUpdateListener> mUpdateListeners = null;
+
+ /**
+ * The property/value sets being animated.
+ */
+ PropertyValuesHolder[] mValues;
+
+ /**
+ * A hashmap of the PropertyValuesHolder objects. This map is used to lookup animated values
+ * by property name during calls to getAnimatedValue(String).
+ */
+ HashMap<String, PropertyValuesHolder> mValuesMap;
+
+ /**
+ * If set to non-negative value, this will override {@link #sDurationScale}.
+ */
+ private float mDurationScale = -1f;
+
+ /**
+ * Animation handler used to schedule updates for this animation.
+ */
+ private AnimationHandler mAnimationHandler;
+
+ /**
+ * Public constants
+ */
+
+ /** @hide */
+ @IntDef({RESTART, REVERSE})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface RepeatMode {}
+
+ /**
+ * When the animation reaches the end and <code>repeatCount</code> is INFINITE
+ * or a positive value, the animation restarts from the beginning.
+ */
+ public static final int RESTART = 1;
+ /**
+ * When the animation reaches the end and <code>repeatCount</code> is INFINITE
+ * or a positive value, the animation reverses direction on every iteration.
+ */
+ public static final int REVERSE = 2;
+ /**
+ * This value used used with the {@link #setRepeatCount(int)} property to repeat
+ * the animation indefinitely.
+ */
+ public static final int INFINITE = -1;
+
+ /**
+ * @hide
+ */
+ @UnsupportedAppUsage
+ @TestApi
+ @MainThread
+ public static void setDurationScale(@FloatRange(from = 0) float durationScale) {
+ sDurationScale = durationScale;
+ List<WeakReference<DurationScaleChangeListener>> listenerCopy;
+
+ synchronized (sDurationScaleChangeListeners) {
+ listenerCopy = new ArrayList<>(sDurationScaleChangeListeners);
+ }
+
+ int listenersSize = listenerCopy.size();
+ for (int i = 0; i < listenersSize; i++) {
+ final DurationScaleChangeListener listener = listenerCopy.get(i).get();
+ if (listener != null) {
+ listener.onChanged(durationScale);
+ }
+ }
+ }
+
+ /**
+ * Returns the system-wide scaling factor for Animator-based animations.
+ *
+ * This affects both the start delay and duration of all such animations. Setting to 0 will
+ * cause animations to end immediately. The default value is 1.0f.
+ *
+ * @return the duration scale.
+ */
+ @FloatRange(from = 0)
+ public static float getDurationScale() {
+ return sDurationScale;
+ }
+
+ /**
+ * Registers a {@link DurationScaleChangeListener}
+ *
+ * This listens for changes to the system-wide scaling factor for Animator-based animations.
+ * Listeners will be called on the main thread.
+ *
+ * @param listener the listener to register.
+ * @return true if the listener was registered.
+ */
+ public static boolean registerDurationScaleChangeListener(
+ @NonNull DurationScaleChangeListener listener) {
+ int posToReplace = -1;
+ synchronized (sDurationScaleChangeListeners) {
+ for (int i = 0; i < sDurationScaleChangeListeners.size(); i++) {
+ final WeakReference<DurationScaleChangeListener> ref =
+ sDurationScaleChangeListeners.get(i);
+ if (ref.get() == null) {
+ if (posToReplace == -1) {
+ posToReplace = i;
+ }
+ } else if (ref.get() == listener) {
+ return false;
+ }
+ }
+ if (posToReplace != -1) {
+ sDurationScaleChangeListeners.set(posToReplace, new WeakReference<>(listener));
+ return true;
+ } else {
+ return sDurationScaleChangeListeners.add(new WeakReference<>(listener));
+ }
+ }
+ }
+
+ /**
+ * Unregisters a DurationScaleChangeListener.
+ *
+ * @see #registerDurationScaleChangeListener(DurationScaleChangeListener)
+ * @param listener the listener to unregister.
+ * @return true if the listener was unregistered.
+ */
+ public static boolean unregisterDurationScaleChangeListener(
+ @NonNull DurationScaleChangeListener listener) {
+ synchronized (sDurationScaleChangeListeners) {
+ WeakReference<DurationScaleChangeListener> listenerRefToRemove = null;
+ for (WeakReference<DurationScaleChangeListener> listenerRef :
+ sDurationScaleChangeListeners) {
+ if (listenerRef.get() == listener) {
+ listenerRefToRemove = listenerRef;
+ break;
+ }
+ }
+ return sDurationScaleChangeListeners.remove(listenerRefToRemove);
+ }
+ }
+
+ /**
+ * Returns whether animators are currently enabled, system-wide. By default, all
+ * animators are enabled. This can change if either the user sets a Developer Option
+ * to set the animator duration scale to 0 or by Battery Savery mode being enabled
+ * (which disables all animations).
+ *
+ * <p>Developers should not typically need to call this method, but should an app wish
+ * to show a different experience when animators are disabled, this return value
+ * can be used as a decider of which experience to offer.
+ *
+ * @return boolean Whether animators are currently enabled. The default value is
+ * <code>true</code>.
+ */
+ public static boolean areAnimatorsEnabled() {
+ return !(sDurationScale == 0);
+ }
+
+ /**
+ * Creates a new ValueAnimator object. This default constructor is primarily for
+ * use internally; the factory methods which take parameters are more generally
+ * useful.
+ */
+ public ValueAnimator() {
+ }
+
+ /**
+ * Constructs and returns a ValueAnimator that animates between int values. A single
+ * value implies that that value is the one being animated to. However, this is not typically
+ * useful in a ValueAnimator object because there is no way for the object to determine the
+ * starting value for the animation (unlike ObjectAnimator, which can derive that value
+ * from the target object and property being animated). Therefore, there should typically
+ * be two or more values.
+ *
+ * @param values A set of values that the animation will animate between over time.
+ * @return A ValueAnimator object that is set up to animate between the given values.
+ */
+ public static ValueAnimator ofInt(int... values) {
+ ValueAnimator anim = new ValueAnimator();
+ anim.setIntValues(values);
+ return anim;
+ }
+
+ /**
+ * Constructs and returns a ValueAnimator that animates between color values. A single
+ * value implies that that value is the one being animated to. However, this is not typically
+ * useful in a ValueAnimator object because there is no way for the object to determine the
+ * starting value for the animation (unlike ObjectAnimator, which can derive that value
+ * from the target object and property being animated). Therefore, there should typically
+ * be two or more values.
+ *
+ * @param values A set of values that the animation will animate between over time.
+ * @return A ValueAnimator object that is set up to animate between the given values.
+ */
+ public static ValueAnimator ofArgb(int... values) {
+ ValueAnimator anim = new ValueAnimator();
+ anim.setIntValues(values);
+ anim.setEvaluator(ArgbEvaluator.getInstance());
+ return anim;
+ }
+
+ /**
+ * Constructs and returns a ValueAnimator that animates between float values. A single
+ * value implies that that value is the one being animated to. However, this is not typically
+ * useful in a ValueAnimator object because there is no way for the object to determine the
+ * starting value for the animation (unlike ObjectAnimator, which can derive that value
+ * from the target object and property being animated). Therefore, there should typically
+ * be two or more values.
+ *
+ * @param values A set of values that the animation will animate between over time.
+ * @return A ValueAnimator object that is set up to animate between the given values.
+ */
+ public static ValueAnimator ofFloat(float... values) {
+ ValueAnimator anim = new ValueAnimator();
+ anim.setFloatValues(values);
+ return anim;
+ }
+
+ /**
+ * Constructs and returns a ValueAnimator that animates between the values
+ * specified in the PropertyValuesHolder objects.
+ *
+ * @param values A set of PropertyValuesHolder objects whose values will be animated
+ * between over time.
+ * @return A ValueAnimator object that is set up to animate between the given values.
+ */
+ public static ValueAnimator ofPropertyValuesHolder(PropertyValuesHolder... values) {
+ ValueAnimator anim = new ValueAnimator();
+ anim.setValues(values);
+ return anim;
+ }
+ /**
+ * Constructs and returns a ValueAnimator that animates between Object values. A single
+ * value implies that that value is the one being animated to. However, this is not typically
+ * useful in a ValueAnimator object because there is no way for the object to determine the
+ * starting value for the animation (unlike ObjectAnimator, which can derive that value
+ * from the target object and property being animated). Therefore, there should typically
+ * be two or more values.
+ *
+ * <p><strong>Note:</strong> The Object values are stored as references to the original
+ * objects, which means that changes to those objects after this method is called will
+ * affect the values on the animator. If the objects will be mutated externally after
+ * this method is called, callers should pass a copy of those objects instead.
+ *
+ * <p>Since ValueAnimator does not know how to animate between arbitrary Objects, this
+ * factory method also takes a TypeEvaluator object that the ValueAnimator will use
+ * to perform that interpolation.
+ *
+ * @param evaluator A TypeEvaluator that will be called on each animation frame to
+ * provide the ncessry interpolation between the Object values to derive the animated
+ * value.
+ * @param values A set of values that the animation will animate between over time.
+ * @return A ValueAnimator object that is set up to animate between the given values.
+ */
+ public static ValueAnimator ofObject(TypeEvaluator evaluator, Object... values) {
+ ValueAnimator anim = new ValueAnimator();
+ anim.setObjectValues(values);
+ anim.setEvaluator(evaluator);
+ return anim;
+ }
+
+ /**
+ * Sets int values that will be animated between. A single
+ * value implies that that value is the one being animated to. However, this is not typically
+ * useful in a ValueAnimator object because there is no way for the object to determine the
+ * starting value for the animation (unlike ObjectAnimator, which can derive that value
+ * from the target object and property being animated). Therefore, there should typically
+ * be two or more values.
+ *
+ * <p>If there are already multiple sets of values defined for this ValueAnimator via more
+ * than one PropertyValuesHolder object, this method will set the values for the first
+ * of those objects.</p>
+ *
+ * @param values A set of values that the animation will animate between over time.
+ */
+ public void setIntValues(int... values) {
+ if (values == null || values.length == 0) {
+ return;
+ }
+ if (mValues == null || mValues.length == 0) {
+ setValues(PropertyValuesHolder.ofInt("", values));
+ } else {
+ PropertyValuesHolder valuesHolder = mValues[0];
+ valuesHolder.setIntValues(values);
+ }
+ // New property/values/target should cause re-initialization prior to starting
+ mInitialized = false;
+ }
+
+ /**
+ * Sets float values that will be animated between. A single
+ * value implies that that value is the one being animated to. However, this is not typically
+ * useful in a ValueAnimator object because there is no way for the object to determine the
+ * starting value for the animation (unlike ObjectAnimator, which can derive that value
+ * from the target object and property being animated). Therefore, there should typically
+ * be two or more values.
+ *
+ * <p>If there are already multiple sets of values defined for this ValueAnimator via more
+ * than one PropertyValuesHolder object, this method will set the values for the first
+ * of those objects.</p>
+ *
+ * @param values A set of values that the animation will animate between over time.
+ */
+ public void setFloatValues(float... values) {
+ if (values == null || values.length == 0) {
+ return;
+ }
+ if (mValues == null || mValues.length == 0) {
+ setValues(PropertyValuesHolder.ofFloat("", values));
+ } else {
+ PropertyValuesHolder valuesHolder = mValues[0];
+ valuesHolder.setFloatValues(values);
+ }
+ // New property/values/target should cause re-initialization prior to starting
+ mInitialized = false;
+ }
+
+ /**
+ * Sets the values to animate between for this animation. A single
+ * value implies that that value is the one being animated to. However, this is not typically
+ * useful in a ValueAnimator object because there is no way for the object to determine the
+ * starting value for the animation (unlike ObjectAnimator, which can derive that value
+ * from the target object and property being animated). Therefore, there should typically
+ * be two or more values.
+ *
+ * <p><strong>Note:</strong> The Object values are stored as references to the original
+ * objects, which means that changes to those objects after this method is called will
+ * affect the values on the animator. If the objects will be mutated externally after
+ * this method is called, callers should pass a copy of those objects instead.
+ *
+ * <p>If there are already multiple sets of values defined for this ValueAnimator via more
+ * than one PropertyValuesHolder object, this method will set the values for the first
+ * of those objects.</p>
+ *
+ * <p>There should be a TypeEvaluator set on the ValueAnimator that knows how to interpolate
+ * between these value objects. ValueAnimator only knows how to interpolate between the
+ * primitive types specified in the other setValues() methods.</p>
+ *
+ * @param values The set of values to animate between.
+ */
+ public void setObjectValues(Object... values) {
+ if (values == null || values.length == 0) {
+ return;
+ }
+ if (mValues == null || mValues.length == 0) {
+ setValues(PropertyValuesHolder.ofObject("", null, values));
+ } else {
+ PropertyValuesHolder valuesHolder = mValues[0];
+ valuesHolder.setObjectValues(values);
+ }
+ // New property/values/target should cause re-initialization prior to starting
+ mInitialized = false;
+ }
+
+ /**
+ * Sets the values, per property, being animated between. This function is called internally
+ * by the constructors of ValueAnimator that take a list of values. But a ValueAnimator can
+ * be constructed without values and this method can be called to set the values manually
+ * instead.
+ *
+ * @param values The set of values, per property, being animated between.
+ */
+ public void setValues(PropertyValuesHolder... values) {
+ int numValues = values.length;
+ mValues = values;
+ mValuesMap = new HashMap<>(numValues);
+ for (int i = 0; i < numValues; ++i) {
+ PropertyValuesHolder valuesHolder = values[i];
+ mValuesMap.put(valuesHolder.getPropertyName(), valuesHolder);
+ }
+ // New property/values/target should cause re-initialization prior to starting
+ mInitialized = false;
+ }
+
+ /**
+ * Returns the values that this ValueAnimator animates between. These values are stored in
+ * PropertyValuesHolder objects, even if the ValueAnimator was created with a simple list
+ * of value objects instead.
+ *
+ * @return PropertyValuesHolder[] An array of PropertyValuesHolder objects which hold the
+ * values, per property, that define the animation.
+ */
+ public PropertyValuesHolder[] getValues() {
+ return mValues;
+ }
+
+ /**
+ * This function is called immediately before processing the first animation
+ * frame of an animation. If there is a nonzero <code>startDelay</code>, the
+ * function is called after that delay ends.
+ * It takes care of the final initialization steps for the
+ * animation.
+ *
+ * <p>Overrides of this method should call the superclass method to ensure
+ * that internal mechanisms for the animation are set up correctly.</p>
+ */
+ @CallSuper
+ void initAnimation() {
+ if (!mInitialized) {
+ if (mValues != null) {
+ int numValues = mValues.length;
+ for (int i = 0; i < numValues; ++i) {
+ mValues[i].init();
+ }
+ }
+ mInitialized = true;
+ }
+ }
+
+ /**
+ * Sets the length of the animation. The default duration is 300 milliseconds.
+ *
+ * @param duration The length of the animation, in milliseconds. This value cannot
+ * be negative.
+ * @return ValueAnimator The object called with setDuration(). This return
+ * value makes it easier to compose statements together that construct and then set the
+ * duration, as in <code>ValueAnimator.ofInt(0, 10).setDuration(500).start()</code>.
+ */
+ @Override
+ public ValueAnimator setDuration(long duration) {
+ if (duration < 0) {
+ throw new IllegalArgumentException("Animators cannot have negative duration: " +
+ duration);
+ }
+ mDuration = duration;
+ return this;
+ }
+
+ /**
+ * Overrides the global duration scale by a custom value.
+ *
+ * @param durationScale The duration scale to set; or {@code -1f} to use the global duration
+ * scale.
+ * @hide
+ */
+ public void overrideDurationScale(float durationScale) {
+ mDurationScale = durationScale;
+ }
+
+ private float resolveDurationScale() {
+ return mDurationScale >= 0f ? mDurationScale : sDurationScale;
+ }
+
+ private long getScaledDuration() {
+ return (long)(mDuration * resolveDurationScale());
+ }
+
+ /**
+ * Gets the length of the animation. The default duration is 300 milliseconds.
+ *
+ * @return The length of the animation, in milliseconds.
+ */
+ @Override
+ public long getDuration() {
+ return mDuration;
+ }
+
+ @Override
+ public long getTotalDuration() {
+ if (mRepeatCount == INFINITE) {
+ return DURATION_INFINITE;
+ } else {
+ return mStartDelay + (mDuration * (mRepeatCount + 1));
+ }
+ }
+
+ /**
+ * Sets the position of the animation to the specified point in time. This time should
+ * be between 0 and the total duration of the animation, including any repetition. If
+ * the animation has not yet been started, then it will not advance forward after it is
+ * set to this time; it will simply set the time to this value and perform any appropriate
+ * actions based on that time. If the animation is already running, then setCurrentPlayTime()
+ * will set the current playing time to this value and continue playing from that point.
+ *
+ * @param playTime The time, in milliseconds, to which the animation is advanced or rewound.
+ */
+ public void setCurrentPlayTime(long playTime) {
+ float fraction = mDuration > 0 ? (float) playTime / mDuration : 1;
+ setCurrentFraction(fraction);
+ }
+
+ /**
+ * Sets the position of the animation to the specified fraction. This fraction should
+ * be between 0 and the total fraction of the animation, including any repetition. That is,
+ * a fraction of 0 will position the animation at the beginning, a value of 1 at the end,
+ * and a value of 2 at the end of a reversing animator that repeats once. If
+ * the animation has not yet been started, then it will not advance forward after it is
+ * set to this fraction; it will simply set the fraction to this value and perform any
+ * appropriate actions based on that fraction. If the animation is already running, then
+ * setCurrentFraction() will set the current fraction to this value and continue
+ * playing from that point. {@link Animator.AnimatorListener} events are not called
+ * due to changing the fraction; those events are only processed while the animation
+ * is running.
+ *
+ * @param fraction The fraction to which the animation is advanced or rewound. Values
+ * outside the range of 0 to the maximum fraction for the animator will be clamped to
+ * the correct range.
+ */
+ public void setCurrentFraction(float fraction) {
+ initAnimation();
+ fraction = clampFraction(fraction);
+ mStartTimeCommitted = true; // do not allow start time to be compensated for jank
+ if (isPulsingInternal()) {
+ long seekTime = (long) (getScaledDuration() * fraction);
+ long currentTime = AnimationUtils.currentAnimationTimeMillis();
+ // Only modify the start time when the animation is running. Seek fraction will ensure
+ // non-running animations skip to the correct start time.
+ mStartTime = currentTime - seekTime;
+ } else {
+ // If the animation loop hasn't started, or during start delay, the startTime will be
+ // adjusted once the delay has passed based on seek fraction.
+ mSeekFraction = fraction;
+ }
+ mOverallFraction = fraction;
+ final float currentIterationFraction = getCurrentIterationFraction(fraction, mReversing);
+ animateValue(currentIterationFraction);
+ }
+
+ /**
+ * Calculates current iteration based on the overall fraction. The overall fraction will be
+ * in the range of [0, mRepeatCount + 1]. Both current iteration and fraction in the current
+ * iteration can be derived from it.
+ */
+ private int getCurrentIteration(float fraction) {
+ fraction = clampFraction(fraction);
+ // If the overall fraction is a positive integer, we consider the current iteration to be
+ // complete. In other words, the fraction for the current iteration would be 1, and the
+ // current iteration would be overall fraction - 1.
+ double iteration = Math.floor(fraction);
+ if (fraction == iteration && fraction > 0) {
+ iteration--;
+ }
+ return (int) iteration;
+ }
+
+ /**
+ * Calculates the fraction of the current iteration, taking into account whether the animation
+ * should be played backwards. E.g. When the animation is played backwards in an iteration,
+ * the fraction for that iteration will go from 1f to 0f.
+ */
+ private float getCurrentIterationFraction(float fraction, boolean inReverse) {
+ fraction = clampFraction(fraction);
+ int iteration = getCurrentIteration(fraction);
+ float currentFraction = fraction - iteration;
+ return shouldPlayBackward(iteration, inReverse) ? 1f - currentFraction : currentFraction;
+ }
+
+ /**
+ * Clamps fraction into the correct range: [0, mRepeatCount + 1]. If repeat count is infinite,
+ * no upper bound will be set for the fraction.
+ *
+ * @param fraction fraction to be clamped
+ * @return fraction clamped into the range of [0, mRepeatCount + 1]
+ */
+ private float clampFraction(float fraction) {
+ if (fraction < 0) {
+ fraction = 0;
+ } else if (mRepeatCount != INFINITE) {
+ fraction = Math.min(fraction, mRepeatCount + 1);
+ }
+ return fraction;
+ }
+
+ /**
+ * Calculates the direction of animation playing (i.e. forward or backward), based on 1)
+ * whether the entire animation is being reversed, 2) repeat mode applied to the current
+ * iteration.
+ */
+ private boolean shouldPlayBackward(int iteration, boolean inReverse) {
+ if (iteration > 0 && mRepeatMode == REVERSE &&
+ (iteration < (mRepeatCount + 1) || mRepeatCount == INFINITE)) {
+ // if we were seeked to some other iteration in a reversing animator,
+ // figure out the correct direction to start playing based on the iteration
+ if (inReverse) {
+ return (iteration % 2) == 0;
+ } else {
+ return (iteration % 2) != 0;
+ }
+ } else {
+ return inReverse;
+ }
+ }
+
+ /**
+ * Gets the current position of the animation in time, which is equal to the current
+ * time minus the time that the animation started. An animation that is not yet started will
+ * return a value of zero, unless the animation has has its play time set via
+ * {@link #setCurrentPlayTime(long)} or {@link #setCurrentFraction(float)}, in which case
+ * it will return the time that was set.
+ *
+ * @return The current position in time of the animation.
+ */
+ public long getCurrentPlayTime() {
+ if (!mInitialized || (!mStarted && mSeekFraction < 0)) {
+ return 0;
+ }
+ if (mSeekFraction >= 0) {
+ return (long) (mDuration * mSeekFraction);
+ }
+ float durationScale = resolveDurationScale();
+ if (durationScale == 0f) {
+ durationScale = 1f;
+ }
+ return (long) ((AnimationUtils.currentAnimationTimeMillis() - mStartTime) / durationScale);
+ }
+
+ /**
+ * The amount of time, in milliseconds, to delay starting the animation after
+ * {@link #start()} is called.
+ *
+ * @return the number of milliseconds to delay running the animation
+ */
+ @Override
+ public long getStartDelay() {
+ return mStartDelay;
+ }
+
+ /**
+ * The amount of time, in milliseconds, to delay starting the animation after
+ * {@link #start()} is called. Note that the start delay should always be non-negative. Any
+ * negative start delay will be clamped to 0 on N and above.
+ *
+ * @param startDelay The amount of the delay, in milliseconds
+ */
+ @Override
+ public void setStartDelay(long startDelay) {
+ // Clamp start delay to non-negative range.
+ if (startDelay < 0) {
+ Log.w(TAG, "Start delay should always be non-negative");
+ startDelay = 0;
+ }
+ mStartDelay = startDelay;
+ }
+
+ /**
+ * The amount of time, in milliseconds, between each frame of the animation. This is a
+ * requested time that the animation will attempt to honor, but the actual delay between
+ * frames may be different, depending on system load and capabilities. This is a static
+ * function because the same delay will be applied to all animations, since they are all
+ * run off of a single timing loop.
+ *
+ * The frame delay may be ignored when the animation system uses an external timing
+ * source, such as the display refresh rate (vsync), to govern animations.
+ *
+ * Note that this method should be called from the same thread that {@link #start()} is
+ * called in order to check the frame delay for that animation. A runtime exception will be
+ * thrown if the calling thread does not have a Looper.
+ *
+ * @return the requested time between frames, in milliseconds
+ */
+ public static long getFrameDelay() {
+ return AnimationHandler.getInstance().getFrameDelay();
+ }
+
+ /**
+ * The amount of time, in milliseconds, between each frame of the animation. This is a
+ * requested time that the animation will attempt to honor, but the actual delay between
+ * frames may be different, depending on system load and capabilities. This is a static
+ * function because the same delay will be applied to all animations, since they are all
+ * run off of a single timing loop.
+ *
+ * The frame delay may be ignored when the animation system uses an external timing
+ * source, such as the display refresh rate (vsync), to govern animations.
+ *
+ * Note that this method should be called from the same thread that {@link #start()} is
+ * called in order to have the new frame delay take effect on that animation. A runtime
+ * exception will be thrown if the calling thread does not have a Looper.
+ *
+ * @param frameDelay the requested time between frames, in milliseconds
+ */
+ public static void setFrameDelay(long frameDelay) {
+ AnimationHandler.getInstance().setFrameDelay(frameDelay);
+ }
+
+ /**
+ * The most recent value calculated by this <code>ValueAnimator</code> when there is just one
+ * property being animated. This value is only sensible while the animation is running. The main
+ * purpose for this read-only property is to retrieve the value from the <code>ValueAnimator</code>
+ * during a call to {@link AnimatorUpdateListener#onAnimationUpdate(ValueAnimator)}, which
+ * is called during each animation frame, immediately after the value is calculated.
+ *
+ * @return animatedValue The value most recently calculated by this <code>ValueAnimator</code> for
+ * the single property being animated. If there are several properties being animated
+ * (specified by several PropertyValuesHolder objects in the constructor), this function
+ * returns the animated value for the first of those objects.
+ */
+ public Object getAnimatedValue() {
+ if (mValues != null && mValues.length > 0) {
+ return mValues[0].getAnimatedValue();
+ }
+ // Shouldn't get here; should always have values unless ValueAnimator was set up wrong
+ return null;
+ }
+
+ /**
+ * The most recent value calculated by this <code>ValueAnimator</code> for <code>propertyName</code>.
+ * The main purpose for this read-only property is to retrieve the value from the
+ * <code>ValueAnimator</code> during a call to
+ * {@link AnimatorUpdateListener#onAnimationUpdate(ValueAnimator)}, which
+ * is called during each animation frame, immediately after the value is calculated.
+ *
+ * @return animatedValue The value most recently calculated for the named property
+ * by this <code>ValueAnimator</code>.
+ */
+ public Object getAnimatedValue(String propertyName) {
+ PropertyValuesHolder valuesHolder = mValuesMap.get(propertyName);
+ if (valuesHolder != null) {
+ return valuesHolder.getAnimatedValue();
+ } else {
+ // At least avoid crashing if called with bogus propertyName
+ return null;
+ }
+ }
+
+ /**
+ * Sets how many times the animation should be repeated. If the repeat
+ * count is 0, the animation is never repeated. If the repeat count is
+ * greater than 0 or {@link #INFINITE}, the repeat mode will be taken
+ * into account. The repeat count is 0 by default.
+ *
+ * @param value the number of times the animation should be repeated
+ */
+ public void setRepeatCount(int value) {
+ mRepeatCount = value;
+ }
+ /**
+ * Defines how many times the animation should repeat. The default value
+ * is 0.
+ *
+ * @return the number of times the animation should repeat, or {@link #INFINITE}
+ */
+ public int getRepeatCount() {
+ return mRepeatCount;
+ }
+
+ /**
+ * Defines what this animation should do when it reaches the end. This
+ * setting is applied only when the repeat count is either greater than
+ * 0 or {@link #INFINITE}. Defaults to {@link #RESTART}.
+ *
+ * @param value {@link #RESTART} or {@link #REVERSE}
+ */
+ public void setRepeatMode(@RepeatMode int value) {
+ mRepeatMode = value;
+ }
+
+ /**
+ * Defines what this animation should do when it reaches the end.
+ *
+ * @return either one of {@link #REVERSE} or {@link #RESTART}
+ */
+ @RepeatMode
+ public int getRepeatMode() {
+ return mRepeatMode;
+ }
+
+ /**
+ * Adds a listener to the set of listeners that are sent update events through the life of
+ * an animation. This method is called on all listeners for every frame of the animation,
+ * after the values for the animation have been calculated.
+ *
+ * @param listener the listener to be added to the current set of listeners for this animation.
+ */
+ public void addUpdateListener(AnimatorUpdateListener listener) {
+ if (mUpdateListeners == null) {
+ mUpdateListeners = new ArrayList<AnimatorUpdateListener>();
+ }
+ mUpdateListeners.add(listener);
+ }
+
+ /**
+ * Removes all listeners from the set listening to frame updates for this animation.
+ */
+ public void removeAllUpdateListeners() {
+ if (mUpdateListeners == null) {
+ return;
+ }
+ mUpdateListeners.clear();
+ mUpdateListeners = null;
+ }
+
+ /**
+ * Removes a listener from the set listening to frame updates for this animation.
+ *
+ * @param listener the listener to be removed from the current set of update listeners
+ * for this animation.
+ */
+ public void removeUpdateListener(AnimatorUpdateListener listener) {
+ if (mUpdateListeners == null) {
+ return;
+ }
+ mUpdateListeners.remove(listener);
+ if (mUpdateListeners.size() == 0) {
+ mUpdateListeners = null;
+ }
+ }
+
+
+ /**
+ * The time interpolator used in calculating the elapsed fraction of this animation. The
+ * interpolator determines whether the animation runs with linear or non-linear motion,
+ * such as acceleration and deceleration. The default value is
+ * {@link android.view.animation.AccelerateDecelerateInterpolator}
+ *
+ * @param value the interpolator to be used by this animation. A value of <code>null</code>
+ * will result in linear interpolation.
+ */
+ @Override
+ public void setInterpolator(TimeInterpolator value) {
+ if (value != null) {
+ mInterpolator = value;
+ } else {
+ mInterpolator = new LinearInterpolator();
+ }
+ }
+
+ /**
+ * Returns the timing interpolator that this ValueAnimator uses.
+ *
+ * @return The timing interpolator for this ValueAnimator.
+ */
+ @Override
+ public TimeInterpolator getInterpolator() {
+ return mInterpolator;
+ }
+
+ /**
+ * The type evaluator to be used when calculating the animated values of this animation.
+ * The system will automatically assign a float or int evaluator based on the type
+ * of <code>startValue</code> and <code>endValue</code> in the constructor. But if these values
+ * are not one of these primitive types, or if different evaluation is desired (such as is
+ * necessary with int values that represent colors), a custom evaluator needs to be assigned.
+ * For example, when running an animation on color values, the {@link ArgbEvaluator}
+ * should be used to get correct RGB color interpolation.
+ *
+ * <p>If this ValueAnimator has only one set of values being animated between, this evaluator
+ * will be used for that set. If there are several sets of values being animated, which is
+ * the case if PropertyValuesHolder objects were set on the ValueAnimator, then the evaluator
+ * is assigned just to the first PropertyValuesHolder object.</p>
+ *
+ * @param value the evaluator to be used this animation
+ */
+ public void setEvaluator(TypeEvaluator value) {
+ if (value != null && mValues != null && mValues.length > 0) {
+ mValues[0].setEvaluator(value);
+ }
+ }
+
+ /**
+ * Start the animation playing. This version of start() takes a boolean flag that indicates
+ * whether the animation should play in reverse. The flag is usually false, but may be set
+ * to true if called from the reverse() method.
+ *
+ * <p>The animation started by calling this method will be run on the thread that called
+ * this method. This thread should have a Looper on it (a runtime exception will be thrown if
+ * this is not the case). Also, if the animation will animate
+ * properties of objects in the view hierarchy, then the calling thread should be the UI
+ * thread for that view hierarchy.</p>
+ *
+ * @param playBackwards Whether the ValueAnimator should start playing in reverse.
+ */
+ private void start(boolean playBackwards) {
+ if (Looper.myLooper() == null) {
+ throw new AndroidRuntimeException("Animators may only be run on Looper threads");
+ }
+ mReversing = playBackwards;
+ mSelfPulse = !mSuppressSelfPulseRequested;
+ // Special case: reversing from seek-to-0 should act as if not seeked at all.
+ if (playBackwards && mSeekFraction != -1 && mSeekFraction != 0) {
+ if (mRepeatCount == INFINITE) {
+ // Calculate the fraction of the current iteration.
+ float fraction = (float) (mSeekFraction - Math.floor(mSeekFraction));
+ mSeekFraction = 1 - fraction;
+ } else {
+ mSeekFraction = 1 + mRepeatCount - mSeekFraction;
+ }
+ }
+ mStarted = true;
+ mPaused = false;
+ mRunning = false;
+ mAnimationEndRequested = false;
+ // Resets mLastFrameTime when start() is called, so that if the animation was running,
+ // calling start() would put the animation in the
+ // started-but-not-yet-reached-the-first-frame phase.
+ mLastFrameTime = -1;
+ mFirstFrameTime = -1;
+ mStartTime = -1;
+ addAnimationCallback(0);
+
+ if (mStartDelay == 0 || mSeekFraction >= 0 || mReversing) {
+ // If there's no start delay, init the animation and notify start listeners right away
+ // to be consistent with the previous behavior. Otherwise, postpone this until the first
+ // frame after the start delay.
+ startAnimation();
+ if (mSeekFraction == -1) {
+ // No seek, start at play time 0. Note that the reason we are not using fraction 0
+ // is because for animations with 0 duration, we want to be consistent with pre-N
+ // behavior: skip to the final value immediately.
+ setCurrentPlayTime(0);
+ } else {
+ setCurrentFraction(mSeekFraction);
+ }
+ }
+ }
+
+ void startWithoutPulsing(boolean inReverse) {
+ mSuppressSelfPulseRequested = true;
+ if (inReverse) {
+ reverse();
+ } else {
+ start();
+ }
+ mSuppressSelfPulseRequested = false;
+ }
+
+ @Override
+ public void start() {
+ start(false);
+ }
+
+ @Override
+ public void cancel() {
+ if (Looper.myLooper() == null) {
+ throw new AndroidRuntimeException("Animators may only be run on Looper threads");
+ }
+
+ // If end has already been requested, through a previous end() or cancel() call, no-op
+ // until animation starts again.
+ if (mAnimationEndRequested) {
+ return;
+ }
+
+ // Only cancel if the animation is actually running or has been started and is about
+ // to run
+ // Only notify listeners if the animator has actually started
+ if ((mStarted || mRunning || mStartListenersCalled) && mListeners != null) {
+ if (!mRunning) {
+ // If it's not yet running, then start listeners weren't called. Call them now.
+ notifyStartListeners(mReversing);
+ }
+ notifyListeners(AnimatorCaller.ON_CANCEL, false);
+ }
+ endAnimation();
+ }
+
+ @Override
+ public void end() {
+ if (Looper.myLooper() == null) {
+ throw new AndroidRuntimeException("Animators may only be run on Looper threads");
+ }
+ if (!mRunning) {
+ // Special case if the animation has not yet started; get it ready for ending
+ startAnimation();
+ mStarted = true;
+ } else if (!mInitialized) {
+ initAnimation();
+ }
+ animateValue(shouldPlayBackward(mRepeatCount, mReversing) ? 0f : 1f);
+ endAnimation();
+ }
+
+ @Override
+ public void resume() {
+ if (Looper.myLooper() == null) {
+ throw new AndroidRuntimeException("Animators may only be resumed from the same " +
+ "thread that the animator was started on");
+ }
+ if (mPaused && !mResumed) {
+ mResumed = true;
+ if (mPauseTime > 0) {
+ addAnimationCallback(0);
+ }
+ }
+ super.resume();
+ }
+
+ @Override
+ public void pause() {
+ boolean previouslyPaused = mPaused;
+ super.pause();
+ if (!previouslyPaused && mPaused) {
+ mPauseTime = -1;
+ mResumed = false;
+ }
+ }
+
+ @Override
+ public boolean isRunning() {
+ return mRunning;
+ }
+
+ @Override
+ public boolean isStarted() {
+ return mStarted;
+ }
+
+ /**
+ * Plays the ValueAnimator in reverse. If the animation is already running,
+ * it will stop itself and play backwards from the point reached when reverse was called.
+ * If the animation is not currently running, then it will start from the end and
+ * play backwards. This behavior is only set for the current animation; future playing
+ * of the animation will use the default behavior of playing forward.
+ */
+ @Override
+ public void reverse() {
+ if (isPulsingInternal()) {
+ long currentTime = AnimationUtils.currentAnimationTimeMillis();
+ long currentPlayTime = currentTime - mStartTime;
+ long timeLeft = getScaledDuration() - currentPlayTime;
+ mStartTime = currentTime - timeLeft;
+ mStartTimeCommitted = true; // do not allow start time to be compensated for jank
+ mReversing = !mReversing;
+ } else if (mStarted) {
+ mReversing = !mReversing;
+ end();
+ } else {
+ start(true);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public boolean canReverse() {
+ return true;
+ }
+
+ /**
+ * Called internally to end an animation by removing it from the animations list. Must be
+ * called on the UI thread.
+ */
+ private void endAnimation() {
+ if (mAnimationEndRequested) {
+ return;
+ }
+ removeAnimationCallback();
+
+ mAnimationEndRequested = true;
+ mPaused = false;
+ boolean notify = (mStarted || mRunning) && mListeners != null;
+ if (notify && !mRunning) {
+ // If it's not yet running, then start listeners weren't called. Call them now.
+ notifyStartListeners(mReversing);
+ }
+ mLastFrameTime = -1;
+ mFirstFrameTime = -1;
+ mStartTime = -1;
+ mRunning = false;
+ mStarted = false;
+ notifyEndListeners(mReversing);
+ // mReversing needs to be reset *after* notifying the listeners for the end callbacks.
+ mReversing = false;
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+ Trace.asyncTraceEnd(Trace.TRACE_TAG_VIEW, getNameForTrace(),
+ System.identityHashCode(this));
+ }
+ }
+
+ /**
+ * Called internally to start an animation by adding it to the active animations list. Must be
+ * called on the UI thread.
+ */
+ private void startAnimation() {
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+ Trace.asyncTraceBegin(Trace.TRACE_TAG_VIEW, getNameForTrace(),
+ System.identityHashCode(this));
+ }
+
+ mAnimationEndRequested = false;
+ initAnimation();
+ mRunning = true;
+ if (mSeekFraction >= 0) {
+ mOverallFraction = mSeekFraction;
+ } else {
+ mOverallFraction = 0f;
+ }
+
+ notifyStartListeners(mReversing);
+ }
+
+ /**
+ * Internal only: This tracks whether the animation has gotten on the animation loop. Note
+ * this is different than {@link #isRunning()} in that the latter tracks the time after start()
+ * is called (or after start delay if any), which may be before the animation loop starts.
+ */
+ private boolean isPulsingInternal() {
+ return mLastFrameTime >= 0;
+ }
+
+ /**
+ * Returns the name of this animator for debugging purposes.
+ */
+ String getNameForTrace() {
+ return "animator";
+ }
+
+ /**
+ * Applies an adjustment to the animation to compensate for jank between when
+ * the animation first ran and when the frame was drawn.
+ * @hide
+ */
+ public void commitAnimationFrame(long frameTime) {
+ if (!mStartTimeCommitted) {
+ mStartTimeCommitted = true;
+ long adjustment = frameTime - mLastFrameTime;
+ if (adjustment > 0) {
+ mStartTime += adjustment;
+ if (DEBUG) {
+ Log.d(TAG, "Adjusted start time by " + adjustment + " ms: " + toString());
+ }
+ }
+ }
+ }
+
+ /**
+ * This internal function processes a single animation frame for a given animation. The
+ * currentTime parameter is the timing pulse sent by the handler, used to calculate the
+ * elapsed duration, and therefore
+ * the elapsed fraction, of the animation. The return value indicates whether the animation
+ * should be ended (which happens when the elapsed time of the animation exceeds the
+ * animation's duration, including the repeatCount).
+ *
+ * @param currentTime The current time, as tracked by the static timing handler
+ * @return true if the animation's duration, including any repetitions due to
+ * <code>repeatCount</code> has been exceeded and the animation should be ended.
+ */
+ boolean animateBasedOnTime(long currentTime) {
+ boolean done = false;
+ if (mRunning) {
+ final long scaledDuration = getScaledDuration();
+ final float fraction = scaledDuration > 0 ?
+ (float)(currentTime - mStartTime) / scaledDuration : 1f;
+ final float lastFraction = mOverallFraction;
+ final boolean newIteration = (int) fraction > (int) lastFraction;
+ final boolean lastIterationFinished = (fraction >= mRepeatCount + 1) &&
+ (mRepeatCount != INFINITE);
+ if (scaledDuration == 0) {
+ // 0 duration animator, ignore the repeat count and skip to the end
+ done = true;
+ } else if (newIteration && !lastIterationFinished) {
+ // Time to repeat
+ notifyListeners(AnimatorCaller.ON_REPEAT, false);
+ } else if (lastIterationFinished) {
+ done = true;
+ }
+ mOverallFraction = clampFraction(fraction);
+ float currentIterationFraction = getCurrentIterationFraction(
+ mOverallFraction, mReversing);
+ animateValue(currentIterationFraction);
+ }
+ return done;
+ }
+
+ /**
+ * Internal use only.
+ *
+ * This method does not modify any fields of the animation. It should be called when seeking
+ * in an AnimatorSet. When the last play time and current play time are of different repeat
+ * iterations,
+ * {@link android.view.animation.Animation.AnimationListener#onAnimationRepeat(Animation)}
+ * will be called.
+ */
+ @Override
+ void animateValuesInRange(long currentPlayTime, long lastPlayTime) {
+ if (currentPlayTime < 0 || lastPlayTime < -1) {
+ throw new UnsupportedOperationException("Error: Play time should never be negative.");
+ }
+
+ initAnimation();
+ long duration = getTotalDuration();
+ if (lastPlayTime < 0 || (lastPlayTime == 0 && currentPlayTime > 0)) {
+ notifyStartListeners(false);
+ } else if (lastPlayTime > duration
+ || (lastPlayTime == duration && currentPlayTime < duration)
+ ) {
+ notifyStartListeners(true);
+ }
+ if (duration >= 0) {
+ lastPlayTime = Math.min(duration, lastPlayTime);
+ }
+ lastPlayTime -= mStartDelay;
+ currentPlayTime -= mStartDelay;
+
+ // Check whether repeat callback is needed only when repeat count is non-zero
+ if (mRepeatCount > 0) {
+ int iteration = Math.max(0, (int) (currentPlayTime / mDuration));
+ int lastIteration = Math.max(0, (int) (lastPlayTime / mDuration));
+
+ // Clamp iteration to [0, mRepeatCount]
+ iteration = Math.min(iteration, mRepeatCount);
+ lastIteration = Math.min(lastIteration, mRepeatCount);
+
+ if (iteration != lastIteration) {
+ notifyListeners(AnimatorCaller.ON_REPEAT, false);
+ }
+ }
+
+ if (mRepeatCount != INFINITE && currentPlayTime > (mRepeatCount + 1) * mDuration) {
+ throw new IllegalStateException("Can't animate a value outside of the duration");
+ } else {
+ // Find the current fraction:
+ float fraction = Math.max(0, currentPlayTime) / (float) mDuration;
+ fraction = getCurrentIterationFraction(fraction, false);
+ animateValue(fraction);
+ }
+ }
+
+ @Override
+ void animateSkipToEnds(long currentPlayTime, long lastPlayTime) {
+ boolean inReverse = currentPlayTime < lastPlayTime;
+ boolean doSkip;
+ if (currentPlayTime <= 0 && lastPlayTime > 0) {
+ doSkip = true;
+ } else {
+ long duration = getTotalDuration();
+ doSkip = duration >= 0 && currentPlayTime >= duration && lastPlayTime < duration;
+ }
+ if (doSkip) {
+ notifyStartListeners(inReverse);
+ skipToEndValue(inReverse);
+ notifyEndListeners(inReverse);
+ }
+ }
+
+ /**
+ * Internal use only.
+ * Skips the animation value to end/start, depending on whether the play direction is forward
+ * or backward.
+ *
+ * @param inReverse whether the end value is based on a reverse direction. If yes, this is
+ * equivalent to skip to start value in a forward playing direction.
+ */
+ void skipToEndValue(boolean inReverse) {
+ initAnimation();
+ float endFraction = inReverse ? 0f : 1f;
+ if (mRepeatCount % 2 == 1 && mRepeatMode == REVERSE) {
+ // This would end on fraction = 0
+ endFraction = 0f;
+ }
+ animateValue(endFraction);
+ }
+
+ @Override
+ boolean isInitialized() {
+ return mInitialized;
+ }
+
+ /**
+ * Processes a frame of the animation, adjusting the start time if needed.
+ *
+ * @param frameTime The frame time.
+ * @return true if the animation has ended.
+ * @hide
+ */
+ public final boolean doAnimationFrame(long frameTime) {
+ if (mStartTime < 0) {
+ // First frame. If there is start delay, start delay count down will happen *after* this
+ // frame.
+ mStartTime = mReversing
+ ? frameTime
+ : frameTime + (long) (mStartDelay * resolveDurationScale());
+ }
+
+ // Handle pause/resume
+ if (mPaused) {
+ mPauseTime = frameTime;
+ removeAnimationCallback();
+ return false;
+ } else if (mResumed) {
+ mResumed = false;
+ if (mPauseTime > 0) {
+ // Offset by the duration that the animation was paused
+ mStartTime += (frameTime - mPauseTime);
+ }
+ }
+
+ if (!mRunning) {
+ // If not running, that means the animation is in the start delay phase of a forward
+ // running animation. In the case of reversing, we want to run start delay in the end.
+ if (mStartTime > frameTime && mSeekFraction == -1) {
+ // This is when no seek fraction is set during start delay. If developers change the
+ // seek fraction during the delay, animation will start from the seeked position
+ // right away.
+ return false;
+ } else {
+ // If mRunning is not set by now, that means non-zero start delay,
+ // no seeking, not reversing. At this point, start delay has passed.
+ mRunning = true;
+ startAnimation();
+ }
+ }
+
+ if (mLastFrameTime < 0) {
+ if (mSeekFraction >= 0) {
+ long seekTime = (long) (getScaledDuration() * mSeekFraction);
+ mStartTime = frameTime - seekTime;
+ mSeekFraction = -1;
+ }
+ mStartTimeCommitted = false; // allow start time to be compensated for jank
+ }
+ mLastFrameTime = frameTime;
+ // The frame time might be before the start time during the first frame of
+ // an animation. The "current time" must always be on or after the start
+ // time to avoid animating frames at negative time intervals. In practice, this
+ // is very rare and only happens when seeking backwards.
+ final long currentTime = Math.max(frameTime, mStartTime);
+ boolean finished = animateBasedOnTime(currentTime);
+
+ if (finished) {
+ endAnimation();
+ }
+ return finished;
+ }
+
+ @Override
+ boolean pulseAnimationFrame(long frameTime) {
+ if (mSelfPulse) {
+ // Pulse animation frame will *always* be after calling start(). If mSelfPulse isn't
+ // set to false at this point, that means child animators did not call super's start().
+ // This can happen when the Animator is just a non-animating wrapper around a real
+ // functional animation. In this case, we can't really pulse a frame into the animation,
+ // because the animation cannot necessarily be properly initialized (i.e. no start/end
+ // values set).
+ return false;
+ }
+ return doAnimationFrame(frameTime);
+ }
+
+ private void addOneShotCommitCallback() {
+ if (!mSelfPulse) {
+ return;
+ }
+ getAnimationHandler().addOneShotCommitCallback(this);
+ }
+
+ private void removeAnimationCallback() {
+ if (!mSelfPulse) {
+ return;
+ }
+ getAnimationHandler().removeCallback(this);
+ }
+
+ private void addAnimationCallback(long delay) {
+ if (!mSelfPulse) {
+ return;
+ }
+ getAnimationHandler().addAnimationFrameCallback(this, delay);
+ }
+
+ /**
+ * Returns the current animation fraction, which is the elapsed/interpolated fraction used in
+ * the most recent frame update on the animation.
+ *
+ * @return Elapsed/interpolated fraction of the animation.
+ */
+ public float getAnimatedFraction() {
+ return mCurrentFraction;
+ }
+
+ /**
+ * This method is called with the elapsed fraction of the animation during every
+ * animation frame. This function turns the elapsed fraction into an interpolated fraction
+ * and then into an animated value (from the evaluator. The function is called mostly during
+ * animation updates, but it is also called when the <code>end()</code>
+ * function is called, to set the final value on the property.
+ *
+ * <p>Overrides of this method must call the superclass to perform the calculation
+ * of the animated value.</p>
+ *
+ * @param fraction The elapsed fraction of the animation.
+ */
+ @CallSuper
+ @UnsupportedAppUsage
+ void animateValue(float fraction) {
+ if (TRACE_ANIMATION_FRACTION) {
+ Trace.traceCounter(Trace.TRACE_TAG_VIEW, getNameForTrace() + hashCode(),
+ (int) (fraction * 1000));
+ }
+ if (mValues == null) {
+ return;
+ }
+ fraction = mInterpolator.getInterpolation(fraction);
+ mCurrentFraction = fraction;
+ int numValues = mValues.length;
+ for (int i = 0; i < numValues; ++i) {
+ mValues[i].calculateValue(fraction);
+ }
+ if (mSeekFraction >= 0 || mStartListenersCalled) {
+ callOnList(mUpdateListeners, AnimatorCaller.ON_UPDATE, this, false);
+ }
+ }
+
+ @Override
+ public ValueAnimator clone() {
+ final ValueAnimator anim = (ValueAnimator) super.clone();
+ if (mUpdateListeners != null) {
+ anim.mUpdateListeners = new ArrayList<AnimatorUpdateListener>(mUpdateListeners);
+ }
+ anim.mSeekFraction = -1;
+ anim.mReversing = false;
+ anim.mInitialized = false;
+ anim.mStarted = false;
+ anim.mRunning = false;
+ anim.mPaused = false;
+ anim.mResumed = false;
+ anim.mStartTime = -1;
+ anim.mStartTimeCommitted = false;
+ anim.mAnimationEndRequested = false;
+ anim.mPauseTime = -1;
+ anim.mLastFrameTime = -1;
+ anim.mFirstFrameTime = -1;
+ anim.mOverallFraction = 0;
+ anim.mCurrentFraction = 0;
+ anim.mSelfPulse = true;
+ anim.mSuppressSelfPulseRequested = false;
+
+ PropertyValuesHolder[] oldValues = mValues;
+ if (oldValues != null) {
+ int numValues = oldValues.length;
+ anim.mValues = new PropertyValuesHolder[numValues];
+ anim.mValuesMap = new HashMap<String, PropertyValuesHolder>(numValues);
+ for (int i = 0; i < numValues; ++i) {
+ PropertyValuesHolder newValuesHolder = oldValues[i].clone();
+ anim.mValues[i] = newValuesHolder;
+ anim.mValuesMap.put(newValuesHolder.getPropertyName(), newValuesHolder);
+ }
+ }
+ return anim;
+ }
+
+ /**
+ * Implementors of this interface can add themselves as update listeners
+ * to an <code>ValueAnimator</code> instance to receive callbacks on every animation
+ * frame, after the current frame's values have been calculated for that
+ * <code>ValueAnimator</code>.
+ */
+ public static interface AnimatorUpdateListener {
+ /**
+ * <p>Notifies the occurrence of another frame of the animation.</p>
+ *
+ * @param animation The animation which was repeated.
+ */
+ void onAnimationUpdate(@NonNull ValueAnimator animation);
+
+ }
+
+ /**
+ * Return the number of animations currently running.
+ *
+ * Used by StrictMode internally to annotate violations.
+ * May be called on arbitrary threads!
+ *
+ * @hide
+ */
+ public static int getCurrentAnimationsCount() {
+ return AnimationHandler.getAnimationCount();
+ }
+
+ @Override
+ public String toString() {
+ String returnVal = "ValueAnimator@" + Integer.toHexString(hashCode());
+ if (mValues != null) {
+ for (int i = 0; i < mValues.length; ++i) {
+ returnVal += "\n " + mValues[i].toString();
+ }
+ }
+ return returnVal;
+ }
+
+ /**
+ * <p>Whether or not the ValueAnimator is allowed to run asynchronously off of
+ * the UI thread. This is a hint that informs the ValueAnimator that it is
+ * OK to run the animation off-thread, however ValueAnimator may decide
+ * that it must run the animation on the UI thread anyway. For example if there
+ * is an {@link AnimatorUpdateListener} the animation will run on the UI thread,
+ * regardless of the value of this hint.</p>
+ *
+ * <p>Regardless of whether or not the animation runs asynchronously, all
+ * listener callbacks will be called on the UI thread.</p>
+ *
+ * <p>To be able to use this hint the following must be true:</p>
+ * <ol>
+ * <li>{@link #getAnimatedFraction()} is not needed (it will return undefined values).</li>
+ * <li>The animator is immutable while {@link #isStarted()} is true. Requests
+ * to change values, duration, delay, etc... may be ignored.</li>
+ * <li>Lifecycle callback events may be asynchronous. Events such as
+ * {@link Animator.AnimatorListener#onAnimationEnd(Animator)} or
+ * {@link Animator.AnimatorListener#onAnimationRepeat(Animator)} may end up delayed
+ * as they must be posted back to the UI thread, and any actions performed
+ * by those callbacks (such as starting new animations) will not happen
+ * in the same frame.</li>
+ * <li>State change requests ({@link #cancel()}, {@link #end()}, {@link #reverse()}, etc...)
+ * may be asynchronous. It is guaranteed that all state changes that are
+ * performed on the UI thread in the same frame will be applied as a single
+ * atomic update, however that frame may be the current frame,
+ * the next frame, or some future frame. This will also impact the observed
+ * state of the Animator. For example, {@link #isStarted()} may still return true
+ * after a call to {@link #end()}. Using the lifecycle callbacks is preferred over
+ * queries to {@link #isStarted()}, {@link #isRunning()}, and {@link #isPaused()}
+ * for this reason.</li>
+ * </ol>
+ * @hide
+ */
+ @Override
+ public void setAllowRunningAsynchronously(boolean mayRunAsync) {
+ // It is up to subclasses to support this, if they can.
+ }
+
+ /**
+ * @return The {@link AnimationHandler} that will be used to schedule updates for this animator.
+ * @hide
+ */
+ public AnimationHandler getAnimationHandler() {
+ return mAnimationHandler != null ? mAnimationHandler : AnimationHandler.getInstance();
+ }
+
+ /**
+ * Sets the animation handler used to schedule updates for this animator or {@code null} to use
+ * the default handler.
+ * @hide
+ */
+ public void setAnimationHandler(@Nullable AnimationHandler animationHandler) {
+ mAnimationHandler = animationHandler;
+ }
+
+ /**
+ * Listener interface for the system-wide scaling factor for Animator-based animations.
+ *
+ * @see #registerDurationScaleChangeListener(DurationScaleChangeListener)
+ * @see #unregisterDurationScaleChangeListener(DurationScaleChangeListener)
+ */
+ public interface DurationScaleChangeListener {
+ /**
+ * Called when the duration scale changes.
+ * @param scale the duration scale
+ */
+ void onChanged(@FloatRange(from = 0) float scale);
+ }
+}
diff --git a/android-35/android/annotation/AnimRes.java b/android-35/android/annotation/AnimRes.java
new file mode 100644
index 0000000..56f8acf
--- /dev/null
+++ b/android-35/android/annotation/AnimRes.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2013 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.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * Denotes that an integer parameter, field or method return value is expected
+ * to be an anim resource reference (e.g. {@link android.R.anim#fade_in}).
+ *
+ * {@hide}
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface AnimRes {
+}
diff --git a/android-35/android/annotation/AnimatorRes.java b/android-35/android/annotation/AnimatorRes.java
new file mode 100644
index 0000000..cd4c189
--- /dev/null
+++ b/android-35/android/annotation/AnimatorRes.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2013 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.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * Denotes that an integer parameter, field or method return value is expected
+ * to be an animator resource reference (e.g. {@link android.R.animator#fade_in}).
+ *
+ * {@hide}
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface AnimatorRes {
+}
diff --git a/android-35/android/annotation/AnyRes.java b/android-35/android/annotation/AnyRes.java
new file mode 100644
index 0000000..44411a0
--- /dev/null
+++ b/android-35/android/annotation/AnyRes.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2013 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.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * Denotes that an integer parameter, field or method return value is expected
+ * to be a resource reference of any type. If the specific type is known, use
+ * one of the more specific annotations instead, such as {@link StringRes} or
+ * {@link DrawableRes}.
+ *
+ * {@hide}
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface AnyRes {
+}
diff --git a/android-35/android/annotation/AnyThread.java b/android-35/android/annotation/AnyThread.java
new file mode 100644
index 0000000..ee36a42
--- /dev/null
+++ b/android-35/android/annotation/AnyThread.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2016 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.annotation;
+
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Denotes that the annotated method can be called from any thread (e.g. it is
+ * "thread safe".) If the annotated element is a class, then all methods in the
+ * class can be called from any thread.
+ * <p>
+ * The main purpose of this method is to indicate that you believe a method can
+ * be called from any thread; static tools can then check that nothing you call
+ * from within this method or class have more strict threading requirements.
+ * <p>
+ * Example:
+ *
+ * <pre>
+ * <code>
+ * @AnyThread
+ * public void deliverResult(D data) { ... }
+ * </code>
+ * </pre>
+ *
+ * @memberDoc This method is safe to call from any thread.
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({METHOD,CONSTRUCTOR,TYPE,PARAMETER})
+public @interface AnyThread {
+}
diff --git a/android-35/android/annotation/AppIdInt.java b/android-35/android/annotation/AppIdInt.java
new file mode 100644
index 0000000..29838dd
--- /dev/null
+++ b/android-35/android/annotation/AppIdInt.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2016 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.annotation;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Denotes that the annotated element is a multi-user application ID. This is
+ * <em>not</em> the same as a UID.
+ *
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface AppIdInt {
+}
diff --git a/android-35/android/annotation/ArrayRes.java b/android-35/android/annotation/ArrayRes.java
new file mode 100644
index 0000000..1407af1
--- /dev/null
+++ b/android-35/android/annotation/ArrayRes.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2013 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.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * Denotes that an integer parameter, field or method return value is expected
+ * to be an array resource reference (e.g. {@link android.R.array#phoneTypes}).
+ *
+ * {@hide}
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface ArrayRes {
+}
diff --git a/android-35/android/annotation/AttrRes.java b/android-35/android/annotation/AttrRes.java
new file mode 100644
index 0000000..285b80c
--- /dev/null
+++ b/android-35/android/annotation/AttrRes.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2013 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.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * Denotes that an integer parameter, field or method return value is expected
+ * to be an attribute reference (e.g. {@link android.R.attr#action}).
+ *
+ * {@hide}
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface AttrRes {
+}
diff --git a/android-35/android/annotation/BinderThread.java b/android-35/android/annotation/BinderThread.java
new file mode 100644
index 0000000..ca5e14c
--- /dev/null
+++ b/android-35/android/annotation/BinderThread.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2015 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.annotation;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * Denotes that the annotated method should only be called on the binder thread.
+ * If the annotated element is a class, then all methods in the class should be called
+ * on the binder thread.
+ * <p>
+ * Example:
+ * <pre><code>
+ * @BinderThread
+ * public BeamShareData createBeamShareData() { ... }
+ * </code></pre>
+ *
+ * {@hide}
+ */
+@Retention(SOURCE)
+@Target({METHOD,CONSTRUCTOR,TYPE,PARAMETER})
+public @interface BinderThread {
+}
\ No newline at end of file
diff --git a/android-35/android/annotation/BoolRes.java b/android-35/android/annotation/BoolRes.java
new file mode 100644
index 0000000..f50785b
--- /dev/null
+++ b/android-35/android/annotation/BoolRes.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2013 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.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * Denotes that an integer parameter, field or method return value is expected
+ * to be a boolean resource reference.
+ *
+ * {@hide}
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface BoolRes {
+}
diff --git a/android-35/android/annotation/BroadcastBehavior.java b/android-35/android/annotation/BroadcastBehavior.java
new file mode 100644
index 0000000..87bf554
--- /dev/null
+++ b/android-35/android/annotation/BroadcastBehavior.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2017 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.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Description of how the annotated broadcast action behaves.
+ *
+ * @hide
+ */
+@Target({ ElementType.FIELD })
+@Retention(RetentionPolicy.SOURCE)
+public @interface BroadcastBehavior {
+ /**
+ * This broadcast will only be delivered to an explicit target.
+ *
+ * @see android.content.Intent#setPackage(String)
+ * @see android.content.Intent#setComponent(android.content.ComponentName)
+ */
+ boolean explicitOnly() default false;
+
+ /**
+ * This broadcast will only be delivered to registered receivers.
+ *
+ * @see android.content.Intent#FLAG_RECEIVER_REGISTERED_ONLY
+ */
+ boolean registeredOnly() default false;
+
+ /**
+ * This broadcast will include all {@code AndroidManifest.xml} receivers
+ * regardless of process state.
+ *
+ * @see android.content.Intent#FLAG_RECEIVER_INCLUDE_BACKGROUND
+ */
+ boolean includeBackground() default false;
+
+ /**
+ * This broadcast is protected and can only be sent by the OS.
+ */
+ boolean protectedBroadcast() default false;
+}
diff --git a/android-35/android/annotation/BytesLong.java b/android-35/android/annotation/BytesLong.java
new file mode 100644
index 0000000..f5e1a9c
--- /dev/null
+++ b/android-35/android/annotation/BytesLong.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2017 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.annotation;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * @memberDoc Value is a non-negative number of bytes.
+ * @paramDoc Value is a non-negative number of bytes.
+ * @returnDoc Value is a non-negative number of bytes.
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface BytesLong {
+}
diff --git a/android-35/android/annotation/CallSuper.java b/android-35/android/annotation/CallSuper.java
new file mode 100644
index 0000000..c16b511
--- /dev/null
+++ b/android-35/android/annotation/CallSuper.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2015 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.annotation;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Denotes that any overriding methods should invoke this method as well.
+ * <p>
+ * Example:
+ *
+ * <pre>
+ * <code>
+ * @CallSuper
+ * public abstract void onFocusLost();
+ * </code>
+ * </pre>
+ *
+ * @memberDoc If you override this method you <em>must</em> call through to the
+ * superclass implementation.
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({METHOD})
+public @interface CallSuper {
+}
diff --git a/android-35/android/annotation/CallbackExecutor.java b/android-35/android/annotation/CallbackExecutor.java
new file mode 100644
index 0000000..9529d3e
--- /dev/null
+++ b/android-35/android/annotation/CallbackExecutor.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2017 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.annotation;
+
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+import java.util.concurrent.Executor;
+
+/**
+ * @paramDoc Callback and listener events are dispatched through this
+ * {@link Executor}, providing an easy way to control which thread is
+ * used. To dispatch events through the main thread of your
+ * application, you can use
+ * {@link android.content.Context#getMainExecutor() Context.getMainExecutor()}.
+ * Otherwise, provide an {@link Executor} that dispatches to an appropriate thread.
+ * @hide
+ */
+@Retention(SOURCE)
+@Target(PARAMETER)
+public @interface CallbackExecutor {
+}
diff --git a/android-35/android/annotation/CheckResult.java b/android-35/android/annotation/CheckResult.java
new file mode 100644
index 0000000..97d031a
--- /dev/null
+++ b/android-35/android/annotation/CheckResult.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2015 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.annotation;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * Denotes that the annotated method returns a result that it typically is
+ * an error to ignore. This is usually used for methods that have no side effect,
+ * so calling it without actually looking at the result usually means the developer
+ * has misunderstood what the method does.
+ * <p>
+ * Example:
+ * <pre>{@code
+ * public @CheckResult String trim(String s) { return s.trim(); }
+ * ...
+ * s.trim(); // this is probably an error
+ * s = s.trim(); // ok
+ * }</pre>
+ *
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({METHOD})
+public @interface CheckResult {
+ /** Defines the name of the suggested method to use instead, if applicable (using
+ * the same signature format as javadoc.) If there is more than one possibility,
+ * list them all separated by commas.
+ * <p>
+ * For example, ProcessBuilder has a method named {@code redirectErrorStream()}
+ * which sounds like it might redirect the error stream. It does not. It's just
+ * a getter which returns whether the process builder will redirect the error stream,
+ * and to actually set it, you must call {@code redirectErrorStream(boolean)}.
+ * In that case, the method should be defined like this:
+ * <pre>
+ * @CheckResult(suggest="#redirectErrorStream(boolean)")
+ * public boolean redirectErrorStream() { ... }
+ * </pre>
+ */
+ String suggest() default "";
+}
\ No newline at end of file
diff --git a/android-35/android/annotation/ChecksSdkIntAtLeast.java b/android-35/android/annotation/ChecksSdkIntAtLeast.java
new file mode 100644
index 0000000..3223125
--- /dev/null
+++ b/android-35/android/annotation/ChecksSdkIntAtLeast.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.annotation;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Denotes that the annotated method checks if the SDK_INT API level is
+ * at least the given value, and either returns it or executes the
+ * given lambda in that case (or if it's a field, has the value true).
+ *
+ * The API level can be specified either as an API level via
+ * {@link #api()}, or for preview platforms as a codename (such as "R") via
+ * {@link #codename()}}, or it can be passed in to the method; in that
+ * case, the parameter containing the API level or code name should
+ * be specified via {@link #parameter()}, where the first parameter
+ * is numbered 0.
+ *
+ * <p>
+ * Examples:
+ * <pre><code>
+ * // Simple version check
+ * @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.O)
+ * public static boolean isAtLeastO() {
+ * return Build.VERSION.SDK_INT >= 26;
+ * }
+ *
+ * // Required API level is passed in as first argument, and function
+ * // in second parameter is executed if SDK_INT is at least that high:
+ * @ChecksSdkIntAtLeast(parameter = 0, lambda = 1)
+ * inline fun fromApi(value: Int, action: () -> Unit) {
+ * if (Build.VERSION.SDK_INT >= value) {
+ * action()
+ * }
+ * }
+ *
+ * // Java field:
+ * @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.LOLLIPOP)
+ * public static final boolean SUPPORTS_LETTER_SPACING =
+ * Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
+ *
+ * </code></pre>
+ *
+ * @hide
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD, FIELD})
+public @interface ChecksSdkIntAtLeast {
+ /**
+ * The API level is at least the given level
+ */
+ int api() default -1;
+
+ /**
+ * The API level is at least the given codename (such as "R")
+ */
+ String codename() default "";
+
+ /**
+ * The API level is specified in the given parameter, where the first parameter is number 0
+ */
+ int parameter() default -1;
+
+ /**
+ * The parameter number for a lambda that will be executed if the API level is at least
+ * the value supplied via {@link #api()}, {@link #codename()} or
+ * {@link #parameter()}
+ */
+ int lambda() default -1;
+}
diff --git a/android-35/android/annotation/ColorInt.java b/android-35/android/annotation/ColorInt.java
new file mode 100644
index 0000000..4671b1b
--- /dev/null
+++ b/android-35/android/annotation/ColorInt.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2015 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.annotation;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * Denotes that the annotated element represents a packed color
+ * int, {@code AARRGGBB}. If applied to an int array, every element
+ * in the array represents a color integer.
+ * <p>
+ * Example:
+ * <pre>{@code
+ * public abstract void setTextColor(@ColorInt int color);
+ * }</pre>
+ *
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({PARAMETER,METHOD,LOCAL_VARIABLE,FIELD})
+public @interface ColorInt {
+}
\ No newline at end of file
diff --git a/android-35/android/annotation/ColorLong.java b/android-35/android/annotation/ColorLong.java
new file mode 100644
index 0000000..9b19c76
--- /dev/null
+++ b/android-35/android/annotation/ColorLong.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2016 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.annotation;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * <p>Denotes that the annotated element represents a packed color
+ * long. If applied to a long array, every element in the array
+ * represents a color long. For more information on how colors
+ * are packed in a long, please refer to the documentation of
+ * the {@link android.graphics.Color} class.</p>
+ *
+ * <p>Example:</p>
+ *
+ * <pre>{@code
+ * public void setFillColor(@ColorLong long color);
+ * }</pre>
+ *
+ * @see android.graphics.Color
+ *
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({PARAMETER,METHOD,LOCAL_VARIABLE,FIELD})
+public @interface ColorLong {
+}
diff --git a/android-35/android/annotation/ColorRes.java b/android-35/android/annotation/ColorRes.java
new file mode 100644
index 0000000..061faa0
--- /dev/null
+++ b/android-35/android/annotation/ColorRes.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2013 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.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * Denotes that an integer parameter, field or method return value is expected
+ * to be a color resource reference (e.g. {@link android.R.color#black}).
+ *
+ * {@hide}
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface ColorRes {
+}
diff --git a/android-35/android/annotation/Condemned.java b/android-35/android/annotation/Condemned.java
new file mode 100644
index 0000000..186409b
--- /dev/null
+++ b/android-35/android/annotation/Condemned.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2017 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.annotation;
+
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PACKAGE;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * A program element annotated @Condemned is one that programmers are
+ * blocked from using, typically because it's about to be completely destroyed.
+ * <p>
+ * This is a stronger version of @Deprecated, and it's typically used to
+ * mark APIs that only existed temporarily in a preview SDK, and which only
+ * continue to exist temporarily to support binary compatibility.
+ *
+ * @hide
+ */
+@Retention(SOURCE)
+@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
+public @interface Condemned {
+}
diff --git a/android-35/android/annotation/CurrentTimeMillisLong.java b/android-35/android/annotation/CurrentTimeMillisLong.java
new file mode 100644
index 0000000..355bb5a
--- /dev/null
+++ b/android-35/android/annotation/CurrentTimeMillisLong.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2017 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.annotation;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * @memberDoc Value is a non-negative timestamp measured as the number of
+ * milliseconds since 1970-01-01T00:00:00Z.
+ * @paramDoc Value is a non-negative timestamp measured as the number of
+ * milliseconds since 1970-01-01T00:00:00Z.
+ * @returnDoc Value is a non-negative timestamp measured as the number of
+ * milliseconds since 1970-01-01T00:00:00Z.
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface CurrentTimeMillisLong {
+}
diff --git a/android-35/android/annotation/CurrentTimeSecondsLong.java b/android-35/android/annotation/CurrentTimeSecondsLong.java
new file mode 100644
index 0000000..2b4ffd7
--- /dev/null
+++ b/android-35/android/annotation/CurrentTimeSecondsLong.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2019 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.annotation;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * @memberDoc Value is a non-negative timestamp measured as the number of
+ * seconds since 1970-01-01T00:00:00Z.
+ * @paramDoc Value is a non-negative timestamp measured as the number of
+ * seconds since 1970-01-01T00:00:00Z.
+ * @returnDoc Value is a non-negative timestamp measured as the number of
+ * seconds since 1970-01-01T00:00:00Z.
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface CurrentTimeSecondsLong {
+}
diff --git a/android-35/android/annotation/DeprecatedForSdk.java b/android-35/android/annotation/DeprecatedForSdk.java
new file mode 100644
index 0000000..bbd0a2c
--- /dev/null
+++ b/android-35/android/annotation/DeprecatedForSdk.java
@@ -0,0 +1,54 @@
+/*
+ * 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.annotation;
+
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PACKAGE;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * The annotated element is considered deprecated in the public SDK. This will be turned into a
+ * plain @Deprecated annotation in the SDK.
+ *
+ * <p>The value parameter should be the message to include in the documentation as a @deprecated
+ * comment.
+ *
+ * @hide
+ */
+@Retention(SOURCE)
+@Target(value = {CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
+public @interface DeprecatedForSdk {
+ /**
+ * The message to include in the documentation, which will be merged in as a @deprecated
+ * tag.
+ */
+ String value();
+
+ /**
+ * If specified, one or more annotation classes corresponding to particular API surfaces where
+ * the API will <b>not</b> be marked as deprecated, such as {@link SystemApi} or {@link
+ * TestApi}.
+ */
+ Class<?>[] allowIn() default {};
+}
diff --git a/android-35/android/annotation/DimenRes.java b/android-35/android/annotation/DimenRes.java
new file mode 100644
index 0000000..02ae00c
--- /dev/null
+++ b/android-35/android/annotation/DimenRes.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2013 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.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * Denotes that an integer parameter, field or method return value is expected
+ * to be a dimension resource reference (e.g. {@link android.R.dimen#app_icon_size}).
+ *
+ * {@hide}
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface DimenRes {
+}
diff --git a/android-35/android/annotation/Dimension.java b/android-35/android/annotation/Dimension.java
new file mode 100644
index 0000000..5f705ad
--- /dev/null
+++ b/android-35/android/annotation/Dimension.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2016 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.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * Denotes that a numeric parameter, field or method return value is expected
+ * to represent a dimension.
+ *
+ * {@hide}
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD,PARAMETER,FIELD,LOCAL_VARIABLE,ANNOTATION_TYPE})
+public @interface Dimension {
+ @Unit
+ int unit() default PX;
+
+ int DP = 0;
+ int PX = 1;
+ int SP = 2;
+
+ @IntDef({PX, DP, SP})
+ @Retention(SOURCE)
+ @interface Unit {}
+}
diff --git a/android-35/android/annotation/Discouraged.java b/android-35/android/annotation/Discouraged.java
new file mode 100644
index 0000000..d4e4dfc
--- /dev/null
+++ b/android-35/android/annotation/Discouraged.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2021 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.annotation;
+
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD