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> &lt;service android:name=".MyAccessibilityService"
+ *         android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"&gt;
+ *     &lt;intent-filter&gt;
+ *         &lt;action android:name="android.accessibilityservice.AccessibilityService" /&gt;
+ *     &lt;/intent-filter&gt;
+ *     . . .
+ * &lt;/service&gt;</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> &lt;service android:name=".MyAccessibilityService"&gt;
+ *     &lt;intent-filter&gt;
+ *         &lt;action android:name="android.accessibilityservice.AccessibilityService" /&gt;
+ *     &lt;/intent-filter&gt;
+ *     &lt;meta-data android:name="android.accessibilityservice" android:resource="@xml/accessibilityservice" /&gt;
+ * &lt;/service&gt;</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>&lt;{@link android.R.styleable#AccessibilityService accessibility-service}&gt;</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>&lt;{@link android.R.styleable#AccessibilityService accessibility-service}&gt;</code>
+     * tag. This is a sample XML file configuring an accessibility service:
+     * <pre> &lt;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"
+     *     . . .
+     * /&gt;</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>&lt;accessibility-shortcut-target&gt;</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>
+ *   &lt;intent-filter&gt;
+ *     &lt;action android:name="android.accounts.AccountAuthenticator" /&gt;
+ *   &lt;/intent-filter&gt;
+ *   &lt;meta-data android:name="android.accounts.AccountAuthenticator"
+ *             android:resource="@xml/authenticator" /&gt;
+ * </pre>
+ * The <code>android:resource</code> attribute must point to a resource that looks like:
+ * <pre>
+ * &lt;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"
+ * /&gt;
+ * </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>
+ * &lt;PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"&gt;
+ *    &lt;PreferenceCategory android:title="@string/title_fmt" /&gt;
+ *    &lt;PreferenceScreen
+ *         android:key="key1"
+ *         android:title="@string/key1_action"
+ *         android:summary="@string/key1_summary"&gt;
+ *         &lt;intent
+ *             android:action="key1.ACTION"
+ *             android:targetPackage="key1.package"
+ *             android:targetClass="key1.class" /&gt;
+ *     &lt;/PreferenceScreen&gt;
+ * &lt;/PreferenceScreen&gt;
+ * </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&lt;?&gt;</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>&lt;selector></code> element.
+ * Each State Animator is defined in a nested <code>&lt;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>
+ *  &#64;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>
+ *  &#64;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>
+ *  &#64;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>
+     *  &#64;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
+ *  &#64;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:
+ *  &#64;ChecksSdkIntAtLeast(parameter = 0, lambda = 1)
+ *  inline fun fromApi(value: Int, action: () -> Unit) {
+ *      if (Build.VERSION.SDK_INT >= value) {
+ *          action()
+ *      }
+ *  }
+ *
+ *  // Java field:
+ *  &#64;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 &#64;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 &#64;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 &#64;Deprecated annotation in the SDK.
+ *
+ * <p>The value parameter should be the message to include in the documentation as a &#64;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 &#64;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